Launch agents outside the lineage, and group children in the sidebar - #973
Merged
selfcontained merged 3 commits intoAug 18, 2026
Merged
Conversation
`dispatch_launch_agent` gains `child` (default true, today's behaviour). `child: false` launches an agent that is not part of the launcher's lineage: `parent_agent_id` stays null, so it owns a top-level sidebar card instead of nesting under whoever launched it. The launcher is still recorded, in a new `launched_by_agent_id` column. `parent_agent_id` answers "whose card does this render in", which is exactly the question `child: false` says no to — but the launcher still created the session, and without recording it an independent launch would have no provenance and no owner allowed to archive it. dispatch_archive_agent and the messaging visibility bypass both consult it now, and list_agents reports launchedByAgentId/launchedByName only when the launcher is not already the parent. The sidebar's top level was never actually parent-scoped: it filtered out review children only, so a plain child appeared both as its own card and nowhere in its parent. Top level is now "agents with no parent" and every child renders in its parent's Sub Agents section. The REVIEW badge still keys off role='review'; a plain child gets no badge, which is what distinguishes it. That move would have stripped a child of the lifecycle controls an agent card carries, so the sub agent row gains pause, session settings, and archive behind an overflow menu. Persona launch is deliberately not there — a child cannot launch reviews. Depth is capped at one level of children: a sub agent row cannot host rows of its own, so a grandchild would simply stop being visible. A child agent can no longer launch children or persona reviews; it can still launch independent agents with `child: false`. Trees deeper than that already exist, so the sidebar flattens any depth into the root card's list rather than losing the agents in the middle. An agent whose parent has been archived is promoted to its own card for the same reason — only review children cascade on archive, so a plain child routinely outlives its parent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four reviewers; the findings that changed code: The migration's partial index had no reader — every lookup of the new column goes through the primary key and compares in application code, so it was pure startup cost holding a table lock behind the backfill. Dropped. The backfill itself stays unbatched: measured at 105ms over 150k child rows in a 200k-row table, and 0036_pin-ids.sql already ships a heavier unqualified UPDATE on this table. list_agents claimed parentAgentId/parentName "name the agent that launched it", which `child: false` makes false. It now describes two separate things: lineage (whose child this is, and relation on that same parent tree) and provenance (who created the session), and says outright that a `child: false` agent reports as unrelated to the agent that launched it. The lineage-to-card helpers moved out of agent-types.ts, which is otherwise type labels and validators, into lib/agent-lineage.ts. They encode the sidebar's projection — deep-tree degradation and orphan rescue included — not a generic type helper. Cycle handling was a real bug rather than a hardening nicety: detecting the cycle stopped the walk but left each member resolving to a different member, so none owned a card and both disappeared from the sidebar entirely. The walk now elects the lowest id among the cycle's own members, so every member agrees on one representative that renders the rest. Agents that merely walk into a cycle elect the same one. Cycling with a sub agent selected fell through to index -1 and jumped to the first card. It now anchors on the card the sub agent lives in and steps from there. The existing test asserted the old behaviour for review agents specifically; that was codifying the accident, so it now asserts the same anchoring. The overflow trigger was 28px wide against a 44px height on mobile. Matched to its neighbours at w-11 sm:w-7. E2E covered the old grouping — a plain child was asserted to render as its own top-level card. Updated to the new grouping, plus a case exercising the sub agent row's resume/settings/archive controls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
X is the app's close/dismiss glyph, so on a row that also offers Archive it reads as "delete this agent" rather than "stop watching its terminal". Unplug says disconnect and cannot be confused with either. The overflow menu's rows were also still 34px on mobile while the row buttons around them are 44px — raised to match on small screens only, so desktop density is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
selfcontained
deleted the
agt_f1a16daccacb/build-non-child-launches-subagent-ui
branch
August 18, 2026 03:14
selfcontained added a commit
that referenced
this pull request
Aug 18, 2026
#973 moved plain children out of the sidebar's top level and into their parent card's Sub Agents list, added `child: false` for launching outside the lineage, and capped nesting at one level. It shipped with no doc changes, so four surfaces still said a launched agent "appears as a top-level entry in the sidebar". Rewrites the Agents and Repo Tools orchestration sections around what actually renders now: the Sub Agents list, the row's session controls behind an overflow menu, the depth cap, orphan promotion when a parent is archived, and `child: false`. Corrects the agent-cycling shortcut (it skips every sub agent, not only review children), folds lineage and provenance into the list_agents bullet, and covers self-archive in the README row. The subagents skill gains the same contract in agent-facing terms — the depth cap in particular, since it fails at the point of use halfway through work the agent has already planned around. Adds a "Sub Agents" ambient tip (since 0.35.0, the release ab79797 landed in) pointing at the new agents#sub-agents anchor. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on the lineage/provenance work (#972 era): that shipped
parentAgentId/relationonlist_agents; this is the launch option and UI layer on top of it.dispatch_launch_agentgainschildDefault
true— today's behaviour, unchanged.child: falselaunches an agent that is not part of the launcher's lineage:parent_agent_idstays null, so it owns a top-level sidebar card rather than nesting under whoever launched it. It still inherits the launcher's cwd, type, and access level (fullAccessstays capped by the launcher's, so a non-child launch can't escalate).The launcher is still recorded, in a new
launched_by_agent_idcolumn (migration0041, backfilled fromparent_agent_id).parent_agent_idanswers "whose card does this render in" — exactly the questionchild: falsesays no to — but the launcher created the session, and without recording it an independent launch would have no provenance and no owner allowed to archive it. So:dispatch_archive_agentaccepts a target the caller launched, not only one it parented.list_agentsreportslaunchedByAgentId/launchedByNameonly when the launcher is not already the parent — for a child it would just repeatparentAgentIdin every row.Sidebar is now actually parent-scoped
The top-level list was never "agents with no parent": it filtered out review children only (
isNestedReviewAgent), so a plain child appeared as its own card and nowhere in its parent's Sub Agents section. Now top level is agents with no parent, and every child renders as a sub agent row.Badges are unchanged by design: the REVIEW badge still keys off
role = 'review'(set only bydispatch_launch_persona), and a plain child gets no badge — the absence is the signal.Sub agent row action gaps
Moving plain children into that section would have stripped them of controls the agent card footer carries. Audited the footer (persona launcher, resume/pause, session settings, archive) against the row (attach/detach, resume, open review); the row gains Pause, Session settings, and Archive behind an overflow menu. Persona launch is deliberately not added — a child cannot launch reviews.
Depth capped at one level of children
A sub agent row cannot host rows of its own, so a grandchild would simply stop being visible. Enforced server-side in both launch paths: a child agent gets a 409 from
dispatch_launch_agentwithchild: true, and a refusal fromdispatch_launch_persona. It can still launch independent agents withchild: false, which surface as their own top-level cards. Children are told this in their initial prompt rather than discovering it at the point of use.Two cases the cap doesn't cover, both handled in the sidebar:
This does walk back part of the reasoning behind the lineage work (which chose to surface deep chains rather than disallow them) — that's an intentional tradeoff, confirmed before building.
Verification
Against an isolated dev stack, through the real MCP endpoint (
POST /api/mcp/<agentId>), confirmingdispatch_launch_agentcarried the newchildparam intools/listbefore calling it:parent_agent_idandlaunched_by_agent_idboth set to the launcherchild: falseparent_agent_idnull,launched_by_agent_idset;list_agents→relation: unrelated,launchedByNamepresentchild: truedispatch_launch_personachild: falsechild: falseagentBefore/after on the same live endpoint: with the source stashed and the API reloaded, the identical child-launching-a-grandchild call succeeded and wrote
parent_agent_id = <the child>; unstashed, it is refused.UI driven in Playwright against the same stack at 1500×1000, 390×844 and 320×700: sidebar regrouping, the overflow menu in both running and stopped states, and archive/pause/session-settings each driven through to a confirmed server-side state change (
status = archiving,status = stopped, dialog bound to the sub agent rather than the parent). Archiving a sub agent live-promoted its surviving grandchild to a top-level card without a reload. On a review-ready row — which renders a full-bleed overlay button — the overflow trigger opens without firing the overlay, and the overlay still fires from the rest of the row.Live agents
The above ran on the dev stack's default inert runtime, which spawns no tmux sessions or CLI processes. Repeated on
dispatch-dev up --livewith real Claude CLI agents, which is the only way to exercise the parts inert cannot reach:child: falseagent's carries "as an independent agent — you are not its child" and no notice. Read off the live panes, not asserted on the handler argument.child: trueanddispatch_launch_personaboth refused. From the livechild: falseagent:dispatch_launch_personasucceeded, and its reviewer appears as that agent's own sub agent row with the REVIEW badge — the orchestrator → independent builder → reviewers chain this feature exists to enable.runningand its tmux session intact; Pause stops the CLI and removes the session; Resume recreates it; Archive tears it down and removes the row.All four live agents were archived and their sessions confirmed gone afterwards.
pnpm run check,finalize:web, 951 web tests, 2797 server tests and the 181-test e2e suite pass.🤖 Generated with Claude Code