diff --git a/apps/server/src/mcp/toolkits/pair/logic.test.ts b/apps/server/src/mcp/toolkits/pair/logic.test.ts index f463d0f00f..cb45f9da8d 100644 --- a/apps/server/src/mcp/toolkits/pair/logic.test.ts +++ b/apps/server/src/mcp/toolkits/pair/logic.test.ts @@ -171,12 +171,11 @@ describe("pair message ids and title", () => { }); describe("pair lead support", () => { - it("refuses the providers whose own subagents Pylon cannot pause", () => { - // Antigravity offers no control. Codex 0.153 keeps its collaboration tools - // whatever feature flags say, and a paired Codex lead used them every time. + it("refuses only the provider that cannot be steered away from its own subagents", () => { + // Codex keeps its collaboration tools whatever feature flags say, but once it + // receives the pair protocol it briefs the executor and leaves them alone. expect(isPairLeadSupported("antigravity")).toBe(false); - expect(isPairLeadSupported("codex")).toBe(false); - for (const driver of ["claudeAgent", "primeAgent", "cursor", "opencode", undefined]) { + for (const driver of ["claudeAgent", "codex", "primeAgent", "cursor", "opencode", undefined]) { expect(isPairLeadSupported(driver)).toBe(true); } }); diff --git a/apps/server/src/mcp/toolkits/pair/logic.ts b/apps/server/src/mcp/toolkits/pair/logic.ts index 8095fe4f29..6536003db3 100644 --- a/apps/server/src/mcp/toolkits/pair/logic.ts +++ b/apps/server/src/mcp/toolkits/pair/logic.ts @@ -86,13 +86,13 @@ export function pairExecutorTitle(leadTitle: string): string { /** * Whether a provider can lead a pair. - * Antigravity has no per-session control over its own subagents. Codex has - * feature flags for them, but 0.153 keeps its `collaboration.*` tools with both - * flags off, and a paired Codex lead briefed its own subagent every time it was - * tried. Both still work as the executor. + * Antigravity has no per-session control over its own subagents and receives no + * Pylon instructions, so it can only be the executor. Codex cannot have its + * `collaboration.*` tools removed either, but it follows the pair protocol once + * that reaches it as thread developer instructions, so it can lead. */ export function isPairLeadSupported(leadDriver: string | undefined): boolean { - return leadDriver !== "antigravity" && leadDriver !== "codex"; + return leadDriver !== "antigravity"; } export interface ProtectedPathRecord { diff --git a/docs/internals/delegation.md b/docs/internals/delegation.md index 43d988b324..d13ce3a9db 100644 --- a/docs/internals/delegation.md +++ b/docs/internals/delegation.md @@ -121,17 +121,16 @@ ships. A pair started mid-session reaches the lead through the `pair_start` resu same protocol text; the tool denial applies from the next session start. Prime Agent's own subagent depth is not yet held while paired. -Two providers cannot lead and are refused by `pair_start` and by the clients, though both work as the -executor. Antigravity offers no per-session control over its own subagents. Codex has the two flags -above, but Codex 0.153.4 keeps all six `collaboration.*` tools with both off (checked with -`codex exec -c features.multi_agent=false -c features.multi_agent_v2=false`), and in four live runs a -paired Codex lead spawned its own subagent on its own model and never called a pair tool. Part of -the cause was that Pylon's instructions did not reach Codex at all (see the Codex protocol traps in -[providers](providers.md)); its `t3-code` tools are deferred behind tool discovery, so a lead that is -never told about them does not find them. Codex stays refused until a paired run on a fresh thread -shows it briefing the executor with its collaboration tools still present. -A Claude lead was verified end to end: `pair_handoff`, `pair_await`, the executor's own session -writing the file, and the lead checking it. +Antigravity cannot lead and is refused by `pair_start` and by the clients, though it works as the +executor: it offers no per-session control over its own subagents and receives no Pylon +instructions. Codex can lead, but for it the protocol is the only control. Codex 0.153.4 keeps all +six `collaboration.*` tools with both flags above off (checked with +`codex exec -c features.multi_agent=false -c features.multi_agent_v2=false`), and its `t3-code` +tools are deferred behind tool discovery, so a Codex lead that is not told about the pair tools +spawns its own subagent on its own model instead. That happened in every run until Pylon's +instructions reached Codex (see the Codex protocol traps in [providers](providers.md)). With them, a +fresh Codex lead made one `pair_handoff` and one `pair_await`, a Claude executor did the work, and no +collaboration tool was called. A Claude lead was verified the same way. The executor follows its lead. `PairLifecycleReactor` watches domain events and dispatches existing commands: archiving, settling, or deleting a lead does the same to its executor, including an diff --git a/docs/user/agent-delegation.md b/docs/user/agent-delegation.md index b3a177b77c..cca46abe7c 100644 --- a/docs/user/agent-delegation.md +++ b/docs/user/agent-delegation.md @@ -144,10 +144,10 @@ message, for example “Pair with Antigravity for this.” While a thread is paired, the lead's own subagents are paused for that thread only, so implementation goes to the executor. Your provider's settings are not changed and other threads are -unaffected; this takes effect the next time the lead's session starts. Antigravity and Codex can be -the executor but cannot lead a pair yet: Pylon has no way to pause their own subagents, and a Codex -lead hands work to those instead of the executor. On a thread using either one, **Pair** is -unavailable and says why. +unaffected; this takes effect the next time the lead's session starts. Antigravity can be the +executor but cannot lead a pair, because Pylon has no way to pause its own subagents; on an +Antigravity thread **Pair** is unavailable and says why. Codex can lead, though its own subagents +cannot be switched off, so it is told to leave them alone rather than prevented from using them. The executor appears under its parent in the sidebar like any delegated thread. Archive it to turn the pair off for that thread. Archiving, settling, or deleting the lead does the same to its diff --git a/packages/client-runtime/src/state/pair.test.ts b/packages/client-runtime/src/state/pair.test.ts index ce3335df68..ceb840a4b3 100644 --- a/packages/client-runtime/src/state/pair.test.ts +++ b/packages/client-runtime/src/state/pair.test.ts @@ -91,8 +91,15 @@ const lead = (driverKind: string | null | undefined = "claudeAgent") => ({ describe("isPairLeadSupported", () => { it("refuses the providers the server refuses", () => { expect(isPairLeadSupported("antigravity")).toBe(false); - expect(isPairLeadSupported("codex")).toBe(false); - for (const driver of ["claudeAgent", "primeAgent", "cursor", "opencode", null, undefined]) + for (const driver of [ + "claudeAgent", + "codex", + "primeAgent", + "cursor", + "opencode", + null, + undefined, + ]) expect(isPairLeadSupported(driver)).toBe(true); }); }); diff --git a/packages/client-runtime/src/state/pair.ts b/packages/client-runtime/src/state/pair.ts index 71d9e7c1a1..107ebc41c6 100644 --- a/packages/client-runtime/src/state/pair.ts +++ b/packages/client-runtime/src/state/pair.ts @@ -40,12 +40,9 @@ export type PairState = readonly activity: string | null; }; -/** - * Mirrors the server's rule. Antigravity offers no control over its own - * subagents, and Codex keeps its collaboration tools whatever Pylon passes it. - */ +/** Mirrors the server's rule: Antigravity offers no control over its own subagents. */ export function isPairLeadSupported(driverKind: string | null | undefined): boolean { - return driverKind !== "antigravity" && driverKind !== "codex"; + return driverKind !== "antigravity"; } export function resolvePairState(input: {