Files
deepseek-harness/docs/rfc/rejected/simplification/2026-06-20-retire-mid-turn-steering.md
T
Tianyi Cui e6fad266a6 docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus
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.
2026-07-05 22:58:25 +08:00

3.7 KiB

RFC: Retire mid-turn steering

Status: rejected — mid-turn steering is an intentional agent capability for between-step user/plugin input and future goal/loop workflows. It is complexity with a product direction, not an accidental duplicate of send().

Problem

The agent exposes two user-message paths that look close but have different lifecycle semantics: send() queues a normal user turn, while steer() injects a message between steps of the currently running turn and falls back to send() when idle. That distinction leaks through the whole stack: Agent.steer() is public API, the session log has a durable steering/message event, the agent event taxonomy has agent/steering, the loop maintains a steering FIFO beside the queued-message FIFO, cancellation clears both queues, and deriveMessages() has to render steering as a tagged synthetic user message rather than a normal prompt.

The continuation seam amplifies the cost. agent/turn-continuation defaults to hadToolCalls || steeringInjected, so a same-turn steering message can force the loop to call the model again even if the model did not ask for tools. The comments name future /goal, /loop, and budget-guard uses, but the current repo has no production listener; only tests register the waterfall. Separately, the only production UI that calls steer() is the stdio demo. ACP already sends prompts through the ordinary queue while a turn is running.

Proposal

Delete mid-turn user steering for now. Agent.send() becomes the single public way to submit user content; when the agent is running, the content waits for the next turn. The loop continues within a turn only for tool calls, not because a user typed while a step was running. A caller that wants to interrupt the current turn uses cancel() and then send().

Remove Agent.steer(), the steering FIFO, steering/message, agent/steering, steering-derived continuation, and the cancellation logic that distinguishes queued messages from steering messages. Remove agent/turn-continuation in the same change unless the implementing PR discovers a production listener; without steering, the current repo has no concrete continuation consumer left. If a real budget or goal plugin later needs forced continuation, it should reintroduce a narrower seam with that plugin as the concrete consumer.

Acceptance criteria

  • Agent exposes one user-message entry point, send().
  • The durable session event vocabulary no longer contains steering/message.
  • deriveMessages() renders normal user messages and context injections, with no steering tag path.
  • The loop has one queued-message FIFO and no same-turn user-message continuation path.
  • agent/turn-continuation is removed or narrowed to a named production consumer.
  • The stdio UI and docs describe input while running as queued next-turn input.
  • The session format version and recorded fixtures are refreshed; non-current stored logs are rejected per the pre-release format policy.

What we give up

A user cannot add same-turn steering content while a model is between tool steps. That behavior is useful in theory for "while you are already working, also consider X", but it is not the behavior ACP exposes today and it makes the turn boundary much harder to reason about. The simpler behavior is reasonable: user input becomes the next prompt, and cancellation remains the explicit tool for replacing in-flight work.

This pairs naturally with dropping durable step boundaries, because removing same-turn steering and agent/turn-continuation leaves tool calls as the only reason a turn contains multiple model steps.