[FEATURE] Let a parent session observe its spawned children — return a session id from spawn_task and/or expose spawnedBy lineage in list_sessions

Status Open
Maintainer reply None cached
Activity 5 comments · opened Jun 27, 2026

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_task returns a task_id, not a session id.
  • list_sessions has no spawnedBy / lineage field, and there is no task_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:

  1. Have spawn_task return the spawned session's id (once the chip is started), so the parent holds a stable handle to its child.
  2. Add a spawnedBy (parent session id) field to list_sessions entries, so a parent can filter list_sessions to 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.

View original on GitHub ↗

5 Comments

yurukusa · 2 months ago

This is a real gap, and the two primitives you propose — spawn_task returning the spawned session id, and a spawnedBy lineage field on list_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:

  • A PostToolUse hook matched to your spawn tool (here mcp__ccd_session__spawn_task) writes a receipt the moment a child is actually dispatched — the task_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 than task_id alone gives the parent.
  • A Stop hook 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 (the dispatch-receipt and subagent-closure-verify-gate examples are written for the native Agent/Task tool; you'd point the matcher at mcp__ccd_session__spawn_task to adapt them). Strong +1 on the native spawnedBy field regardless — it's the clean fix.

kcarriedo · 2 months ago

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 spawnedBy field on list_sessions entries would be the lowest-friction fix. The parent already holds the task_id returned from spawn_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_task return value change be backward-compatible? If the existing return schema is fixed, a spawnedBy field on list_sessions alone might be easier to land without breaking current callers.

odakin · 2 months ago
Would the spawn_task return value change be backward-compatible? If the existing return schema is fixed, a spawnedBy field on list_sessions alone might be easier to land without breaking current callers.

Agreed — a spawnedBy field on list_sessions is the lowest-friction path and probably the right first step. It doesn't break any caller, and as you note the parent already has task_id from spawn_task, so reverse lookup closes the title-matching gap.

On the spawn_task return 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 annotating list_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_id isn't enough (e.g. needing the handle synchronously inside the spawn_task response without a subsequent list_sessions call).

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.

blwfish · 1 month ago

Building on this — a refinement that I think strengthens the spawnedBy proposal 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 in list_sessions, and get_session returns 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 — no spawnedBy, no parent id, and no task_id on the record, so the parent (which holds the task_id from spawn_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: spawnedBy closes identification, but on this same surface two other gaps sit next to it, and together they're what actually bites:

  • No completion push (#76681): the parent is never signaled that the child finished, so it never knows to poll list_sessions in the first place.
  • State can go stale (#68822): a spawned session that's genuinely done can fail to be reaped, so its reported state (isRunning, lastActivityAt) isn't dependably current. I did see one finished chip whose isRunning was correctly false — but I can't generalize that, and #68822 is evidence the state itself isn't reliable.

So the three compose: spawnedBy answers "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_sessions the whole time, but nothing pushed a completion signal and nothing linked our task_id to 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 routine git operation from gone. It survived only because a human happened to spot the stale worktree. spawnedBy plus even a coarse "child finished" push would have surfaced it before that window opened.

(Related: #76681, #77661, #68822, #77609.)

aromal-a · 1 month ago

.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]