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
9 changes: 4 additions & 5 deletions apps/server/src/mcp/toolkits/pair/logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
Expand Down
10 changes: 5 additions & 5 deletions apps/server/src/mcp/toolkits/pair/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 10 additions & 11 deletions docs/internals/delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions docs/user/agent-delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions packages/client-runtime/src/state/pair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down
7 changes: 2 additions & 5 deletions packages/client-runtime/src/state/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading