steer: drained mid-run steer is recorded with no model-visible framing, so the model abandons the in-flight task
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 152
- Forks
- 16
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 536
Description
Bug description
Typing a follow-up while a run is streaming (mecatui: enter mid-run on a
steer-capable server) makes the model abandon the in-flight task and start
the new one. From the operator's seat it reads as "adding a queued request
cancels the previous one and starts processing the new one."
Verified against the code: no cancellation exists anywhere on this path.
- Client:
cmd/mecatui/ui/update.go(enqueuePrompt→sendSteer) sends a
steer frame into the live Converse stream; it never cancels the run. - Engine:
engine/agent/steer.goparks the steer in the single-slot
steerInbox; the loop drains it at the next turn boundary — the
in-flight provider turn always completes; the run is never interrupted. - Commit:
engine/agent/steer.go(commitSteer) records the drained text via
session.Session.RecordUserPromptWithParts(content.text, …)— a bare,
unframed user message. There is no harness note, no prefix, and no
prompt-layer contract anywhere (grep for steer framing inengine/agent/
internal/appcomes back empty) telling the model what a mid-run steer IS.
So the "termination" is the model's doing, structurally invited: mid-task it
receives a fresh user message carrying only the new instruction, and an LLM
treats the newest user instruction as superseding. It silently drops the
in-flight task and pivots. The harness's own docs promise the opposite
semantic — docs/tui.md keys table: "enter (while a run streams) → steer
the current run when supported (applies at the next turn boundary)" —
steering means guiding the current work, not replacing it.
This is the ADR 0070 bug class, verbatim from AGENTS.md: "A model-facing
gate/affordance that depends on the model's behavior is incomplete without a
model-visible prompt instruction + a test proving that instruction lands in
the system prompt." The steer affordance depends entirely on the model
treating the injected message as mid-run guidance — and nothing tells it so
(the same failure shape as issue #206's plan-approval gate).
Steps to reproduce
- mecatui against a steer-capable server; start a long multi-turn task
("implement X across these files"). - While it streams, type a second, unrelated instruction ("also file an
issue about Y") and press enter → status shows "steering…". - At the next turn boundary the steer text is recorded as a plain user turn.
- Observe the model abandon task X (often without acknowledging it) and work
exclusively on Y. Task X's remaining steps are silently lost.
Expected behavior
The documented steer semantic: the current task continues; the steer is
incorporated as guidance/additional work. At minimum the model should
explicitly acknowledge the pivot-vs-continue decision rather than silently
dropping the in-flight task.
Actual behavior
The model treats the steer as a replacement instruction; the previous task is
abandoned mid-flight. Indistinguishable, from the operator's seat, from the
harness cancelling the run.
Environment
- mecatl @
ebb14c78, mecatui embedded engine (steer-capable), observed
repeatedly during a long implementation session on macOS 26.5.1.
Additional context
Candidate fix-shape, following the repo's own idiom:
- Frame the drained steer at commit —
commitSteerwraps the recorded
continuation with a short harness note (the trusted-principal analogue of
the*PostureNoteconstants), e.g. a prefix marking it as a MID-RUN
STEERING NOTE: incorporate into the current task; do not abandon in-flight
work unless it explicitly says to stop. The steer text itself is the
principal's own input (trusted,WithTeamGoal-style provenance), so this
is framing, not fencing. - Or a prompt-layer contract — a steer posture note in the system prompt
(theapplyXPostureidiom) telling the model steered continuations are
additive guidance; per ADR 0070 this needs the paired test asserting the
instruction lands in the BUILT engine's prompt via the real factory path. - mecatui's keys-table wording and the
enqueuePromptdoc comment describe
queue-mode as "follow-up when the turn ends" and steer-mode as "steer the
current run" — whichever fix lands should make the runtime behavior match
that documented split, and the local-queue drain (popAndSubmit, which
merges ALL staged lines into ONE prompt at run end) may deserve the same
framing treatment for its multi-task case.
Not a duplicate of the steer plumbing tests (steer_test.go etc.) — those pin
the wire/inbox mechanics (accepted/appended/retracted/too_late), which all
work as designed. The gap is exclusively the missing model-visible contract on
the recorded continuation.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in engine/agent/steer.go at commitSteer and trace the existing steer tests in engine/agent/steer_test.go; compare the recorded continuation with the documented behavior in docs/tui.md and the ADR 0070 guidance in AGENTS.md. Check the real engine factory and prompt path described by the issue. Done means a mid-run steer is model-visible as guidance, in-flight work is not silently abandoned, and a regression test proves the contract.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- ai, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100