Skip to content

Launch agents outside the lineage, and group children in the sidebar - #973

Merged
selfcontained merged 3 commits into
mainfrom
agt_f1a16daccacb/build-non-child-launches-subagent-ui
Aug 18, 2026
Merged

Launch agents outside the lineage, and group children in the sidebar#973
selfcontained merged 3 commits into
mainfrom
agt_f1a16daccacb/build-non-child-launches-subagent-ui

Conversation

@selfcontained

@selfcontainedselfcontained commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Builds on the lineage/provenance work (#972 era): that shipped parentAgentId/relation on list_agents; this is the launch option and UI layer on top of it.

dispatch_launch_agent gains child

Default true — today's behaviour, unchanged. 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 rather than nesting under whoever launched it. It still inherits the launcher's cwd, type, and access level (fullAccess stays capped by the launcher's, so a non-child launch can't escalate).

The launcher is still recorded, in a new launched_by_agent_id column (migration 0041, backfilled from parent_agent_id). parent_agent_id answers "whose card does this render in" — exactly the question child: false says 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_agent accepts a target the caller launched, not only one it parented.
  • The parent↔child bypass in messaging visibility extends to launcher↔launched, so the two can still coordinate across repo roots.
  • list_agents reports launchedByAgentId/launchedByNameonly when the launcher is not already the parent — for a child it would just repeat parentAgentId in 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 by dispatch_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_agent with child: true, and a refusal from dispatch_launch_persona. It can still launch independent agents with child: 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:

  • Pre-cap deep trees already exist. The card lookup walks to the root rather than stopping at the direct parent, so any depth flattens into one list instead of losing the agents in the middle.
  • Orphans. Only review children cascade on archive, so a plain child routinely outlives its parent. A child whose parent is not in the list is promoted to its own card.

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>), confirming dispatch_launch_agent carried the new child param in tools/list before calling it:

result
default child launchparent_agent_id and launched_by_agent_id both set to the launcher
child: falseparent_agent_id null, launched_by_agent_id set; list_agentsrelation: unrelated, launchedByName present
child → child: truerefused, "cannot launch further children"
child → dispatch_launch_personarefused, "cannot launch persona reviews"
child → child: falselaunched
launcher archives its child: false agentaccepted (no parent link to authorize it)

Before/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 --live with real Claude CLI agents, which is the only way to exercise the parts inert cannot reach:

  • A real child agent's terminal carries the injected depth-cap notice ("You are a child agent: you cannot launch child agents or persona reviews of your own"); a real child: false agent'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.
  • From the live child: child: true and dispatch_launch_persona both refused. From the live child: false agent: 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.
  • Sub agent row controls against live processes: attach connects to a real terminal; detach disconnects while leaving the agent running and 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

selfcontainedand others added 3 commits August 17, 2026 20:21
`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>
@selfcontained
selfcontained merged commit ab79797 into mainAug 18, 2026
1 check passed
@selfcontained
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>
Sign up for freeto 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

@selfcontained