dsh-steer-anchor
DeepSeek Harness (DSH) plugin: keep a running task on track when the user steers mid‑turn.
In DSH, a steering message (GUI: press Ctrl+Enter while a turn is running) is recorded as an ordinary
user/message with source.kind === 'user'. Only the UI projection marks it as "steering"; the model sees
one more user message at the end of the context and tends to treat it as a fresh instruction — abandoning the
running plan, redoing finished steps, or drifting off the task.
Codex's turn/steer has the same delivery point (input is merged into the active turn and consumed at the next
model request). The difference is that Codex's harness prompt and model training already establish "a mid-turn
message continues the current task". DSH has no such convention, so this plugin adds it explicitly.
Quick start
dsh plugin --profile <your-profile> add github:yelloooooooow/dsh-steer-anchor
Restart the instance. The next Ctrl+Enter steering message is admitted with a short handling frame.
A profile composition can also mount the row by hand instead of using the bundle — useful for hot‑reload testing, and note that the two paths must not both be enabled (the row id would be inserted twice):
# profile cordis.patch.yml
- insert:
- id: steer-anchor
name: dsh-steer-anchor
How it works
user presses Ctrl+Enter while the turn runs
│
▼
agent.steer(message) ──► inbox next-step (claimed at step N+1, N ≥ 1)
│
▼
agent/pre-step waterfall ──► this plugin prepends the handling frame
│
▼
the loop logs the admitted message as user/message and sends it
The listener:
- Runs only when
payload.step > 1, i.e. the step continues an open turn. A message that opens a turn (step === 1, including an idle steer) is left untouched. - Touches only messages whose
source.kind === 'user'. Context producers that already attribute themselves —runtime-context,peer-bus-message,agent-message— are left untouched. - Prepends one text block with three rules; the user's original blocks stay verbatim after it.
- Is idempotent: a message whose first text block already carries the marker is not framed again.
Because the agent loop appends the admitted pre-step decision messages to the session log, the framed content is durable with the message it frames. Model-visible content therefore stays reconstructable from the log (DSH's "model-visible ⟺ logged" rule), with no hidden side channel.
Frame text
【任务中途插话 · 不是新任务】
这条用户消息是在当前任务进行中补发的,请按以下规则处理:
1. 保持当前任务主线与已经完成的步骤,不要因为这条消息重启、重做或丢弃整个任务;
2. 把它当作补充信息、约束或纠偏来纳入;只有它明确要求改变目标或步骤时,才调整与它冲突的部分;
3. 若与当前计划无冲突:简短确认后继续原步骤;若有冲突:说明差异,只改冲突部分,保留已完成的有效结果。
【用户插话原文】
(The rules are written in the language most of this deployment's sessions use. Edit ANCHOR_TEXT in
lib/index.js to change them.)
Cost
One extra text block per steering message: 198 characters (~100 tokens). Nothing else — no extra model calls, no extra messages, no rewriting of history.
Self-check
While mounted under the Web profile, the plugin serves a read-only route on the local server:
curl "http://127.0.0.1:3080/api/steer-anchor/state?selftest=1"
{
"ok": true,
"mounted": true,
"marker": "【任务中途插话 · 不是新任务】",
"framed": 1,
"lastTurn": 1,
"lastStep": 2,
"lastChars": 198,
"selftest": { "rewritten": true, "keptOriginal": true }
}
framed counts every framed steering message; lastTurn / lastStep name the most recent one. Steering is
always claimed at step >= 2.
Limitations
- Covers GUI user steering (
source.kind === 'user'). Peer-bus and subagent messages carry their own attribution and are intentionally left alone. - The frame appears inside the user's message bubble — the visible price of keeping "model-visible ⟺ logged".
- The frame is guidance, not enforcement: the model can still decide an interjection changes the goal, and the rule explicitly allows changing only the conflicting part.
- Text is currently fixed at the module level; no config surface yet.
License
MIT
No comments yet. Be the first to write one.