Define the in-file RFC contract in docs/rfc/README.md § The file format: the header block (`# RFC: <title>` plus a dateless Status enum cross-checked against the lifecycle folder), the per-lifecycle body skeleton (a Problem opener everywhere; Proposal/Alternatives considered/ Acceptance criteria/Risks in proposed/; present-tense Decision/ Consequences with proposal-era headings banned in implemented/; the frozen proposal shape in rejected/), and a mandatory Alternatives considered section with a date-fenced grandfather comment for pre-format RFCs whose alternatives are not reconstructible from the record. Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the enum, 29 Context openers become Problem, the 39 legacy-format XXX debt markers are resolved and banned from reappearing, proposal-era sections in implemented RFCs are rewritten to shipped reality (including the web/fs/subagent seam RFCs' migration plans and test checklists, closing the doc-tiers deferred-work item on the web seam), every RFC gains an Alternatives considered section or the grandfather comment, and the bilingual pair is re-mirrored and re-recorded. Move the generated index tables out of README.md into a fully generated docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and verify-rfc-classification checks its freshness and rejects index-shaped rows in the curated README — which makes room for the format contract to live in the README front door instead of a separate FORMAT.md. The decision record, and the first RFC written in the new format, is docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
6.3 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 the capability-check mechanism itself — with eyes open about its current reach. The in-process backend genuinely enforces the cap (SubagentDepthError in packages/subagent/subagent-inprocess/src/index.ts), but no production request sets maxDepth (tool-subagent exposes no knob for it), so on the shipped tool path the guard is dormant and recursion is uncapped. The alternative — remove the depth machinery too, on the argument that a dormant guard reads like a safety property while providing none — was considered and rejected: recursion is the seam RFC's named risk, the enforcement is real working code rather than vocabulary awaiting an implementation, and the honest completion is wiring a default cap through tool-subagent (a few-line feature) rather than deleting the only existing guard. One live capability row also keeps the two-tier design demonstrated rather than merely remembered.
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.