refactor(subagent): drop the agentType lifecycle field

Address review: `agentType` was a Claude-Code concept (`subagent_type`) that
does not fit our own subagent seam — nothing in the harness interprets it, and
its only consumer was the CC-dialect hook bridge. Rather than let a foreign
concept sit on the core seam, remove it:

- `SubagentStartRequest`, `SubagentRunInfo`, `SubagentRunEndInfo`: drop the
  `agentType` field; the `subagent/start`/`subagent/end` payloads now carry
  `provider`/`id` (+ end `stopReason`/`lastAssistantMessage`) only.
- `dsh-tool-subagent`: drop `Config.agentType` and its request plumbing.
- Tests: keep the lastAssistantMessage / clone-containment / reject-path
  coverage (rewritten to not assert agentType); delete the two tool-subagent
  tests that only exercised agentType forwarding (dead behavior).
- Docs: retitle + rewrite the subagent-observe-enrich RFC to the one shipped
  enrichment (lastAssistantMessage), with a note on why agentType was dropped;
  update rfc/README index title, both subagent READMEs, and the
  core-data-structures/subagent.md type-equiv block + prose; regenerate catalog.

The CC bridge (PR-F) will feed Claude Code's own default matcher value
"general-purpose" for its SubagentStart/Stop agent_type matcher instead.
This commit is contained in:
Tianyi Cui
2026-07-02 05:52:02 +08:00
parent 8514bddd82
commit 84f3019310
11 changed files with 29 additions and 138 deletions

View File

@@ -20,9 +20,9 @@
* semantics are deferred to a future redesign that unifies long-running-tool
* handling across subagents and bash.
*
* The `subagent/start` / `subagent/end` lifecycle events carry an enriched but
* OBSERVE-ONLY payload (`agentType`, and on end `lastAssistantMessage`) — see
* `docs/rfc/implemented/feature/2026-06-30-subagent-observe-enrich.md`.
* The `subagent/start` / `subagent/end` lifecycle events carry an OBSERVE-ONLY
* payload; `subagent/end` additionally carries the child's `lastAssistantMessage`
* — see `docs/rfc/implemented/feature/2026-06-30-subagent-observe-enrich.md`.
* FIXME(subagent-continuation): a control-flow `subagent/end` (an awaited
* waterfall returning a stop/continue decision, like the other interception
* seams) would require reshaping this emit into a waterfall, awaiting listeners
@@ -82,13 +82,6 @@ export interface SubagentRunInfo {
provider: string
/** The child agent's id. */
id: AgentId
/**
* The caller's subagent-kind label, carried verbatim from
* {@link SubagentStartRequest.agentType} (Claude Code's `subagent_type`).
* Absent when the caller did not supply one. An observer (a hooks bridge,
* a UI) reports or matches on it; the seam never interprets it.
*/
agentType?: string
}
/** Outcome detail for a settled subagent run (the `subagent/end` payload). */
@@ -97,8 +90,6 @@ export interface SubagentRunEndInfo {
provider: string
/** The child agent's id. */
id: AgentId
/** The caller's subagent-kind label (see {@link SubagentRunInfo.agentType}). */
agentType?: string
/** The terminal stop reason. */
stopReason: SubagentResult['stopReason']
/**
@@ -187,10 +178,7 @@ export class SubagentService extends Service {
// acceptable. `ctx.emit` halts the dispatch on the first throw, so a single
// surrounding try/catch is not enough — each listener is invoked and
// contained individually.
// Carry the caller's subagent-kind label verbatim onto both lifecycle events
// (absent when not supplied — the spread omits the key for exactOptionalPropertyTypes).
const agentType = request.agentType !== undefined ? { agentType: request.agentType } : {}
this.emitLifecycle('subagent/start', { provider: name, id: run.id, ...agentType })
this.emitLifecycle('subagent/start', { provider: name, id: run.id })
// Emit `subagent/end` when the run settles. The result promise does not
// reject on a child-level failure (it resolves with stopReason 'error'),
// so a rejection here is an infrastructure fault — surface its stop reason
@@ -220,9 +208,9 @@ export class SubagentService extends Service {
} catch (error: unknown) {
this.ctx.logger.warn(`subagent: could not clone ${name} output for subagent/end: ${String(error)}`)
}
this.emitLifecycle('subagent/end', { provider: name, id: run.id, ...agentType, stopReason: result.stopReason, ...lastAssistantMessage !== undefined ? { lastAssistantMessage } : {} })
this.emitLifecycle('subagent/end', { provider: name, id: run.id, stopReason: result.stopReason, ...lastAssistantMessage !== undefined ? { lastAssistantMessage } : {} })
},
() => { this.emitLifecycle('subagent/end', { provider: name, id: run.id, ...agentType, stopReason: 'error' }) },
() => { this.emitLifecycle('subagent/end', { provider: name, id: run.id, stopReason: 'error' }) },
)
return run
}