feat(bin): add agy crew harness adapter pinned to gemini-3.1-pro-high - #2366
Closed
BohnBawerick wants to merge 13 commits into
Closed
BohnBawerick wants to merge 13 commits into
BohnBawerick wants to merge 13 commits into
Conversation
Verify Antigravity CLI as a crewmate/scout adapter. Pin the launch to gemini-3.1-pro-high, omit --effort (it conflicts with *-high model ids), accept the workspace trust dialog, and install a gated global Stop hook. Secondmate, primary turn-end, and watcher-arm remain unverified.
A separated greater-than pair is unknown on zellij, cmux, and orca because agy is not verified there. Identity-capable backends keep the existing probe-then-shortcut path.
…derive teardown pointers
|
Closed as superseded — this work already landed on main via #4200. — Kun's Firstmate |
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
Verify agy as a firstmate crew harness and wire the adapter so crew dispatch can use Antigravity CLI, not only Grok.
Standing rules: agy is always gemini-3.1-pro-high, never a claude-* model it also offers (the arbiter reading agy output is Claude; running Claude inside agy collapses two voices). agy must not be dispatched until the adapter is verified; this work is evidence, not optimism. Adding agy must not put another harness at risk.
Verification required: launch a trivial supervised task through fm-spawn's raw-launch-command escape hatch; establish empirically the launch command, autonomy flag, model and effort flags, trust/permission dialog on a fresh worktree and what clears it, busy/idle signal, composer empty vs pending, interrupt key and exit command, whether a status-file append from inside agy wakes firstmate, and whether the plugin subcommand can carry a turn-end hook.
Wire only what was proved, in every owner the harness-adapters skill names: fm-spawn.sh, fm-busy-lib.sh, fm-composer-lib.sh (the one fleet-wide composer owner), fm-control-lib.sh, fm-harness.sh, and the skill itself. Be explicit about surfaces not verified (secondmate launch, primary turn-end guard, watcher-arm guard). Do not add agy to config/crew-dispatch.json; verification and routing are separate, and routing is the captain's.
Keep it test-driven and simple. If a surface fails verification, land what works and state plainly what does not.
Observed and accepted implementation choices: pin --model gemini-3.1-pro-high and omit --effort because pairing --effort with a -high model id made agy 1.1.12 fall back to Gemini 3.6 Flash High; keep --prompt-interactive last because placing it before --model consumed --model as the prompt; --dangerously-skip-permissions and --model are named options (agy --help) and so are order-independent relative to each other, and firstmate emits --dangerously-skip-permissions then --model then --prompt-interactive; refuse claude- models at spawn; treat agy as crewmate/scout only because secondmate, primary turn-end, and watcher-arm were not verified; leave semantic busy unknown because Stop does not fire on Escape interrupt; install a gated global Stop hook in ~/.gemini/config/hooks.json because project-local .agents/hooks.json loaded zero files; accept the workspace trust dialog with Enter when visible because --dangerously-skip-permissions does not suppress it; scan every workspacePaths entry for the turn-end pointer; share PATH binary resolution across muse/agy/kimi.
Captain-decided composer classifier narrowing, already applied, do not reopen as unanswered design:
Push target is the fork; open the PR against upstream kunchenguid/firstmate (same route as hz-kimi-trust). The PR body MUST include these two notes:
What Changed
agy(Antigravity CLI) as a crewmate/scout-only adapter across every harness owner:fm-spawn.shgains anagylaunch template (--dangerously-skip-permissions --model <model> --prompt-interactive <brief>with--prompt-interactivelast), a PATH binary resolver, a--model gemini-3.1-pro-highdefault pin, a hard refusal ofclaude-*models, no--effortflag, a bounded workspace-trust dialog wait that presses Enter at most once, and refusals for both local and remote secondmate launches;fm-harness.shdetects it by exactagyprocess ancestry,fm-control-lib.shregisters Escape interrupt //exit/ wiring paths,fm-busy-lib.shdocuments the absent armed busy writer,fm-session-lock-lib.shandbin/backends/tmux.shclassifyagyas an agent process, andfm-bootstrap.shaccepts it as a verified harness with no effort axis.bin/fm-agy-turnend-hook.sh, the sole owner of one surgically named Firstmate key in the global~/.gemini/config/hooks.json, installing a silent always-zero Stop hook that scans everyworkspacePathsentry for a.fm-agy-turnendpointer resolving through a private token registry; install refuses on symlinked, non-regular, malformed, or foreign config/script/registry state, andremovede-registers the key before the file guards run.>shape is recognised only behind identity: a newFM_COMPOSER_SEPARATED_PROMPT_GLYPHSset holds>alone, the shortcut requires thePAIR_VALIDgeometry gate plushas_identity=1, and it defers to_fm_composer_pi_verdictwhen identity is unfetched or names pi. Teardown replaces the per-adapter grok/kimi cleanup functions withremove_turnend_authandremove_turnend_worktree_pointers, both driven by the newfm_control_turnend_global_harnesseslist, andresolve_muse_binary/resolve_kimi_binary/resolve_agy_binarynow shareresolve_path_binary. Newtests/fm-agy-harness.test.sh(19 cases) plus additions to the composer, bootstrap, control, and tmux-liveness suites cover launch shape, model refusal, secondmate refusal, marker clearing, raw-launch, hook install/remove gating, payload framings, trust-dialog success and failure, and teardown.config/crew-dispatch.jsonis deliberately untouched.Notes for review
agy-trust-wait-no-midturn-ready-signal(on identityless backends the trust wait burns the full poll budget because composer state is unknown and the mid-turn footer readsesc to cancel), andagy-claude-model-refusal-is-post-stop-on-relaunch(theclaude-*model refusal is spawn-side only, so a mistyped relaunch with--model claude-*stops a healthy crewmate before spawn refuses).Risk Assessment
>pair and teardown's pointer removal for every harness - so it is safe to merge with the two informational items as follow-ups rather than being purely well-bounded.Testing
Ran the new agy harness suite plus the fleet-wide suites the change touches (composer lib and ghost, control plane and relaunch, real-tmux liveness, teardown, busy adapter wiring, zellij/cmux/orca) - all pass. On top of that I drove a full manual end-to-end: a real tmux server, the real fm-spawn.sh/fm-peek.sh/fm-teardown.sh, and a stubbed agy binary (sandboxed HOME, no Google quota spent), capturing the captain-visible spawn line, the exact argv agy would receive, the rendered crewmate pane, the classifier verdict on that live pane, the gated Stop-hook wake, both spawn refusals, and full teardown cleanup; the launch flags and the pinned/forbidden model ids were cross-checked against the really installed agy 1.1.13. Visual evidence is the captured TUI pane text from fm-peek.sh, which is the actual end-user surface for this terminal-only change - there is no graphical surface to screenshot. One pre-existing, environment-caused failure in tests/fm-bootstrap.test.sh is unrelated to this change and reproduces identically at the base commit.
Evidence: agy crew adapter end-to-end transcript (real tmux, real fm-spawn/fm-peek/fm-teardown)
===== 1. captain-visible spawn result (bin/fm-spawn.sh <id> <project> agy) ===== spawned agy-e2e-x1 harness=agy kind=ship mode=no-mistakes yolo=off window=firstmate:fm-agy-e2e-x1 worktree=/tmp/fm-agy-e2e/wt exit=0 ===== 2. argv the real agy binary would have received ===== --dangerously-skip-permissions --model gemini-3.1-pro-high --prompt-interactive FIRSTMATE_OP: v1 launch-brief: Read docs/architecture.md and report the top-level layout in two sentences. ===== 3. recorded task metadata ===== harness=agy kind=ship model=gemini-3.1-pro-high ===== 4. turn-end wiring minted by the spawn ===== worktree pointer $SANDBOX/wt/.fm-agy-turnend -> token=fm.aWMC9Jt1z7EM registry entry ~/.gemini/config/fm-agy-turn-end.d/fm.aWMC9Jt1z7EM -> $SANDBOX/home/state/agy-e2e-x1.turn-ended hooks.json Stop key: {"fm-agy-turn-end": {"Stop": [{"type": "command", "command": "$SANDBOX/home/.gemini/config/fm-agy-turn-end.sh", "timeout": 10}]}} ===== 5. the live agy crewmate pane, as the captain sees it (bin/fm-peek.sh) ===== Antigravity CLI ──────────────────────────────────────────── > ──────────────────────────────────────────── ? for shortcuts Gemini 3.1 Pro · high ===== 6. composer verdict on that live pane (shared classifier) ===== composer-state: empty agent-liveness: alive ===== 6b. agy's Stop event wakes firstmate for THIS task only ===== Stop in the task worktree -> wake marker written: $SANDBOX/home/state/agy-e2e-x1.turn-ended Stop in an unrelated workspace -> no wake marker (correctly ignored) ===== 7. the same spawn refused for a claude-* model (standing pin) ===== error: agy must not run a claude-* model; the captain's standing pin is gemini-3.1-pro-high ===== 8. the same spawn refused as a secondmate (crewmate/scout only) ===== error: agy is a verified crewmate/scout adapter only and cannot run a secondmate; no primary supervision protocol has been verified for Antigravity CLI ===== 9. teardown retires every agy pointer (bin/fm-teardown.sh) ===== teardown agy-e2e-x1 complete (window firstmate:fm-agy-e2e-x1, worktree $SANDBOX/wt) retired: $SANDBOX/wt/.fm-agy-turnend retired: $SANDBOX/home/state/agy-e2e-x1.agy-turnend-token retired: $SANDBOX/home/.gemini/config/fm-agy-turn-end.d/fm.aWMC9Jt1z7EMEvidence: real Antigravity CLI 1.1.13 model list (pin exists; claude-* ids the adapter refuses)
gemini-3.1-pro-high Gemini 3.1 Pro (High) gemini-3.1-pro-low Gemini 3.1 Pro (Low) claude-sonnet-4-6 Claude Sonnet 4.6 (Thinking) claude-opus-4-6-thinking Claude Opus 4.6 (Thinking) gpt-oss-120b-medium GPT-OSS 120B (Medium)Evidence: real agy --help: the three launch flags are named options
--dangerously-skip-permissions Auto-approve all tool permission requests without prompting --effort Reasoning effort for the current CLI session (low|medium|high) --model Model for the current CLI session --prompt-interactive Run an initial prompt interactively and continue the sessionEvidence: reproduction script for the end-to-end verification
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-spawn.sh:2673- The agy turn-end mint runs off $HARNESS, but the registry directory it writes into is only created by the installer in the*__AGYBIN__*template block (bin/fm-spawn.sh:1505). A raw launch command whose first word isagysets HARNESS=agy (bin/fm-spawn.sh:1209) without ever expanding a template, somktemp "$AGY_AUTH_DIR/fm.XXXXXXXXXXXX"fails and, under the file-levelset -eu, kills fm-spawn after the backend window and worktree already exist - leaving a half-spawned task and a bare mktemp error. grok's arm avoids exactly this withmkdir -p "$GROK_AUTH_DIR"at bin/fm-spawn.sh:2571. tests/fm-agy-harness.test.sh:244 works around it with its own mkdir and the test comment accepts the residual as "identical for kimi", so this is a deliberate choice - but the raw-launch hatch is the intent's own adapter-verification path, and onemkdir -p "$AGY_AUTH_DIR"before the mktemp would close it for agy without touching kimi.bin/fm-spawn.sh:2233- agy_maybe_accept_trust states its contract as returning non-zero "only when the dialog is STILL up at the end of the budget", but when the dialog first becomes visible on the final poll (i == max-1) the accept Enter is sent and the loop exits immediately with dialog=1, so the accept is never given a single re-check. The spawn then recordsfailed: agy is still showing its workspace trust dialog, so the brief never reached the agentand exits 1 while a live agy pane is about to receive the brief - a false failure plus an orphaned working agent. The comment itself notes the ~30s budget exists because "a cold agy start reaches the dialog well after the launch keystroke", which is precisely the case that lands the dialog on a late poll. Re-poll once after sending the accept before declaring failure (for example, do not break out of the budget on the same iteration that set accepted=1).bin/fm-teardown.sh:670- remove_turnend_worktree_pointers hardcodes.fm-grok-turnend .fm-kimi-turnend .fm-agy-turnend, while the commit's stated point was to declare that adapter set exactly once (fm_control_turnend_global_harnesses) so "a new adapter of that shape cannot be wired without teardown retiring both its registry entry and the private state token". fm_control_harness_wiring_paths already owns each of those pointer paths per harness, so a fourth global-hook adapter still needs two edits and the hardcoded triple can silently drift from the declared set. Deriving the pointers from fm_control_turnend_global_harnesses + fm_control_harness_wiring_paths removes the second list.🔧 Fix: fix agy trust re-poll, raw-launch registry dir, derive teardown pointers
2 infos still open:
bin/fm-spawn.sh:2680- The newmkdir -p "$AGY_AUTH_DIR"runs before theumask 077window on the next line, so on the one path where it actually creates the directory (raw launch, where the installer never runs) the private token registry is created at the ambient umask - typically 0755 - while bin/fm-agy-turnend-hook.sh:183 deliberately creates and re-enforces it at 0700 (private_dir(REGISTRY, ..., True)). The samemkdir -palso silently follows a symlinked~/.gemini/config, which the installer refuses outright (bin/fm-agy-turnend-hook.sh:119). Exposure is narrow - token file contents stay 0600, the hook is not installed on the raw path, and the next templated agy spawn chmods the directory back to 0700 - but moving themkdir -pbelowumask 077closes the mode half for free and keeps spawn from contradicting its own installer's stated posture. grok's arm has the same shape, so this is a divergence from the agy installer rather than from the fleet.AGENTS.md:93- AGENTS.md's state/ contract table enumerates every per-task firstmate-owned state file, including<id>.grok-turnend-token,<id>.kimi-turnend-token,<id>.muse-sessionand<id>.cursor-session, but the new<id>.agy-turnend-tokenwritten by bin/fm-spawn.sh:2686 and retired by bin/fm-teardown.sh:663 was not added. AGENTS.md is edited by this change (section 4's verified-harness list) and is loaded on every turn, so the omission leaves the always-on state-directory contract incomplete for the adapter this change wires. One row after line 93 mirroring the kimi wording closes it.tests/fm-bootstrap.test.sh:906- tests/fm-bootstrap.test.sh fails on this machine at test_network_phase_partitions_the_run: the fixture removesnodefrom its fake bin dir but the machine has a real /usr/bin/node inside the test's BASE_PATH (/usr/bin:/bin:/usr/sbin:/sbin), so the expected 'MISSING: node (install:' diagnostic never appears. It reproduces identically at base commit 6789876 and is untouched by this change (bin/fm-bootstrap.sh's only edit here is the crew-dispatch harness list). Because the harness's fail() exits immediately, this abort also masks the new agy crew-dispatch validation rows in a default local run; with a node-free FM_TEST_BASE_PATH the whole script passes, including those rows. Left unfixed as pre-existing and out of this change's scope.bin/fm-test-run.sh tests/fm-agy-harness.test.sh(19 cases: detection, launch shape and model pin, effort omission, claude-* refusal, secondmate refusal, marker clearing, raw-launch hatch, hook install/remove/fire, trust dialog, teardown)bin/fm-test-run.sh tests/fm-composer-lib.test.sh tests/fm-control.test.shbin/fm-test-run.sh tests/fm-tmux-agent-liveness.test.sh(real tmux: agy pane idle=empty, typed=pending, identical screen over live pi never empty)bin/fm-test-run.sh tests/fm-composer-ghost.test.sh tests/fm-busy-adapter-wiring.test.sh tests/fm-backend-zellij.test.sh tests/fm-backend-cmux.test.sh tests/fm-backend-orca.test.sh tests/fm-teardown.test.sh tests/fm-control-relaunch.test.shFM_TEST_BASE_PATH=/tmp/fm-nodefree-bin bash tests/fm-bootstrap.test.sh(exit 0; confirms the agy crew-dispatch validation rows pass once the environment's /usr/bin/node no longer defeats the fixture)bash /tmp/no-mistakes-evidence/01KZZQRZVPPK1ZXF1EJGMAD7EM/agy-e2e-verify.sh <repo> /tmp/fm-agy-e2e- real-tmux end-to-end: fm-spawn.sh -> live agy pane -> fm-peek.sh -> fm_backend_composer_state -> Stop-hook wake -> claude-* and --secondmate refusals -> fm-teardown.shagy --helpandagy modelsagainst the really installed Antigravity CLI 1.1.13git archive 6789876 | tar -x -C /tmp/fm-base-check && bash tests/fm-bootstrap.test.sh(proves the one failure is pre-existing at the base commit)docs/verification/runtime-backends.md:203- tests/fm-composer-matrix-live-e2e.test.sh now iterates agy, but the recorded output in docs/verification/runtime-backends.md is the 2026-08-10 run and carries no agy line. I documented the gap and the regressions that pin agy meanwhile (portable captures plus the real-process identity round trip), but the evidence itself can only be refreshed by rerunning FM_COMPOSER_MATRIX_LIVE=1 on a machine with agy installed. Left as a follow-up rather than fabricating a result.docs/agent-control.md:51- docs/agent-control.md's "resume is not a verb" rationale enumerates codex/grok (session id at exit) and claude/pi/pi-signed/kimi (no contract), omitting cursor, muse, and now agy - which per the harness-adapters skill printsagy --conversation=<uuid>at exit, the same shape as codex and grok. The conclusion is unchanged and the omission predates this change by two adapters, so completing that list is an out-of-scope consolidation worth a separate pass rather than an edit here.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.