Skip to content

Commit 4609ade

Browse files
committed
Unmount wait_agents on TUI primary and nested orchestrators
1 parent a61cd76 commit 4609ade

26 files changed

Lines changed: 272 additions & 79 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
1515

1616
### Changed
1717

18+
- `wait_agents` is now exec-primary opt-in (`mountWaitAgents` in
19+
`createAgentToolset`; CL-7678). The TUI primary and nested orchestrators no
20+
longer mount it and collect fleet results through mailbox mail instead:
21+
spawn then idle, do not poll. Headless `corbits exec` keeps it mounted with
22+
an advertised allow.
1823
- Headless `corbits exec` unmounts `ask_operator` when stdin/stdout are not
1924
TTYs instead of advertising a cancel stub. TUI Skywalker still mounts it;
2025
TTY exec still prompts on stdin.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ Details live in `docs/PRODUCT.md` (safety model) and `docs/ARCHITECTURE.md`
154154
Corbits Code is a single-process CLI built on Interchange primitives. The primary
155155
session is always the **orchestrator** (Skywalker): it can act directly and
156156
delegates substantial work through a closed director fleet via `spawn_agent`
157-
then idle (mailbox mail inbound), `search_agents`, and optional `wait_agents`
158-
for nested orchestrators.
157+
then idle (mailbox mail inbound) and `search_agents`. `wait_agents` stays
158+
mounted only on headless `corbits exec` primary runs.
159159

160160
```
161161
CLI (src/index.ts)

docs/ARCHITECTURE.md

Lines changed: 11 additions & 11 deletions
Large diffs are not rendered by default.

scripts/eval-capability.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ export async function buildEvalDiagnostics(
686686
// Capability evals run through exec; they are non-TTY, so ask_operator
687687
// is unmounted the same way the runner does when interactive is false.
688688
operatorAvailable: false,
689+
// ...and wait_agents stays mounted the way the exec runner mounts it.
690+
waitAgentsMounted: true,
689691
});
690692
return {
691693
advertisedTools,

src/agent/agent-search.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function formatAgentSearchResults(
111111
"",
112112
...entries.flatMap((entry, i) => (i === 0 ? [entry] : ["", entry])),
113113
"",
114-
"Spawn with spawn_agent(description, prompt, agent=<id>). For a team, call spawn_agent once per member (parallel in one turn when independent), then reply and idle — mailbox mail arrives as inbound. Nested orchestrators still collect with wait_agents.",
114+
"Spawn with spawn_agent(description, prompt, agent=<id>). For a team, call spawn_agent once per member (parallel in one turn when independent), then reply and idle — mailbox mail arrives as inbound. wait_agents is mounted on exec-primary runs only.",
115115
].join("\n"),
116116
);
117117
}

src/agent/directors/greybeard/package.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ describe("greybeardPackage", () => {
104104
const allow = greybeardPackage.tools?.allow ?? [];
105105
expect(allow).not.toContain("task");
106106
expect(allow).toContain("spawn_agent");
107-
expect(allow).toContain("wait_agents");
107+
// CL-7678: nested orchestrators collect through mailbox mail; wait_agents
108+
// is exec-primary opt-in, so it stays off the Greybeard allow.
109+
expect(allow).not.toContain("wait_agents");
108110
// CL-7051: search_agents is Skywalker-only — nested directors spawn from allowlist.
109111
expect(allow).not.toContain("search_agents");
110112
expect(allow).toContain("write_file");

src/agent/directors/greybeard/package.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Judge the approach:
3333
4. Rank risks for long-term maintainability and backward compatibility.
3434
5. Report a clear verdict: hold / revise / block — with the why, not a checklist theater.
3535
36-
Spawn only when a concrete unknown blocks that judgment. Package spawn rules allow intern (mechanical shell), explorer (map/read), and critic (code evidence). When spawning critic, pass non-empty success_criteria (runtime fail-closes without it). intern and explorer remain optional. Prefer doing the review yourself with mounted read/search tools. Do not invent numeric spawn caps or act as a scheduler — width follows the unknown, not a soft ladder. Nested orchestrators collect with wait_agents — mailbox mail is the primary parent path.
36+
Spawn only when a concrete unknown blocks that judgment. Package spawn rules allow intern (mechanical shell), explorer (map/read), and critic (code evidence). When spawning critic, pass non-empty success_criteria (runtime fail-closes without it). intern and explorer remain optional. Prefer doing the review yourself with mounted read/search tools. Do not invent numeric spawn caps or act as a scheduler — width follows the unknown, not a soft ladder. Spawn then idle; reports arrive as mailbox mail — do not poll.
3737
3838
Blinders: do not call search_agents to discover the fleet (even when nested). You already know the limited spawn set; stay inside it. Do not spawn builder, counsel, skywalker, or other directors outside the allowlist.
3939

src/agent/directors/registry.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ describe("director registry", () => {
188188
expect(s.systemPrompt).toMatch(/No catch-all worker/i);
189189
expect(s.tools?.allow).not.toContain("task");
190190
expect(s.tools?.allow).toContain("spawn_agent");
191-
expect(s.tools?.allow).toContain("wait_agents");
191+
// CL-7678: wait_agents is exec-primary opt-in, off the Skywalker allow —
192+
// TUI primary collects through mailbox mail.
193+
expect(s.tools?.allow).not.toContain("wait_agents");
192194
expect(s.tools?.allow).toContain("write_file");
193195
expect(s.tools?.allow).toContain("edit_file");
194196
expect(s.tools?.allow).toContain("delete_file");

src/agent/directors/skywalker/package.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ describe("skywalkerPackage", () => {
5252
const allow = skywalkerPackage.tools?.allow ?? [];
5353
expect(allow).not.toContain("task");
5454
expect(allow).toContain("spawn_agent");
55-
expect(allow).toContain("wait_agents");
55+
// CL-7678: TUI primary collects through mailbox mail; wait_agents is
56+
// exec-primary opt-in, so it stays off the Skywalker allow.
57+
expect(allow).not.toContain("wait_agents");
5658
expect(allow).toContain("search_agents");
5759
expect(allow).toContain("write_file");
5860
expect(allow).toContain("edit_file");
@@ -123,7 +125,9 @@ describe("skywalkerPackage", () => {
123125
expect(p).toContain("wait_agents");
124126
expect(p).toContain("Idle-orchestrator");
125127
expect(p).not.toContain("task()");
126-
expect(p).toContain("do not poll wait_agents");
128+
expect(p).toContain("Spawn then idle; do not poll");
129+
expect(p).not.toContain("do not poll wait_agents");
130+
expect(p).toContain("wait_agents is mounted on exec-primary runs only");
127131
expect(p).toContain("mailbox mail arrives as inbound");
128132
expect(p).toContain(
129133
"When the fleet goes dry the runtime re-enters with collected reports",

src/agent/directors/skywalker/package.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import { SKYWALKER_TOOLS } from "../tool-sets.js";
66
const SKYWALKER_SYSTEM_PROMPT = `You are Skywalker — the primary orchestrator for Corbits Code.
77
88
When asked your name, answer: Skywalker.
9-
Agent id: skywalker (primary session; not a spawned worker). Prefer spawn_agent for specialists (parallel OK), then idle. Mailbox mail arrives as inbound when workers finish — do not poll wait_agents.
9+
Agent id: skywalker (primary session; not a spawned worker). Prefer spawn_agent for specialists (parallel OK), then idle. Mailbox mail arrives as inbound when workers finish — spawn then idle; do not poll.
1010
1111
PRIMARY INTENT: run the workflow. Classify every request. DIY tiny/single-file/one-route product edits. Delegate substantial work. Chain specialists into a sequence of actions. Track who is running. You are the only surface that talks to the operator — give frequent short status updates while work is in flight. Synthesize for the operator. Do not become the reviewer or explorer by default.
1212
13-
You do not do the specialists' jobs by default. For tiny bounded product edits, use write_file/edit_file/delete_file yourself. For substantial work you start specialists with spawn_agent, give the operator a short status, then idle so mailbox mail can wake you. Do not poll wait_agents.
13+
You do not do the specialists' jobs by default. For tiny bounded product edits, use write_file/edit_file/delete_file yourself. For substantial work you start specialists with spawn_agent, give the operator a short status, then idle so mailbox mail can wake you; do not poll.
1414
1515
# Parent tools
1616
1717
Do not run long-blocking jobs on the parent (evals, full test suites, long installs, long-running implementation). Dispatch intern (mechanical shell), tester (suite / repro), or builder (substantial code). Path tools (write_file/edit_file/delete_file) are the DIY surface; shell file-writes stay denied.
1818
19-
Idle-orchestrator: fire one or more spawn_agent calls in a turn — each returns immediately with an agent_id and does not hold the parent. Then **reply to the operator** with who is running and **end the turn**. Workers keep running while you are idle; mailbox mail arrives as inbound when a worker finishes or fails — read it and decide the next action. Do not poll wait_agents. wait_agents is optional/deprecated on this primary parent (nested orchestrators such as greybeard still collect with it). list_agents shows the fleet without blocking; do not poll list_agents. interrupt_agent unblocks an in-flight wait immediately. Enter mid-run delivers at the next parent tool.boundary — a long parent foreground run_shell or awaiting wait_agents holds those steers (start long commands with run_shell background:true instead). A bare spawn_agent does not. When the fleet goes dry the runtime re-enters with collected reports.
19+
Idle-orchestrator: fire one or more spawn_agent calls in a turn — each returns immediately with an agent_id and does not hold the parent. Then **reply to the operator** with who is running and **end the turn**. Workers keep running while you are idle; mailbox mail arrives as inbound when a worker finishes or fails — read it and decide the next action. Spawn then idle; do not poll. wait_agents is mounted on exec-primary runs only. list_agents shows the fleet without blocking; do not poll list_agents. interrupt_agent unblocks an in-flight wait immediately. Enter mid-run delivers at the next parent tool.boundary — a long parent foreground run_shell holds those steers (start long commands with run_shell background:true instead). A bare spawn_agent does not. When the fleet goes dry the runtime re-enters with collected reports.
2020
2121
# Operator updates (mandatory while fleet is live)
2222

0 commit comments

Comments
 (0)