Files
deepseek-harness/packages/subagent/tool-subagent
Tianyi Cui 7cc7b9cf7f 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.
2026-06-30 21:29:08 +08:00
..

@deepseek-ai/dsh-tool-subagent

The model-facing subagent tool: delegate a self-contained task to a child agent and return its final output. Pure schema + lifecycle shaping over the ctx.subagents provider registry — an in-process, ACP, or future A2A backend swaps in without changing what the model sees.

Provider selection is config, not model-facing

This plugin binds to exactly one provider (Config.provider). The model sees only { description, prompt } — there is no provider/type parameter in the schema. To expose more than one transport, load the plugin more than once, each bound to a different provider and a distinct toolName (the tool registry rejects a duplicate name, so a second load that kept the default subagent name would throw). Keeping selection in config (not the schema) is the deliberate split: the service holds a multi-provider registry; the tool picks one.

Config key Meaning
provider (required) The ctx.subagents provider name to start runs on (spawn, fork, acp, …).
toolName The model-facing tool name to register (default subagent). Set a distinct value per load when exposing multiple providers, e.g. subagent + subagent_acp.
agentOptions Default per-child { model?, systemPrompt? } applied to every spawned child.
agentType Optional subagent-kind label (Claude Code's subagent_type) stamped on every run's subagent/start/subagent/end events, so an observer (a hooks bridge, a UI) can report or match on which kind ran. Set a distinct value per load when exposing multiple subagent kinds.

Lifecycle (synchronous collect)

execute starts a run on the configured provider and awaits run.result inside a try/finally that always dispose()s the run — the owned child agent/session is torn down on every path (success, error, abort), never leaked. The tool's abort signal (exec.signal) is bridged to run.cancel(). A non-completed stop reason (aborted/error/max-tokens/refusal) maps to an isError tool result rather than returning partial output as success.

Background / poll collection is deferred (see the RFC); this cut blocks the parent turn until the child finishes.