feat(subagent): enrich subagent/start + subagent/end lifecycle events (observe-only)
A hooks bridge translating SubagentStart/SubagentStop needs to know WHICH kind of subagent ran and WHAT it produced — Claude Code's hooks carry subagent_type and the child's final message. Enrich the existing lifecycle emits to match, observe-only: - agentType: an optional caller-supplied subagent-kind label (CC's subagent_type), added to SubagentStartRequest and carried VERBATIM onto both subagent/start (SubagentRunInfo) and subagent/end (SubagentRunEndInfo). The seam never interprets it. dsh-tool-subagent threads it from a new optional Config.agentType, so a deployment exposing multiple subagent kinds (one tool load per kind) labels each. - lastAssistantMessage: the child's final output (SubagentResult.output), added to SubagentRunEndInfo on the settle path so an observer sees what the subagent produced without holding the run. Absent on the reject path (no result produced). Strictly observe-only: both events stay plain emits (subagent/end fires from a detached .then and awaits no listener). A control-flow subagent/end (awaited waterfall returning a decision) would need the emit→waterfall reshape, awaiting listeners before settling, and a provider resume capability — deferred to the background/steering redesign (FIXME(subagent-continuation) anchors it). RFC: implemented/feature/2026-06-30-subagent-observe-enrich.md.
This commit is contained in:
@@ -32,6 +32,8 @@ Unlike the bash seam (one executor per context, second load throws), **multiple
|
||||
|
||||
`provider.start(request)` returns a `SubagentRun`: a handle with a `result` promise, `cancel()`, `dispose()`, and the optional runtime methods. `result` resolves with a `SubagentResult` (`output`, optional `structured`, `stopReason`) — it does **not** reject on a child-level failure (a model/transport failure resolves with `stopReason: 'error'`), so the consumer maps a non-`completed` reason to an `isError` tool result. The consumer MUST `dispose()` on every path (success, error, abort) to reach child quiescence and avoid leaking an idle child / session.
|
||||
|
||||
The service emits `subagent/start` (payload `SubagentRunInfo`) and `subagent/end` (payload `SubagentRunEndInfo`) around the run — both **observe-only** (plain `emit`s; `subagent/end` fires from a detached `.then` and awaits no listener). Both payloads carry the request's optional `agentType` label (Claude Code's `subagent_type`, verbatim — the seam never interprets it); `subagent/end` additionally carries `lastAssistantMessage` (the child's final `output`) on the settle path, absent when the run rejected at the infrastructure level. A `subagent/start` listener can still reach the live child via `ctx.agents.get(info.id)`; a `subagent/end` listener can only observe (the run has settled). Any run-affecting decision (continuation, injection that changes the run) is out of scope for this observe-only surface.
|
||||
|
||||
## Scope (first cut)
|
||||
|
||||
The consumer collects **synchronously**: it starts a run and awaits `result`. Steering (`sendMessage`) is part of the contract but intentionally unused. Background / poll / spill semantics are deferred to a future redesign unifying long-running-tool handling across subagents and bash. See the RFC: [docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md).
|
||||
|
||||
Reference in New Issue
Block a user