feat(harness): add agy (Antigravity CLI) crewmate adapter - #2
Open
RooseveltAdvisors wants to merge 19 commits into
Open
RooseveltAdvisors wants to merge 19 commits into
RooseveltAdvisors wants to merge 19 commits into
Conversation
Wires agy as a verified crewmate/scout worker runtime: detection, launch, control mechanics, turn-end wake, and teardown. The load-bearing piece is workspace trust. agy's --dangerously-skip-permissions governs tool permissions only and does NOT suppress its separate workspace-trust dialog, so every fresh task worktree would park on a dialog that renders no status-bar text at all - a wedged worker indistinguishable from an idle one. bin/fm-agy-trust.sh registers the worktree before launch and refuses rather than degrades. Its scope test is structural: only a linked git worktree of the named project is accepted, and a primary checkout, a foreign project's worktree, a worktree subdirectory, a plain directory, the home directory, and the settings directory are each refused. It neutralises CDPATH and the git environment overrides that could otherwise defeat those refusals, follows a store symlink only to a target this user owns, requires node rather than degrading without it, and refuses a store that changed underneath it rather than clobbering agy's own write. bin/fm-agy-turnend-hook.sh owns one firstmate-turn-end key in agy's global hooks.json, preserving every operator hook, plus a silent always-zero hook script and a private per-task token registry. The hook fires only when the Stop payload reports fullyIdle true: agy backgrounds a command that outruns its own wait, yields the composer, and fires Stop with fullyIdle false while that command is still running. Two paths end an agy turn with no Stop event and are documented rather than papered over: a declined tool call, and an Escape interrupt. A crewmate launches with --dangerously-skip-permissions so it never reaches the first, and firstmate initiates its own interrupts; the watcher's staleness check is the backstop for both. agy is crewmate/scout only. A secondmate is a firstmate instance and needs a primary supervision protocol, which agy has no verified path for, so fm-spawn refuses a --secondmate launch on it. agy's rendered status bar is a delivery guard only, never a recorded worker state: fm-busy-lib.sh forbids a second rendered-text classifier, so agy is deliberately unarmed and its documented upgrade path is a semantic PreInvocation/Stop pair. tests/fm-agy-harness.test.sh proves the accepted path and every refusal with no harness installed; FM_AGY_SIGNALS_LIVE_E2E=1 tests/fm-agy-signals-live-e2e.test.sh proves the trust dialog, both status bars, typed submission, and the Stop hook against the real binary and fails naming the harness and version, because agy self-updates.
…nd live-guard cleanup
Upstream owns the shared contract; agy is added alongside as one more harness using it, never as a competing implementation. - fm-spawn.sh: take upstream's `if [ "$KIND" != secondmate ]` trust pre-registration block from kunchenguid#3663 verbatim for the claude path and add `agy)` as a second arm, so agy uses that contract rather than its own parallel `if`. gemini's launch clear-set is split out of the shared arm so it stays byte-identical to upstream while the shared arm also drops an inherited agy marker. - fm-harness.sh: keep both detection blocks. agy is ordered before gemini because agy is built on the same gemini_coder tree and whether it also exports GEMINI_CLI is unverified; testing agy's own marker first is correct under both possibilities and cannot change gemini's verdict. - fm-control-lib.sh, docs, SKILL.md: union of both harness rosters. - tests: upstream's spawn fixture now pins HOME to $home/user-home so a trust pre-registration cannot reach the developer's real store, and that pin beats an outer HOME= on the call. The agy spawn tests now assert against that sandboxed store, which is the same one claude registers in. No behaviour changes for any harness other than agy: the claude trust arm and gemini's launch clear-set were both diffed against 8f7b79c and are identical.
RooseveltAdvisors
added a commit
that referenced
this pull request
Sep 5, 2026
… the CI failure is the fixture's `tasks-axi add` (beads backend) failing instantly on the runner, then hiding behind three diagnosability defects that this change fixes in tests/fm-stale-sweep.test.sh: (1) tasks-axi reports its errors only on stdout, which every fixture call discarded via >/dev/null, so the captured fixture log was empty exactly when a diagnosis was needed — the inner redirects are removed so both streams land in the log; (2) `bd init`'s exit status and output were silently ignored although bd init can exit non-zero while leaving a half-usable graph — it is now checked and its log captured; (3) make_fixture runs inside $(...), so its fail() only killed the subshell and the suite cascaded into the misleading 'backend is not beads' sweep against an empty FM_HOME — fx/bd-init failures now write a marker file that read_fixture (running in the real test shell) checks to abort the whole suite with the captured log attached. Verified locally: sabotaged-bd and failing-tasks-axi shims both abort loudly with the real error named; full suite passes (10/10, unchanged behavior); bin/fm-lint.sh clean. Note: the runner-side bd failure itself is not reproducible locally (identical OS/toolchain passes every simulation), so the next CI run will name the exact underlying error via the captured fixture log. Greptile Review: P1 #1 (captain-hold answer racing the sweep) was already fixed at 124a5e5 — fm-captain-hold now holds the per-task record lock across answer/hold, with a regression test in captain-hold-lifecycle; the failing Greptile run is stale against that head. P1 #2 (bare `tasks-axi done`/`hold` racing the second-proof→reopen window) needs no code change: verified locally that reopen leaves no detectable trace and tasks-axi offers no compare-and-swap, so no post-reopen guard is possible; the sweep already refuses when the record lock is held, refuses on a pending backlog-close replay, and re-proofs immediately before reopening — only an actor bypassing the documented locking protocol can hit the sub-second window, which is an author-response (comment resolution) matter, not a defect introduced by this change
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Make agy (Antigravity CLI) usable as a firstmate crewmate/scout worker runtime, so a worker can be spawned on agy, reach its brief without a human answering a workspace-trust dialog, and report turn end back to firstmate's supervision.
Requirements in their current accepted form:
What Changed
Add agy across the adapter plane: harness detection tests
ANTIGRAVITY_CONVERSATION_IDbeforeCLAUDECODE/GEMINI_CLI(with anchoredagyancestry fallback), a new-ilaunch template with foreign-marker clearing and a verified--model/--effortaxis, control verbs on the default backend (interrupt, exit, wiring paths, turn-end registry), tmux/herdr liveness identity, and dispatch/bootstrap/quota registration. agy is crewmate/scout only —fm-spawnrefuses a--secondmatelaunch since no primary supervision protocol exists.Pre-register agy workspace trust at spawn via new
bin/fm-agy-trust.sh(structural linked-worktree scope test writingtrustedWorkspacesin~/.gemini/antigravity-cli/settings.json), since--dangerously-skip-permissionsdoes not suppress the trust dialog and no environment bypass exists. Trust is withdrawn exactly for the spellings this spawn added — at teardown while the task still owns the path, after rollback, and by spawn abort cleanup when no task record was published.Add new
bin/fm-agy-turnend-hook.shinstalling a global Stop hook in~/.gemini/config/hooks.jsongated onfullyIdle true(a shell command outrunning agy's wait emits two Stop events), which wakes supervision through a gitignored.fm-agy-turnendpointer and a per-task token registry entry; document known gaps (a declined tool call or Escape interrupt ends a turn with no Stop event), add harness adapter reference docs and runtime-backend verification notes, and pin new/updated suites (fm-agy-harness,fm-agy-signals-live-e2e, teardown, herdr, tmux liveness) against the real binary.Risk Assessment
Testing
Completed 1 recorded test check.
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-agy-turnend-hook.sh:208- The hooks.json install rewrites the whole file via read-then-atomic-rename without the fingerprint-and-retry guard its sibling writer has: bin/fm-agy-trust.sh fingerprints settings.json specifically because agy sessions rewrite that store concurrently, while the hooks.json edit would silently drop an external write landing between readConfig and rename. This is acceptable today (agy exposes no hooks subcommand and plausibly never writes this file itself, and concurrent firstmate installs are race-tested), but if agy is ever observed writing hooks.json, this control should adopt the same moved-store refusal as the trust writer rather than clobbering operator hooks.bin/fm-composer-lib.sh:315- Adding agy'sesc to cancelto FM_DELIVERY_BUSY_REGEX_DEFAULT widens the union used when no harness is recorded (herdr's rendered busy state without a harness argument): any unrecorded pane rendering that string now classifies busy. A recorded muse pane never matches (it falls into the never-borrow branch), so an unrecorded muse pane is now treated differently from a recorded one; the effect is conservative (a submit is refused, never falsely delivered), so no behavioral fix is requested, but the widening is a deliberate cross-harness surface change worth being aware of.bin/fm-test-run.sh --changed --exclude-family real-herdr-gated✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.