5.7 KiB
RFC: Prune the unimplemented subagent seam vocabulary
Status: rejected — the deferred capability vocabulary (outputSchema/structured, toolFilter, sendMessage/resume) is intentionally reserved surface: the seam advertises the full intended contract ahead of its implementations by design, so providers and consumers grow into a stable shape rather than re-negotiating it per capability. The consumer-evidence analysis below stands as the record of what is currently unimplemented.
Problem
The subagent seam shipped a two-tier capability design: start-time capability flags checked by the service, and optional runtime methods on SubagentRun. Three start-time features and both optional runtime methods have zero implementations and zero callers:
outputSchema/structuredandtoolFilter(SubagentCapabilities,SubagentStartRequest,SubagentResultinpackages/subagent/subagent/src/types.ts): every real provider declaresoutputSchema: false, toolFilter: false(packages/subagent/subagent-spawn/src/index.ts,packages/subagent/subagent-fork/src/index.ts,packages/subagent/subagent-acp/src/index.ts); the sole productionctx.subagents.startcaller (packages/subagent/tool-subagent/src/index.ts) builds{ prompt, parent, signal?, agentOptions? }and structurally cannot set either;structuredis produced only by the test mock (packages/support/subagent-mock) for its own spec. The service's capability check carries two assert rows whose only exercisers are the rejection tests.SubagentRun.sendMessage/SubagentRun.resume(same file): implemented by NO provider — not even the mock; the spawn spec asserts their absence.
The only reason dsh-subagent depends on dsh-tools at all is outputSchema's SchemaSpec type. Three subsequent subagent workstreams (per-session snapshot replay, the fork seed boundary, the ACP backend) landed around this surface without growing a single consumer.
Proposal
Remove outputSchema/structured, toolFilter, sendMessage, and resume from the seam; shrink SubagentCapabilities to { depthLimit }; drop the two capability-assert rows, the all-false flags on the three providers, the mock's structured branch and its capabilities/structured config knobs, and the tests that exist to pin the removed surface (the two rejection rows, the spawn absence test, the mock structured specs). Drop the dsh-tools peer/dev dependency from packages/subagent/subagent/package.json. Update the subagent.md pastes and the type-equiv manifest, and the README rows in packages/subagent/subagent, packages/subagent/subagent-spawn, packages/subagent/subagent-fork, and packages/support/subagent-mock. The implementing PR amends the seam RFC's capability catalog per implemented/AGENTS.md.
Keep depthLimit/maxDepth and capability checks. The in-process backend enforces the limit, although the shipping tool does not yet set it. Recursion is a known seam risk, so the appropriate follow-up is to supply a tool default rather than delete working enforcement.
Adjacent surface examined and deliberately left alone: SubagentService.getProvider()/list() have test-harness consumers only, but the prune-dead-seam-methods implementation note records precisely this shape being removed from the bash executor and reverted — a test harness IS a consumer for a one-line accessor over an already-tracked map. SubagentRunEndInfo.lastAssistantMessage is a recorded keep (the subagent-observe-enrich RFC's review dropped agentType and kept it deliberately, as the only final-message channel for out-of-process children); its currently-unwired bridge forwarding is a gap to close or a consumer to document, not surface for this RFC to cut.
This is the seam-vocabulary echo of prune dead methods from the persistence seam: members every implementation must declare for nobody — weaker even, since here zero implementations exist.
Alternatives considered
Why not keep it?
The two-kinds-of-capability design is the seam RFC's headline, and re-adding outputSchema later touches several files. But the design survives with depthLimit as its live example and the RFCs as its record, and the seam RFC itself concedes the shipped toolFilter shape is wrong (real enforcement needs a tools/pre-execute deny in the child's context, not schema filtering) — that deny primitive exists on the interception seams, so re-adding against a real implementing provider will pin a better contract than the current speculative one.
Acceptance criteria
- The removed spellings appear only in this RFC and the amended seam RFCs;
SubagentCapabilitiesis{ depthLimit: boolean }; thedsh-toolsdependency edge is gone (hygienegreen). - Depth-enforcement tests are unchanged and green.
Risks
The subagent lifecycle events carry lastAssistantMessage on the end payload — that enrichment lives in the service module, not the seam vocabulary this RFC shrinks, and the observe-enrich RFC records dropping an agentType sibling for lacking a consumer: the judgment this RFC extends. The CC hooks bridge, the first outside consumer of those lifecycle events, reads only the event payloads and touches none of the surface removed here; the observe-enrich RFC's deferred control-flow redesign names implementing resume as its own future work — exactly the re-add trigger this RFC's pattern anticipates.