Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions .github/workflows/agents-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,29 @@ concurrency:
jobs:
native-build:
runs-on: ubuntu-22.04
timeout-minutes: 60
timeout-minutes: ${{ matrix.timeout }}
strategy:
fail-fast: false
matrix:
include:
- target: check-agents-harness-native
timeout: 60
- target: build-agents-harness
timeout: 120
env:
CARGO_HOME: /home/runner/.parsar/cache/agents-harness-cargo
CARGO_TARGET_DIR: /home/runner/.parsar/cache/agents-harness-target
AGENTS_HARNESS_NATIVE_SOURCE: /home/runner/.parsar/references/codex-native
CARGO_BUILD_JOBS: 4
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_INCREMENTAL: 0
steps:
- uses: actions/checkout@v7
- name: Reserve disk for native compilation
run: |
# These preinstalled SDKs are unused by the Rust-only job.
sudo rm -rf /usr/share/dotnet /usr/local/lib/android
df -h "$HOME"
- name: Check packaging
run: make check-agents-harness
- name: Install native build prerequisites
Expand All @@ -51,10 +65,15 @@ jobs:
- uses: actions/cache@v6
with:
path: |
~/.parsar/cache/agents-harness-cargo
~/.parsar/cache/agents-harness-target
key: agents-harness-${{ runner.os }}-1.95.0-${{ hashFiles('packages/codex-harness/source.json') }}
- name: Check native harness and independent release build
~/.parsar/cache/agents-harness-cargo/registry
~/.parsar/cache/agents-harness-cargo/git
key: agents-harness-deps-v2-${{ runner.os }}-1.95.0-${{ hashFiles('packages/codex-harness/source.json') }}
- name: Run native target
env:
NATIVE_TARGET: ${{ matrix.target }}
run: make "$NATIVE_TARGET"
- name: Report disk usage
if: always()
run: |
make check-agents-harness-native
make build-agents-harness
df -h "$HOME"
du -sh "$CARGO_HOME" "$CARGO_TARGET_DIR" 2>/dev/null || true
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ Changes to native Harness sources require `make check-agents-harness-native`,
checks require real models; do not count omitted prerequisites or mocked responses
as live acceptance. Never expand this extraction into unrelated behavioral fixes.

Native Harness CI runs checks and the release build on separate disposable runners
so debug and release artifacts do not compete for disk. It disables incremental
compilation and caches downloaded Cargo dependencies, not target directories.
The Rust-only jobs remove unused preinstalled Android/.NET SDKs and report disk
usage. Both matrix targets must pass; release optimization settings and native
test/Clippy coverage remain unchanged. These resource settings apply to CI, not
operator build defaults. Checks retain a 60-minute limit; cold optimized release
builds receive 120 minutes after the standard runner exceeded one hour with disk
space remaining. A timeout is still a failed build, not a skipped gate.

## Architecture boundaries

The following execution rules are retained from the source contributor guide.
Expand Down Expand Up @@ -1954,6 +1964,10 @@ replaced; do not carry obsolete compatibility code forward to satisfy this secti
Lease Close invalidates its writer and waits for pgx connection cleanup within
the caller deadline. A later Close can resume that wait after a timeout. This
drains client resources; it does not acknowledge remote advisory-lock release.
Tests that immediately transfer ownership must observe the previous owner's
exact database advisory lock disappearing before starting its successor. Bound
that wait and fail on query errors; do not retry Worker startup to mask competing
owners or change production lease behavior for a test's timing assumption.
Worker shutdown retains its existing bounded best-effort close policy.
This fences database writes, not already queued daemon commands or native effects.
Native quiescence/reconnect and recovery of unreported outcomes remain separate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,22 @@ func TestWorkerReconcilesEnvironmentPromotionBeforeStart(t *testing.T) {
t.Fatal("promotion did not retain the active claim", turn, err)
}
// Simulate owner loss after commit, without sending any daemon Start.
awaitRelease := observeExecutionLeaseRelease(t, pool)
if err := lease.Close(t.Context()); err != nil {
t.Fatal(err)
}
awaitRelease()
restarted, err := execution.StartWorker(t.Context(), &execution.Dispatcher{Store: s, Registry: gateway.NewRegistry()})
if err != nil {
t.Fatal(err)
}
stopped, cancel := context.WithCancel(t.Context())
cancel()
awaitRelease = observeExecutionLeaseRelease(t, pool)
if err := restarted.Run(stopped); !errors.Is(err, context.Canceled) {
t.Fatal(err)
}
awaitRelease()
turn, err = s.GetTurn(t.Context(), tenant, pending.SessionID, turnID)
var outcome struct {
ErrorCode string `json:"error_code"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestEnvironmentConnectionWorkerReconcilesAndClosesBeforeLease(t *testing.T) {
s, _ := store.NewTestStore(t)
s, pool := store.NewTestStore(t)
tenant := uuid.NewString()
session, err := s.CreateSession(t.Context(), tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "connection-worker", Configuration: []byte(`{"environment":{"type":"self_hosted","workspace_directory":"/workspace"}}`)})
if err != nil {
Expand All @@ -34,9 +34,11 @@ func TestEnvironmentConnectionWorkerReconcilesAndClosesBeforeLease(t *testing.T)
if err := lease.Store().ObserveEnvironmentConnection(t.Context(), tenant, environment.ID, generation, 1, true); err != nil {
t.Fatal(err)
}
awaitRelease := observeExecutionLeaseRelease(t, pool)
if err := lease.Close(t.Context()); err != nil {
t.Fatal(err)
}
awaitRelease()
var worker *execution.Worker
closed := false
next := uuid.NewString()
Expand Down
37 changes: 37 additions & 0 deletions services/agents-api/internal/store/execution_lease_handoff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package store_test

import (
"context"
"testing"
"time"

"github.com/jackc/pgx/v5/pgxpool"
)

// observeExecutionLeaseRelease captures the current owner before shutdown. Local
// pgx cleanup does not acknowledge the server's release of its advisory lock.
func observeExecutionLeaseRelease(t *testing.T, pool *pgxpool.Pool) func() {
t.Helper()
// Match the single-bigint key in queries/scheduling.sql, scoped to this DB.
const lock = `locktype='advisory' AND granted AND objsubid=1
AND classid::bigint * 4294967296 + objid::bigint = 706172736172
AND database=(SELECT oid FROM pg_database WHERE datname=current_database())`
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
var owner int32
if err := pool.QueryRow(ctx, "SELECT pid FROM pg_locks WHERE "+lock).Scan(&owner); err != nil {
t.Fatal("observe execution lease owner", err)
}
return func() {
t.Helper()
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
awaitDaemonRemoteCondition(t, ctx, 5*time.Second, "previous execution lease release", func() bool {
var held bool
if err := pool.QueryRow(ctx, "SELECT EXISTS (SELECT 1 FROM pg_locks WHERE "+lock+" AND pid=$1)", owner).Scan(&held); err != nil {
t.Fatal("observe execution lease release", err)
}
return !held
})
}
}
Loading