[FEATURE] Let a parent session observe its spawned children — return a session id from spawn_task and/or expose spawnedBy lineage in list_sessions
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Priority: Medium
Feature Category: Sessions / subagent orchestration
Problem Statement
After mcp__ccd_session__spawn_task creates a child task/session, the parent session has no reliable way to observe whether the child actually launched or is running:
spawn_taskreturns atask_id, not a session id.list_sessionshas nospawnedBy/ lineage field, and there is notask_id→ session mapping.
So the only way for a parent to locate its child is fragile string-matching of the child's title against list_sessions, and there is no path for the harness to notify the parent that the child launched. When the chip title is generic, the parent can match the wrong session; when it can't match at all, the parent has no signal.
In practice this fragility leads a parent to assert child status without a reliable check (e.g. "the child hasn't started yet" / "it's still running") when it has no actual way to know — a confabulation hazard that better primitives would remove.
Proposed Solution
Either (or both) of:
- Have
spawn_taskreturn the spawned session's id (once the chip is started), so the parent holds a stable handle to its child. - Add a
spawnedBy(parent session id) field tolist_sessionsentries, so a parent can filterlist_sessionsto exactly its own children without title-matching.
Either eliminates the title-matching fragility and makes child launch/run state reliably discoverable; (1) additionally gives the parent a direct handle for any follow-up.
Additional Context
The Agent tool returns a subagent's result directly to the caller, whereas spawn_task (independent sessions) intentionally decouples — the right design for independence, but it currently leaves the parent with no lineage handle whatsoever. A returned session id or a spawnedBy field closes the observability gap without re-coupling the sessions.
5 Comments
This is a real gap, and the two primitives you propose —
spawn_taskreturning the spawned session id, and aspawnedBylineage field onlist_sessions— are the right native fix. Title-matching is genuinely fragile, and as you note it pushes the parent into asserting child status it has no way to actually verify.One thing that helps today, while the native primitives are pending: the confabulation hazard you describe (parent claiming "child started" / "still running" with no reliable check) is exactly the failure where the parent's narrative diverges from the actual dispatch state. You can shrink that gap operator-side, without title-matching, with a receipt-and-verify pattern:
PostToolUsehook matched to your spawn tool (heremcp__ccd_session__spawn_task) writes a receipt the moment a child is actually dispatched — thetask_id, a timestamp, and a hash of the spawn input. That gives you a durable, machine-checkable record of "what was actually spawned," which is more thantask_idalone gives the parent.Stophook then compares the parent's completion narrative ("dispatched N children", "child X returned …") against the receipt set, and surfaces any divergence as an advisory instead of letting an unbacked claim through. A claim about child state with no matching receipt gets flagged.That doesn't give you the parent→child handle your proposal #1 would — only the harness can return the real session id — but it does remove the silent confabulation: an assertion about a child that isn't backed by a receipt no longer passes unnoticed.
The caveat to be honest about: a receipt records that a dispatch happened and its boundary state; it can't observe the child's internal progress (that needs the harness-level lineage/handle you're asking for). So this is a stopgap for the "is the parent making this up?" half, not a replacement for the native primitives.
There's a free MIT collection of reference hooks of this shape at
cc-safe-setup(thedispatch-receiptandsubagent-closure-verify-gateexamples are written for the nativeAgent/Tasktool; you'd point the matcher atmcp__ccd_session__spawn_taskto adapt them). Strong +1 on the nativespawnedByfield regardless — it's the clean fix.This is a real pain in orchestration-heavy setups. The title-matching workaround breaks quickly when you run more than 2-3 children concurrently -- titles like "analyze codebase" or "run tests" collide constantly.
A
spawnedByfield onlist_sessionsentries would be the lowest-friction fix. The parent already holds thetask_idreturned fromspawn_task, so a reverse lookup (task_id -> session entry) is all that's needed. No new API surface, just annotate what's already tracked.The confabulation problem you describe (asserting child status without actual knowledge) is probably the worst failure mode here. An orchestrator that thinks its child is running when it has actually crashed and been garbage-collected will silently produce incomplete work. There's no good way to detect this from the parent side today.
Would the
spawn_taskreturn value change be backward-compatible? If the existing return schema is fixed, aspawnedByfield onlist_sessionsalone might be easier to land without breaking current callers.Agreed — a
spawnedByfield onlist_sessionsis the lowest-friction path and probably the right first step. It doesn't break any caller, and as you note the parent already hastask_idfromspawn_task, so reverse lookup closes the title-matching gap.On the
spawn_taskreturn change (proposal #1): I framed it as "either or both" precisely because adding a new optional field to the return value (e.g.session_id) should also be backward-compatible — existing callers that read the schema by name keep working. But it's strictly more API surface than annotatinglist_sessions, so I'd be happy to see (2) land first and (1) treated as a follow-up only if cases turn up where reverse-lookup-by-task_idisn't enough (e.g. needing the handle synchronously inside thespawn_taskresponse without a subsequentlist_sessionscall).On the confabulation point: I think this is actually the most under-appreciated failure mode. I've observed multiple instances of a parent confidently asserting "child X is still running" or "child Y completed" when neither claim could be verified — it's not a hallucination in the LLM sense, it's the absence of any primitive that could ground the claim. That maps directly onto why title-matching is dangerous: it gives the parent a shape that looks like verification, so the parent doesn't notice the gap.
Building on this — a refinement that I think strengthens the
spawnedByproposal while showing it's necessary-but-not-sufficient on its own.Checking the programmatic surfaces (
list_sessions/get_session, not the UI panel): the spawned child session is tracked. It appears inlist_sessions, andget_sessionreturns a populated record —createdAt,lastActivityAt,branch,worktreePath,sourceBranch,model,isRunning. So the raw material for the reverse lookup already exists; what's missing is purely the link — nospawnedBy, no parent id, and notask_idon the record, so the parent (which holds thetask_idfromspawn_task) can't tell which tracked session is its child. That confirms @kcarriedo's framing: "annotate what's already tracked," not build new tracking.The caveat worth stating, so the fix isn't oversold:
spawnedBycloses identification, but on this same surface two other gaps sit next to it, and together they're what actually bites:list_sessionsin the first place.isRunning,lastActivityAt) isn't dependably current. I did see one finished chip whoseisRunningwas correctlyfalse— but I can't generalize that, and #68822 is evidence the state itself isn't reliable.So the three compose:
spawnedByanswers "which session is my child," #76681 answers "am I told when it finishes," #68822 answers "is the state I read actually current." A parent can only trust a child's status when all three hold; today a child that starts but fails to complete-and-notify is effectively indistinguishable from one still running.Concrete stakes from our side: a chip's finished-but-uncommitted work (#77661) sat unnoticed — its session was in
list_sessionsthe whole time, but nothing pushed a completion signal and nothing linked ourtask_idto it. And it wasn't merely unnoticed: that work lived in a worktree the pool had duplicate-allocated to a still-active session (#77609) — itself a data-loss path, since a contested or reclaimed worktree destroys uncommitted changes regardless of how they got there — so it was one routinegitoperation from gone. It survived only because a human happened to spot the stale worktree.spawnedByplus even a coarse "child finished" push would have surfaced it before that window opened.(Related: #76681, #77661, #68822, #77609.)
.md mark-format : [instantiate{prodigy}]. under-os: call , mark-frame- [if any list like executable , flag-repo : Prune-ministry]
The signal is mark- max : [Formidable, set- routery , via- call : Ministry :L. []
Agents: SLS- [Root- completion , Main-paraframes{[Active-sessions]. session-hold]]
Base AR params : <R's at 29 , 29 - [8 header - [Header-best , re-set : re_call]]>
When coarsed at beach. the grain-precedes to talk , : [] Life- long , display - semantics : [Which you have to acc]