From 6fbeed345e4400cf0b50722c94ce340806482487 Mon Sep 17 00:00:00 2001
From: Federico Jaramillo Martinez
- Tracked subsessions let an agent delegate work to child sessions, get notified when children stop
- working, and inspect their transcripts. Restart the session daemon after changing this setting.
+ Tracked subsessions let an agent delegate work to child sessions, receive a notification when each child
+ stops working, and inspect their status and transcripts. Calling spawn_subsession returns
+ immediately. The parent can continue independent work while treating every child whose result it needs
+ as pending. Before producing work that depends on those results, the parent reaches a join point and
+ yields until every required child has sent a completion notice.
+
+ A completion notice wakes an idle parent. If the parent is busy, the notice queues until the current
+ turn ends rather than interrupting in-flight work. For multiple required children, each notice resolves
+ one pending child; after processing it, the parent yields again if another required child is pending.
+ list_subsessions, check_subsession, and read_subsession provide
+ on-demand status and transcript inspection for deliberate progress checks or recovery. Completion
+ notifications, rather than polling these tools, are the normal synchronization mechanism.
In Settings → Session daemon, these keys are saved on the selected machine. Restart the
diff --git a/docs/config.md b/docs/config.md
index 61553c4..1d8dfee 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -171,7 +171,9 @@ The per-request size limit is still controlled by `maxUploadBytes` / `PI_WEB_MAX
`subsessions` is beta and controls whether agents receive the tracked-subsession tools: `spawn_subsession`, `list_subsessions`, `check_subsession`, and `read_subsession`. It defaults to `false` and also requires `spawnSessions` to be enabled.
-Tracked subsessions let an agent delegate work to child sessions, get notified when children stop working, and inspect their transcripts.
+Tracked subsessions let an agent delegate work to child sessions, receive a notification when each child stops working, and inspect their status and transcripts. Calling `spawn_subsession` returns immediately. The parent can continue independent work while treating every child whose result it needs as pending. Before producing work that depends on those results, the parent reaches a join point and yields until every required child has sent a completion notice.
+
+A completion notice wakes an idle parent. If the parent is busy, the notice queues until the current turn ends rather than interrupting in-flight work. For multiple required children, each notice resolves one pending child; after processing it, the parent yields again if another required child is pending. `list_subsessions`, `check_subsession`, and `read_subsession` provide on-demand status and transcript inspection for deliberate progress checks or recovery. Completion notifications, rather than polling these tools, are the normal synchronization mechanism.
In **Settings → Session daemon**, these keys are saved on the selected machine. Restart the session daemon on that machine after changing them.
diff --git a/src/server/sessions/spawnSubsessionTool.test.ts b/src/server/sessions/spawnSubsessionTool.test.ts
index c49999b..dd9546a 100644
--- a/src/server/sessions/spawnSubsessionTool.test.ts
+++ b/src/server/sessions/spawnSubsessionTool.test.ts
@@ -52,26 +52,25 @@ describe("createSubsessionToolDefinitions", () => {
expect(firstText(result.content)).toContain("Started tracked subsession child-1");
});
- it("describes tracked dispatch and notification without workflow policy", async () => {
+ it("guides the parent to join all required subsessions without polling", async () => {
const { spawn: spawnTool } = tools({
spawn: vi.fn(() => Promise.resolve({ sessionId: "child-1", cwd: "/repos/a-feature" })),
});
- expect(spawnTool.description).toBe("Start a tracked child session and send it an initial prompt. The call returns after dispatch; the parent is notified when the child stops working and can inspect its status, latest output, and transcript.");
+ expect(spawnTool.description).toBe("Start a tracked child and return after dispatch. Track required children as pending: continue independent work, then yield at a join point until all have notified completion. Notifications queue while the parent is busy; do not poll for completion.");
+ expect(spawnTool.promptSnippet).toBe("spawn_subsession: delegate parallel work; yield at a join point until all required children complete.");
const result = await spawnTool.execute("call-contract", { prompt: "do it" }, undefined, undefined, ctxFor("parent-1", undefined));
- const message = firstText(result.content);
- expect(message).toBe("Started tracked subsession child-1 in /repos/a-feature. The parent will be notified when it stops working.");
- expect(`${spawnTool.description}\n${message}`).not.toMatch(/do not poll|continue (?:useful|independent) work|end (?:this|the) turn|relay/i);
+ expect(firstText(result.content)).toBe("Started tracked subsession child-1 in /repos/a-feature. Track it as pending and, before finalizing dependent work, yield until all required children have notified completion.");
});
- it("keeps all subsession tool descriptions capability-oriented", () => {
+ it("keeps subsession inspection tool descriptions capability-oriented", () => {
const definitions = tools({});
expect(definitions.list.description).toBe("List tracked child sessions owned by the calling session, with each child's current status (working, idle, error, or unknown).");
expect(definitions.check.description).toBe("Return a tracked subsession's current status, message count, and most recent assistant output.");
expect(definitions.read.description).toBe("Return a filtered, paginated transcript of a tracked subsession. Filters select message roles and content kinds, search full message content, optionally include raw tool arguments, and cap or page the returned entries.");
- for (const definition of Object.values(definitions)) {
+ for (const definition of [definitions.list, definitions.check, definitions.read]) {
expect(definition.description).not.toMatch(/use this|do not poll|continue working|start narrow|for just the final|relay/i);
}
});
diff --git a/src/server/sessions/spawnSubsessionTool.ts b/src/server/sessions/spawnSubsessionTool.ts
index 1bb0cf1..b3c2a01 100644
--- a/src/server/sessions/spawnSubsessionTool.ts
+++ b/src/server/sessions/spawnSubsessionTool.ts
@@ -178,8 +178,8 @@ export function createSubsessionToolDefinitions(spawningCwd: string, deps: Subse
const spawnTool = defineTool