feat: derive sub-agent state and surface delegated work - #60
Merged
Merged
Conversation
OpenCode delegates to child sessions but exposes no durable background-job API, so sub-agent state has to be derived. server/opencode/subagents.ts reconciles four upstream facts — the child list, the parent's task parts, the process-local status map, and each child's own transcript — into one ledger keyed by child session id, never by task-part id. Precedence is fixed and each row reports the evidence behind it, because the sources differ sharply in strength. A background task part reporting "completed" only means the launch call returned, so it is never read as a finished child; a synchronous one blocked on the child and is. When nothing settles a child — cancelled, or the agent server restarted mid-run — the row is `unknown` and names what was checked rather than guessing. Surfaces: - Hub lists roots depth-first with their delegated work, a `sub` pill and a direct child count. Nested delegation and orphaned children are both preserved; nothing is dropped or duplicated. - A child transcript badges itself and links back to the parent that delegated it. - Task rows link to the child that ran them, and stay out of collapsed action groups so that route is never hidden. - A background hand-back — injected as a user-role message — renders as a status row instead of a prompt the human never typed. - An Agents inspector tab lists every child with state, evidence and cost. Controls only promise what the connected process can deliver: Stop appears solely for children reported busy, background promotion is gated on /experimental/capabilities, and child endpoints verify the parent link before acting so they cannot be used as a general-purpose abort. Cost is bounded: one child-list and status call, the newest parent page for intent, and child transcripts probed only for children that are neither running nor already settled — newest first, capped, concurrency-limited, with truncation reported rather than implied.
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 free
to 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.
Closes #46. Implements the state model and orchestration surface in #51.
Why
OpenCode has child sessions but no durable background-job API, so "what are this session's sub-agents doing?" cannot be read — it has to be derived. Until now the runner had the primitives (
parentID,subagent_depth) and used none of them: children were invisible as children, a delegation was an opaquetaskchip, and a background hand-back rendered as a chat bubble the human never typed. In one audited project 124 of 149 sessions were children, so the flat list was ~83% noise.The state model
server/opencode/subagents.tsreconciles four upstream facts into one ledger keyed by child session id — never by task-part id, since resume emits several parts per child:/session/statusreports busyPrecedence follows that order. A background task part flips to
completedwhen the launch call returns, typically long before the child finishes, so it is never terminal evidence. A synchronous one blocked on the child and is.When nothing settles a child — cancelled, or the agent server restarted mid-run — the row is
unknownand says what was checked. The audit found three cancelled children with no parent notification at all; rendering those as "completed" would be a confident falsehood, so the UI admits it instead. Every row shows its provenance.Surfaces
subpill, and a direct child count. Nested delegation and children whose parent is archived or elsewhere are both preserved — the flatten emits every session exactly once, including through a corrupt parent cycle.?panel=subagents; on mobile this opens the full-screen inspector sheet.Controls promise only what the connected process can deliver
Stop appears solely for children
/session/statusreports busy, because abort authority is process-local. Background promotion is gated on/experimental/capabilities, not on an env var the BFF cannot read. Child endpoints verify the parent link first.Configuration and settings
subagent_depthto 3. OpenCode defaults to 1, which prevents nested delegation.opencode.json.Default (on/off)instead of incorrectly rendering an unset value as off.Cost
One child-list and one status call, the newest parent page for intent, and child transcripts probed only for children that are neither running nor already settled — newest first, capped at 12, concurrency-limited, with
truncatedreported rather than silently implied.Notes for review
SessionSummary.childCountis grouped from the directory listing the Hub already fetches, so it costs no extra upstream call.Verification
npm run typechecknpm test(356 passed)npm run buildnpm run test:e2e(192 passed, 1 expected screenshot-runner skip)npm run screenshots:local(8 routes; desktop + mobile)