Files
deepseek-harness/packages/mode
kingwl 14f7976e3d fix(mode): an approved exit leaves plan mode at the step boundary, not mid-batch
Review finding: the exit tool's direct mode/set append flipped the
folded mode while the loop could still execute further tool calls from
the SAME assistant response — a same-batch exit_plan_mode + write pair
would sail past tools/pre-execute under 'default' even though the
request was assembled under the plan-shaped header. That broke the
design's own invariant (a step's executions run under the mode its
assembly folded), which the pending-intent flush was built to hold for
user flips.

The tool now records the switch as a pending intent like every other
writer, flushed at this step's end (still in-turn); pending intents
carry a narrate flag so the exit's flush stays silent — the tool result
is its narration — while user flips keep the coalesced boundary notice.
The gate, folding the logged mode only, now provably covers the whole
batch: regression test pins approve-then-write-in-the-same-batch as
denied, and the widened toolset still arrives on the next step.

The recorded scenarios are re-recorded: the fixture now shows mode/set
landing after step/end, before the widened fallback header.
2026-07-10 10:35:43 +08:00
..

mode/ — session-mode policy family

Session modes: named, logged, per-agent policy states, with plan mode as the first shipped definition. A single product package — there is no interface/implementation seam here, because a mode's variable parts are config values (allowlist, section text), not swappable implementations.

Package Role ctx key
mode/ mode/set vocabulary + fold, the ctx.modes service (list/get/set with the turn-boundary flush), the soft layer (assemble filter + mode:policy section), the hard layer (tools/pre-execute deny-by-default gate), and the model-facing exit_plan_mode review tool ctx.modes

The mode in force is a pure function of the session log (SessionEventMap['mode/set'], last one wins), so resume and fork restore it with no extra machinery; the default mode is the absence of policy, keeping the plugin invisible until a mode is set. UIs read flips off session/event: the stdio app exposes /mode, the ACP bridge maps the vocabulary to the session-mode picker. RFC: plan mode.