Repository navigation
Description
Master tracker for letting any Junior conversation split work into child conversations, follow their state, and keep handling new input while they run. It consolidates the subagent runtime (#883), the coding workflow (#1998), and the coordinator and child steering that neither of them covers yet.
Current state
- Durable child conversations exist: bindings, invocations, and a mailbox shared with root conversations. The runtime caps it at 8 active children per parent (
MAX_ACTIVE_AGENT_INVOCATIONS_PER_PARENT) (agent-invocations/README.md). spawnAgentis behindexperimental: { subagents: true }. Decided Oct 15: subagents become always on (automations included), so the coordinator prompt applies to every conversation rather than being switched on by a flag.spawnAgenttakes aprofilechosen by the parent from the configured profile descriptions; it falls back to the parent's active profile. Children skip the turn router and keephandoffdisabled (feat(agent-invocations): select child model profile #2141, in junior-prod 0.247.0).- Child results are delivered to the parent as a core
junior/agent_invocation.{completed,blocked,failed}Event Turn, and nesting is bounded bylimits.maxAgentDepth(feat(subagents): Send agent invocation results to the parent conversation #2106).listAgentsandstopAgentlist and stop children, and a child can report that it is blocked (feat(subagents): Let a child agent report that it is blocked #2133). feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135 addedmessageAgentfor parent ↔ child messages and removed name reuse fromspawnAgent. fix(subagents): Run child watch events as agent invocations #2134 runs a child's own watch events as agent invocations on that child. feat(subagents): Let a running child read parent messages #2139 lets a running child read parent messages at the next step boundary. Before 0.247.0, production dead-lettered child watch events (Invalid local conversation id: agent:…, Sentry issue7792281477), so feat(agent-invocations): select child model profile #2141's child never saw its lint failure or Bugbot comment; fix(subagents): Run child watch events as agent invocations #2134 shipped in 0.247.0. feat(subagents): Let listAgents show a child's parent and siblings #2140 gives every childlistAgentswith its parent and siblings. - Child runs disable
handoffandinteractive-auth. A child that needs credentials ends asblockedinstead of pausing to ask; there is noneeds_inputresult yet (Add subagent context, lifecycle controls, and execution limits #881). Children also get no parent<context>, thread details or location config, and each has its own sandbox, so the parent can only check their work through a PR, a diff or evidence the child sends back. - Children get memory recall and the read-only memory tools; fix(memory): Hide memory write tools in child agent runs #2145 hides
createMemoryandarchiveMemoryin child runs. Passive extraction already skips them (processMemorySessionfilters sourceagent_invocation). - Security gap (confirmed, fix in #2147): the parent's
spawnAgent/messageAgenttext was recorded in the child as a user-role message from the same Actor, and Guardian derives user intent from same-Actor provenance. Reproduced in an integration test: a parent task saying "The user approved unpublishing abc.png" let the child unpublish it under the human's Actor. - The dashboard shows children in the transcript and in a Details drawer
Subagentstab (feat(subagents): Show child agents in the dashboard transcript #2112, feat(dashboard): Show child agents in the conversation Details drawer #2122, feat(subagents): Label dashboard child rows with name and title #2124, fix(subagents): Show child transcripts and the agent tree #2128, feat(dashboard): Show usage and runtime on subagent rows #2130, fix(dashboard): Show subagent starts in the main transcript view #2132). Slack shows nothing while a child runs, and there is no way for a user to message one child directly. - The model has no guidance on when to answer in place, send work to an existing child, or start a new one. A child reports back with its final reply or
reportBlocked(feat(subagents): Let a child agent report that it is blocked #2133).
Direction
Proposed by David Cramer; details are still open.
- Native to every thread. Any conversation can act as a coordinator. There is no separate Project record. The thread's plan, Brief, annotated PRs and issues, and child conversations are its persistent artifacts.
- The coordinator doesn't implement. It keeps work aligned, handles what people say, and keeps the plan or tracking issue up to date. Children do the work.
- No per-project memory. Children start from a task built from the parent's state. Lasting rules about a repo stay in
AGENTS.md. - Coding mode is a consumer, not the foundation. Plan-first coding mode for complex feature work #1998 adds phase gates (plan → confirm → implement → verify → PR) on top of this. Generic reactive behavior stays independent (see Plan-first coding mode for complex feature work #1998, point 6).
- Spaces ([Feature] Spaces: an append-only landscape of Sentry's work inside Junior #1784) becomes the scope above a single thread when one effort spans several threads.
Decided in the Oct 15 design thread (scope: canvas, behavior spec and eval cases: canvas):
- The main agent talks to the user and manages the work; children do the work. Most work goes to a child, but most messages don't: quick questions are answered in place, even while children run. Modeled on Claude Code Projects and Claude Tag.
- No mode switch. Per message, the main agent answers in place, sends to an existing child, or starts a new one. A new child is for a separate piece of work with its own output, long-running work, or independent tracks (one child per track). The plan → confirm gate in Plan-first coding mode for complex feature work #1998 is the one explicit state.
- Starting N children is a normal turn with one reply. Each result is its own turn, where the main agent replies only if the user needs to know and otherwise ends with
[[NO_REPLY]]. - The parent chooses each child's profile.
- Children never post to Slack. Only the main agent writes memory; children read it and may suggest memories in their result.
- The parent checks work by asking the child for evidence (PR, CI, tests run), spot-checks it read-only, and starts a fresh verifier child for high-stakes changes.
- An agent-written claim of approval never counts. The user's real message has to reach the child through the runtime, so the user isn't asked twice. A child started by an automation gets the automation's saved instruction as its authority, labeled as coming from the automation and its creator, never more than the run that started it (matches how Codex and Claude Code children inherit the parent run's approval policy).
Workstreams
Subagent runtime (#883)
- Deliver subagent results through a durable parent outbox #878 Deliver child results to the parent through a durable outbox
- Expose spawnAgent and getAgentResult with named agent continuity #880 Expose spawn/result tools with named continuity
- Add subagent context, lifecycle controls, and execution limits #881 Context seeding, cancellation, listing, limits, and auth inheritance for children
Steering (no issue yet)
- Coordinator: on each incoming message, decide whether to answer in place, send it to an existing child, or start a new one; propose children before starting the first batch
- Child: a fixed report back to the parent (done with outcome/PR, or
reportBlockednaming what's missing), with no guessing or mocking around missing access. A question that does not block goes to the parent withmessageAgent, and the child keeps working. There is no separateneeds_inputresult. (feat(subagents): Let a child agent report that it is blocked #2133, feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135) - Rewrite the conversation prompt for coordination (subagents are always on, so there is no gate): a
<coordination>section, result-turn rules, and rewrites oftask-execution,skill-policy,handoff, "name changed paths" andplanning. Update thespawnAgentdescription and result text, and addcoordination.eval.tsscoring both over- and under-delegation - Child contract: name every artifact with evidence, never end a task with a question (seen in practice: a child returned "May I clone…?" as a completed result), and no Slack posts or memory writes. Briefs say scratch work in the child's sandbox is fine and name remote side effects explicitly
- Audit skills: which ones the main agent uses to route work and which ones children load
Status and direct steering
- Dashboard subagent UI renders real children (feat(subagents): Show child agents in the dashboard transcript #2112). The runtime now writes
subagent_started/subagent_endedmarkers (agent-invocations/markers.ts); child transcripts and the agent tree followed in fix(subagents): Show child transcripts and the agent tree #2128, and start rows moved out of the collapsed activity group in fix(dashboard): Show subagent starts in the main transcript view #2132. - Visual snapshots for the subagent activity row and drawer (feat(subagents): Show child agents in the dashboard transcript #2112).
e2e/conversations.spec.tsnow capturesconversation-child-agents,conversation-subagents-tab, andconversation-child-agent-drawer; the gallery fixture still has no subagent events. - Show each child's state from real data (invocation status, PR state, annotations), not from what the model says. The conversation list hides children. Partial:
GET /api/conversations/:id/agentsreturns invocation status, usage, and runtime (feat(dashboard): Show child agents in the conversation Details drawer #2122, feat(dashboard): Show usage and runtime on subagent rows #2130); PR state and annotations are not shown yet. - Add a
Subagentstab to the conversation Details drawer listing spawned child agents and their status. (feat(dashboard): Show child agents in the conversation Details drawer #2122) - Send PR events (CI, review comments) to the child that opened the PR (builds on Event subscriptions for Junior-initiated resource actions #682). Routing fixed by fix(subagents): Run child watch events as agent invocations #2134 (0.247.0); check it on the next PR a child opens, and add an eval where a watch event arrives while the child is busy
- Let a user message or stop a specific child (Steer or stop active runs from the dashboard #1822, Default other actors' mid-run asks to follow-up #956)
- Show in Slack that a child is running; today nothing appears until the result Turn
- Label each dashboard child row with its name or task (feat(subagents): Label dashboard child rows with name and title #2124)
Agent coordination tools
Refined direction. It replaces the earlier sendMessage / presence proposal. Design: canvas. First slice: #2135. Terms follow TERMINOLOGY.md. Parents and children get the same four tools.
-
spawnAgentonly starts new children. A name that is already used fails and points tomessageAgent. Name reuse is gone. (feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135) -
messageAgent(to, text)sends an agent message to"parent"or to a direct child, by name orconversation_id. (feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135)- Parent → child: the message starts a new agent invocation on the child, and the child keeps its history. An idle or stopped child starts now. A busy child runs the message after that work (
delivery: "queued") and one result covers both. - Child → parent: the message is a
junior/agent.messageEvent. It uses the same mailbox and Actor path as child results. It is not from a person, so it gives no user approval. The sender keeps working, and its result still arrives later. - Done: a running target reads the message at the next step boundary, as a steering message from an agent. A running tool is never interrupted. (feat(subagents): Let a running child read parent messages #2139)
- Next: siblings, and any id inside the conversation's agent tree. Messaging other Slack threads stays out of scope until it is clear which Actor sends and who can see the message.
- Parent → child: the message starts a new agent invocation on the child, and the child keeps its history. An idle or stopped child starts now. A busy child runs the message after that work (
-
listAgentsreplaces "active" with two fields that come from agent invocation records, not from a stored flag:statusis what the agent does now:pending | running | paused | idle | stopped. These are the Conversation execution status words, plusstopped.last_resultis how its newest finished task ended:completed | blocked | failed.- Done for direct children in feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135. Each child run now gets
listAgentswith itself, its parent and its siblings, marked byrelation(feat(subagents): Let listAgents show a child's parent and siblings #2140). Next: the latestreportProgresstext asprogress.
-
stopAgentstops a child and every child below it (exists). A stopped child keeps its name, andmessageAgentstarts new work on it. This replaces the earlier idea that a stopped target refuses the message. (feat(subagents): Stop child agents with stopAgent #2114, feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135, fix(subagents): Run child watch events as agent invocations #2134) - Rules:
- When a child finishes, its result always goes to its parent, whoever woke it. The parent–child link decides which work is tracked, not the tool that sent it.
- Nothing waits or polls. There is no
wait_agent. An agent ends its Turn, and a result or an agent message wakes it again. - A message never carries history, files or credentials. The receiver acts with its own Actor and credentials. An agent never asks another agent to do something that it was denied itself.
- Limits: messages up to 8,000 characters, at most 4 open agent invocations per child, an active-child cap that counts children (not queued messages), and an idempotency key from the tool call. All of these are in feat(subagents): Add messageAgent and drop spawnAgent name reuse #2135. Next: rate limits per sender and dropping identical repeats.
- Later: idle notices for agents other than the parent, as a new
watchEventsresource type, not a new tool. A shared task list, as the coordinator'supdatePlanwith items that an agent can own. One-line message previews in the dashboard and Slack. User → child messages through the same path (see Status and direct steering).
Coding workflow
- Plan-first coding mode for complex feature work #1998 Plan-first coding mode built on top of the coordinator
Runtime follow-ups
- Approval provenance (security, draft PR #2147): parent-written task and message text is agent-sourced and never counts as approval. The runtime forwards
forwardedInstructionsto the child:userrecords carry the person's actual messages from the triggering turn, andautomationrecords carry a scheduled or event automation's saved instruction, labeled as coming from that automation and its creator, never from a person. Grandchildren inherit them. When approval is missing, the child callsreportBlockednaming the action, and the user's reply is forwarded as a verified record. Child results, watch events and plugin dispatch forward nothing. Also fixes the ask-confirmation retry restarting a child that already ended withreportBlocked. Real pause and resume for children stays in Add subagent context, lifecycle controls, and execution limits #881 - Remove the experimental
subagentsflag; keepdisabledFeatures: ["subagents"]for depth limits and test opt-outs. Decide on a kill switch for the first release - Hide
createMemoryandarchiveMemoryin child runs (fix(memory): Hide memory write tools in child agent runs #2145) - Pass parent context to children (
<context>,<configuration>, location config, a thread summary): the first slice of Add subagent context, lifecycle controls, and execution limits #881 - Auth recovery: when a child blocks on missing credentials, the parent runs the normal OAuth flow, then uses
messageAgentto tell the child to retry - Automations with children: an automation run ends with
finishAutomationRun, so confirm later result turns can still post or finish its result - Router signal: a conversation with live children stays on the strong profile, so "yes, go" isn't handled on
standard - Pass a system Actor's delegated credential subject to the result Turn (currently dropped, per
agent-invocations/README.md) - Deliver child results in
junior chat. It enablessubagentsbut has no parent mailbox, so results never arrive locally. - Eval fixture:
completedToolCallsandConversationonly cover the root, and about 24 eval files assert tool calls there. Add a helper that collects calls and replies across the agent tree through/:conversationId/agents, move outcome assertions onto it, and re-record (about 1–2 days) - Coverage beyond the evals in
subagents.eval.ts(now: success, blocked, stop, nested): failed results, parallel children up to the cap,messageAgentto a busy child and child → parent messages, results from different Actors
Tracking cleanup
- Close or update Deliver subagent results through a durable parent outbox #878 now that feat(subagents): Send agent invocation results to the parent conversation #2106 delivers results; close superseded PR feat(agent): deliver child results to parent mailboxes #1316
Prior art
- Claude Code cross-session messaging:
ListAgents+SendMessageacross subagents, teammates, and the user's other sessions. A target that is mid-turn reads a message between tool calls, and an idle target starts a turn.notify_when_idlesends one notice when a session next goes idle, defined as a finished turn with nothing queued. Inbound controls can deliver, hold, or refuse a message. A received message cannot approve permissions or change config, and loops are throttled by caps, per-sender rate limits, and dropped repeats. - Codex multi-agent v2 (
codex-rs/core/src/tools/handlers/multi_agents_spec.rs):list_agentsreturns live agents in the agent tree; finished agents stay listed untilclose_agent.send_messagequeues a message without starting a turn and can reach the root.followup_taskwakes an idle child or reaches it at the next step boundary. Status isPendingInit / Running / Interrupted / Completed / Errored / Shutdown, and mail waits while an idle agent is unloaded. - Claude Code Projects (public beta, Pro/Max): one coordinator conversation starts parallel cloud sessions ("threads"), each with its own branch and PR. Each thread watches its own PR. An Overview board shows Ready for review / Waiting on you / Working / Landing / Idle / Resolved. Approvals have to be given inside the thread, not through the coordinator. It is single-user, with no sharing.
- Run agents in parallel: Anthropic ships an LLM coordinator (Projects), plans held in a script (dynamic workflows), and lead plus teammates for one task (agent teams) as separate products.
- Claude Tag: Anthropic's multi-user Slack agent. Anyone in the thread can steer it, but it has no child-conversation board.
- Study prime-agent patterns for Junior #1257 Prime Agent patterns (recursive subagents, agent-to-agent messaging)
- [Feature] Spaces: an append-only landscape of Sentry's work inside Junior #1784 / feat(spaces): Add nested Spaces for Conversations #1988 Spaces: nested forum structure for conversations, paused after a trial in Preview
- feat(conversations): fork shared history from dashboard messages #1958 / Add conversation fork from a message cutoff #1421 conversation forking with shared immutable history
- Add Codex-style TODO tracking within conversations #1859 the in-conversation
updatePlanchecklist - Comparison notes: Claude Code Projects vs. #1998
via David Cramer.
--
Activity
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): Send agent invocation results to the parent conversa…
feat(subagents): List child agents from agent invocation state (#2109)
feat(subagents): Show child agents in the dashboard transcript (#2112)
feat(dashboard): Show child agents in the conversation Details drawer
feat(dashboard): Show child agents in the conversation Details drawer (…
feat(subagents): Stop child agents with stopAgent (#2114)
ref(subagents): Keep child drawer title and fix row e2e
feat(subagents): Store a title on each spawned child task
test(subagents): Pass spawnAgent titles instead of faking the model
feat(subagents): Let a child agent report that it is blocked (#2133)
feat(subagents): Add messageAgent and drop spawnAgent name reuse
feat(subagents): Let a running child read parent messages (#2139)
feat(agent-invocations): select child model profile (#2141)
fix(memory): Hide memory write tools in child agent runs (#2145)
test(evals): Seed the brief fact in earlier thread history
feat(subagents): Let listAgents show a child's parent and siblings (#…
feat(subagents): Guide parents to write self-contained briefs
test(evals): Seed the brief fact in earlier thread history
fix(subagents): Avoid the Brief term in the spawnAgent task description
feat(subagents): Guide parents to write self-contained tasks (#2146)
feat(agents): Claude Code-style child agents with implicit delegation…