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.
4.2 KiB
RFC: Remove the agent/steering mirror emit
Status: implemented
Problem
agent/steering was the last remaining transient mirror of a durable session event. The loop's steering drain appends the durable steering/message { turn, content, source } and, on the very next line, emitted agent/steering(agent, turn, content, source) — the identical fact as a fire-and-forget event (packages/core/agent-loop/src/loop.ts, drainSteering). It had zero production listeners: the only subscriber anywhere was a loop regression test asserting the emit carried source — the same fact the durable event already records one line above.
Both mirror-removal RFCs retained it while explicitly deferring the decision this RFC makes. The boundary-mirror removal kept it as a live control signal rather than a boundary; the stream-chunk removal retained it on the reading that it had no durable twin. The second rationale did not survive the code: the durable twin is steering/message, appended immediately before the emit with the same payload. The mirrored-vs-live-only line the taxonomy actually draws puts it on the mirror side: agent/queued is genuinely live-only (it fires at enqueue time, before any durable event exists, and already carries a steering: boolean flag — cancelled queued work never enters the log), while agent/steering fired at the exact moment its durable twin landed, carrying nothing the log does not.
Steering carries real production traffic — the hook bridges' turn-continuation decisions inject their reasons through inbox.steer(), landing as durable steering/message events that the hook-matrix goldens pin — and every one of those consumers observes the durable event. Nothing observed the mirror.
Decision
agent/steering is removed from the agent event taxonomy: the declaration in packages/core/agent/src/types.ts (and its mention in the live-events JSDoc list there), the emit in drainSteering (whose then-unused ctx parameter went with it), the row in packages/core/agent/README.md, and the emit line in the loop-pseudocode blocks (the packages/core/agent-loop/src/loop.ts module doc and architecture.md); the cordis catalog is regenerated without it. The one regression test pins source preservation on the durable steering/message event — the fact it pins lives on the log.
Three implemented RFCs stated the retention, and each is amended per implemented/AGENTS.md to point here as the record of the removal: the boundary RFC's retained-list entry, the stream-chunk RFC's scope clause, and the event-domain-semantics RFC's transient-emit enumeration.
Alternatives considered
Why not keep it?
"It is a control signal, not a boundary" — but the taxonomy's operative distinction is mirrored-vs-live-only, not control-vs-boundary, and this event mirrored. A consumer that wants enqueue-time notification has agent/queued (with its steering flag); a consumer that wants drain-time notification is by definition asking for the moment steering/message is appended, which session/event delivers with the same payload plus durability. The rejected retire-mid-turn-steering RFC defended the steering capability — steer(), the durable event, continuation forcing — all of which this removal keeps untouched.
Verification
The agent/steering spelling survives only in RFC prose (this RFC, the three amended RFCs above, and the frozen rejected steering-capability RFC, whose text records the proposal it declined); the catalog is regenerated; the retargeted test pins source preservation on steering/message.
Consequences
Zero production listeners existed to migrate, and both live-notification needs keep surviving homes: enqueue-time on agent/queued (with its steering flag), drain-time on session/event as the durable steering/message lands.