Skip to content

feat(session): real fan-out — parallel subtasks, background subagents on by default, per-session cap - #145

Merged
filipeforattini merged 1 commit into
mainfrom
feat/goal-fanout
Sep 6, 2026
Merged

feat(session): real fan-out — parallel subtasks, background subagents on by default, per-session cap#145
filipeforattini merged 1 commit into
mainfrom
feat/goal-fanout

Conversation

@filipeforattini

@filipeforattini filipeforattini commented Sep 5, 2026

Copy link
Copy Markdown

Increment 3 of the /goal plan, stacked on #144.

What the harness allowed before

  • One subtask per message, and silently. The loop popped one SubtaskPart per iteration; the assistant message it wrote became the turn's last finished message, and MessageV2.latest() dropped every part before it — so a message with three subtasks ran the last one and lost the other two.
  • Background subagents behind a flag. task(background: true) failed unless REDCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true.
  • No cap. A model could launch as many background subagents as it liked.
  • Cancellation already cascaded: SessionRunState.cancel walks parentSessionId and cancels owned background jobs.

What it allows now

  • Every pending subtask on a message runs, together, experimental.subtask_concurrency at a time (default 4). Each keeps its own assistant message and tool part; they are created in submission order so the transcript reads in that order whatever the finish order. One "summarize the output above" nudge per batch.
  • Background subagents on by default; REDCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false turns them off. The TUI/app capability flag follows the server, nothing to change there.
  • Per-session cap experimental.background_subagents_max (default 4). Past it the task tool fails with "N already running (limit M). Wait for one to report, or run this task in the foreground." Foreground tasks are not capped.
  • With feat(task): subagents inherit the goal; the loop waits for them #144: a background subagent under a goal parks the loop on WAIT; its report re-enters the parent and the judge runs again. /goal-drop and Ctrl+C cancel the fan-out through the existing cascade (covered by the existing cancelling the parent run cancels running background tasks and the recursive-descendants tests).

Still missing

  • A director/worker mode (oh-my-pi's vibe): one agent that only dispatches and reviews.
  • Cross-agent messaging (hermes' hub): children cannot talk to each other or steer a sibling.
  • A per-child turn/token budget; children inherit the parent's step ceiling only.

Tests

  • prompt.test.ts: three subtasks on one message are all running at once (three running jobs under the parent), finish in reverse order, and their outputs land as alpha, beta, gamma; the turn makes exactly four provider calls.
  • task.test.ts: the cap refuses the second background subagent with the limit in the message, and a foreground task still runs; the flag-off refusal moved to a layer that turns the flag off.
  • registry.test.ts: the background parameter is exposed by default and hidden when the flag is off.
  • runtime-flags.test.ts: on by default, off with the env set to false.
  • Suites: prompt + compaction + tool 477 pass under --timeout 30000 (what CI runs); redcode and core typecheck clean.

https://claude.ai/code/session_014XwJPDhq1ahcm3rd454WzQ


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@filipeforattini
filipeforattini force-pushed the feat/goal-subagents branch 2 times, most recently from 2682b85 to b203e9b Compare September 5, 2026 23:58
@filipeforattini
filipeforattini force-pushed the feat/goal-fanout branch 2 times, most recently from 2a6819c to 5c9fbe5 Compare September 6, 2026 00:38
@filipeforattini
filipeforattini force-pushed the feat/goal-subagents branch 2 times, most recently from 80691c7 to e0e42b0 Compare September 6, 2026 01:08
@filipeforattini
filipeforattini changed the base branch from feat/goal-subagents to main September 6, 2026 01:24
…bagents on by default, capped

A user message with several subtask parts ran exactly one of them. handleSubtask writes an
assistant message with finish "tool-calls"; on the next iteration that message is the turn's
last finished one and MessageV2.latest() drops every part before it — the other subtasks
vanished without a trace. The loop now drains the whole pending batch with Effect.forEach at
experimental.subtask_concurrency (default 4). Each subtask still gets its own assistant message
and tool part, created in submission order, so the transcript keeps that order however the
children finish; the "summarize the output above" nudge is written once for the batch.

Background subagents come out from behind REDCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS: the goal
loop parks on WAIT while one runs and the task tool now caps them, so the guard the experiment
stood behind is built in. The cap is per session — experimental.background_subagents_max,
default 4 — and past it the tool fails with "wait for one to report, or run this task in the
foreground", which is the answer the model needs rather than a silent queue. Foreground tasks
are not capped: they are the caller's own turn waiting.

Claude-Session: https://claude.ai/code/session_014XwJPDhq1ahcm3rd454WzQ
@filipeforattini
filipeforattini merged commit a4e53f8 into main Sep 6, 2026
12 checks passed
@filipeforattini
filipeforattini deleted the feat/goal-fanout branch September 6, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant