Add connector-agy: Google Antigravity CLI as an autonomous mesh worker - #255
Add connector-agy: Google Antigravity CLI as an autonomous mesh worker#255Wally-Ahmed wants to merge 1 commit into
Conversation
A Node shim (dist/serve.js) owns ONE MeshAgent and drives the Antigravity CLI (agy) as a chain of non-interactive agy -p turns, each under a pseudo-TTY (script -qec — load-bearing: non-TTY agy -p exits 0 but drops its final response), resuming via --conversation with the id captured from the per-turn agy log. The cotal_* tool surface is served over a local streamable-HTTP MCP server wired in through the global ~/.gemini/config/mcp_config.json (merged on start, removed on exit — agy has no per-invocation MCP flag; ONE agy worker per machine in v1, fail-loud on a pre-existing entry). TypeScript under the shared strict tsconfig.base.json, sibling-connector package contract (types + typecheck + declaration emit + files whitelist + publishConfig), and a changesets fixed-group entry. v1 fail-loud surface: no resume, no transcript mirror, no tool-sharing, no launchOptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aa718a5 to
59e35fb
Compare
|
Rebased onto current main ( |
|
Closing as superseded by both upstream Antigravity changes and Cotal's current connector contract. Thank you for the original integration work. The current public A future Antigravity connector should be a new current-main edge package built against the documented JSON or stream-JSON surfaces, current launch material, and current extension seed/update rules rather than a rewrite of this historical branch. |
This adds
extensions/connector-agy, which spawns Google's Antigravity CLI (agy) as afull mesh worker:
cotal spawn worker --agent agy --detach.Design. A Node shim owns the mesh agent and runs
agy -pone turn at a time under apseudo-TTY (agy drops its final response on a non-TTY stdout while exiting 0). The first
turn's log yields the conversation id; later turns pass
--conversation <id>so contextpersists, and the shim fails loudly if a resume turn unexpectedly creates a fresh
conversation. The
cotal_*tools are served over a local streamable-HTTP MCP endpoint,merged into the global
~/.gemini/config/mcp_config.jsonfor the worker's lifetime andremoved on shutdown and process exit.
--add-dir <workRoot>is passed on every turn soartifacts land in the work directory instead of agy's own workspace scratch.
The shim, in detail. The process
cotal spawnkeeps alive is a Node supervisor, notagy: it owns the
MeshAgentidentity and inbox plus a loopback streamable-HTTP MCPendpoint (ephemeral
127.0.0.1port, freshMcpServerper request — no MCP sessionstate to lose across respawns). agy has no per-invocation MCP flag, so the shim merges an
mcpServers.cotalentry into the global~/.gemini/config/mcp_config.jsononce theserver is listening, and removes it on
shutdown(), onprocess.on("exit"), and in thefatal-error path. It refuses to start over an existing
cotalentry rather than adoptit — that is the one-worker-per-machine limit, fail-loud by design.
The wake/drain pump is identical to the Codex sibling (#254): event-driven wakes with a
300 ms debounce so bursts coalesce, a single-flight loop that drains the inbox into one
📨 Cotal — N new message(s)injection per turn, and mid-turn arrivals buffering untilthe child exits. What differs is everything about how a turn runs.
A turn spawns
script -qec '<agy command>' /dev/nullin its own process group(
detached: true). The pseudo-TTY wrapper is load-bearing, not cosmetic: on a non-TTYstdout
agy -pexits 0 while dropping its final response, so a bare spawn looks likesuccess with empty output;
script -epropagates agy's real exit code. The wrappedcommand is
agy -p "$(cat '<promptFile>')" --model … --dangerously-skip-permissions --print-timeout 25m --add-dir <workRoot> --log-file <logFile>, plus--conversation <id>after the first turn. The prompt travels via a single-quote-escapedtemp file under a
mkdtemproot (immune to argv limits), and--add-diris what makesartifacts land in the work directory instead of agy's own workspace scratch. The child
env strips every
COTAL_*variable, prepends~/.local/binto PATH, and defaultsTERM=xterm-256color. Output is plain text — ANSI-stripped, last 200 KB kept.Continuity rides on agy's server-side conversation state
(
~/.gemini/antigravity-cli/conversations/): turn 1's log is scanned forCreated conversation <uuid>, later turns pass it back, and the shim fails loudly if aresume turn unexpectedly creates a conversation — a silently-broken
--conversationwas the failure mode we most feared, and
COTAL_AGY_STATELESS=1exists as arolling-digest fallback if a future agy release breaks resume. Failure paths mirror the
process-group reality: a wedged turn hits the 27-minute timeout and gets SIGKILL on the
negative pid — killing only
scriptwould leave agy alive underneath it — and shutdownsends the group a SIGTERM. Ack semantics are the same deliberate at-most-once as #254
and worth stating plainly for review: drains ack at drain time, before the spawn, so a
batch whose turn died is not redelivered (replaying a possibly-half-executed turn would
replay its side effects); crash-tolerance instead comes from the conversation itself
surviving — the next wake resumes the same id.
Code standards. TypeScript under the shared strict
tsconfig.base.json(NodeNext,verbatimModuleSyntax), typed against theConnector/LaunchOpts/LaunchSpecsurface. The package contract mirrors the sibling connectors:
typesfield +typesexports condition,
typecheckscript, declaration emit + esbuild bundles inbuild,fileswhitelist,publishConfig,prepublishOnly.@cotal-ai/connector-agyis addedto the changesets
fixedgroup so it versions in lockstep, andpnpm-lock.yamlcarriesthe new workspace importer (
pnpm install --frozen-lockfileverified).Prior art. No Antigravity connector has existed in-tree or on npm. The existing
connectors all deliver by pushing into a live harness process — hook-injected context at
turn boundaries (Claude Code), API-injected turns (OpenCode), a bridge socket (Hermes),
mid-turn steering (pi) — which requires a cooperation surface the closed
agybinarydoesn't offer: no lifecycle hooks, no wake-notification channel, no server/host mode, not
even a per-invocation MCP flag. This connector therefore uses the same push-by-respawn
pipeline as the sibling Codex PR (#254): the persistent process is the shim, and each
drained inbox batch becomes a fresh
agy -p --conversation <id>turn — no idle sessionever needs waking. If agy later ships hooks or a host API, a live-session connector can
layer on without changing the mesh-facing contract.
Deliberate v1 limits (all fail loudly rather than silently degrade):
to start if a
cotalentry already exists (a stale entry from an unclean shutdown mustbe deleted first).
supportsModelVariant: false— agy bakes the reasoning level into the model displayname (e.g. "Gemini 3.1 Pro (High)"), selected via
COTAL_MODEL.COTAL_AGY_STATELESS=1opts into a rolling-digest fallback (persona + recenttranscript per turn) if conversation resume misbehaves in a future agy release.
Testing. Builds in-tree (
pnpm --filter "@cotal-ai/connector-agy..." run build),pnpm --filter @cotal-ai/connector-agy run typecheckpasses, the esm bundle importsclean, and
pack --dry-runships exactlydist/+ manifest. Live-verified on a realmesh with this same shim logic: spawn joins, mcp_config merge preserves existing entries,
DM round-trip publishes to a channel, multi-turn resume holds context, stop removes the
MCP entry cleanly. Also used as a benchmark arm across a 20+ task hermetic suite —
including runs where agy's native harness beat a foreign harness driving the same Gemini
model by ~1.75× at equal quality.
https://claude.ai/code/session_012MLLWMoW1gTp8fft1bWNQv