fix(agent): deliver mid-turn /steer as a genuine role:user message, not marker text - #82467
fix(agent): deliver mid-turn /steer as a genuine role:user message, not marker text#82467ygd58 wants to merge 1 commit into
Conversation
…ot marker text Fixes NousResearch#81828. The /steer mid-turn user-steering channel appended a static plaintext marker ([OUT-OF-BAND USER MESSAGE ...]) as TEXT inside a role:"tool" message's own content. STEER_CHANNEL_NOTE told the model to trust ONLY this exact marker and ignore lookalikes in tool/web/file output -- but that defense has no effect against a lookalike the model itself constructs, since the marker text is visible in the model's own system prompt and any capable model can reproduce it verbatim. In an observed production case, a model inflated an offhand remark in a normal user message into a fabricated "user said X" block, wrapped it in the exact marker, and then treated its own fabrication as a genuine mid-turn instruction on its next turn. Implements the structural-separation fix: both delivery sites (agent/conversation_loop.py's pre-API-call drain and agent/agent_runtime_helpers.py::apply_pending_steer_to_tool_results) now insert a genuine role:"user" message immediately after the last tool result, instead of appending marker text to that tool message's own content. A model cannot fabricate a message with role:"user" in the API request itself -- role assignment is set by the runtime, not model output, closing the self-fabrication vector structurally rather than relying on the model reliably self-policing a text pattern it can reproduce. Role alternation is preserved: assistant -> tool -> user (steer) -> assistant is a standard, provider-supported sequence (a genuine user follow-up after a tool result), not a novel pattern -- the same slot the previous design already used, just with a real message instead of appended text. STEER_MARKER_OPEN / STEER_MARKER_CLOSE / STEER_CHANNEL_NOTE / format_steer_marker() in agent/prompt_builder.py are left completely unchanged (verified byte-for-byte identical via diff -- only comments were touched) to preserve prompt-cache stability, matching the design constraint 0f45509 established when it kept the marker static rather than per-session/hash-based for the same reason. They're kept in place rather than removed since tests still exercise their own internal consistency and another call site may still reference them. Updated existing tests in tests/run_agent/test_steer.py and tests/run_agent/test_tool_batch_segmentation.py to assert the new role:"user" insertion behavior instead of the old marker-text-append behavior. Verified as a genuine fix by reverting agent_runtime_helpers.py's change in isolation and confirming 3 of 4 TestSteerInjection tests fail, with the marker text visibly present in tool message content exactly as the vulnerability describes. 60/61 pass across tests/run_agent/test_steer.py and tests/run_agent/test_tool_batch_segmentation.py (1 pre-existing unrelated skip); 49/49 across turn-finalizer, start-order-gate, concurrent-interrupt, and subagent-steer test files (no regression). Complementary to NousResearch#82208 (open, different author), which independently fixes a related but distinct symptom in the same STEER_CHANNEL_NOTE system-prompt text: that PR stops the prompt from shipping a complete, delivery-shaped exemplar block that a model could mistake for an already-delivered empty steer. This PR does not touch that exemplar text and does not change format_steer_marker()'s output shape, so the two fixes are independent and non-overlapping: NousResearch#82208 addresses the system prompt teaching the model to recognize a byte-identical lookalike, while this PR removes the model's ability to have a self-fabricated lookalike trusted in the first place, regardless of how the note is worded.
|
suggesting changes The new Security evidence:
Not checked:
Signed: GPT-5.6-sol-xhigh in Codex |
Fixes #81828.
Problem
The /steer mid-turn user-steering channel appended a static plaintext marker (
[OUT-OF-BAND USER MESSAGE ...]) as TEXT inside arole:"tool"message's own content.STEER_CHANNEL_NOTEtold the model to trust ONLY this exact marker -- but that defense has no effect against a lookalike the model itself constructs, since the marker text is visible in the model's own system prompt. In an observed production case, a model inflated an offhand remark into a fabricated "user said X" block, wrapped it in the exact marker, and treated its own fabrication as a genuine mid-turn instruction.Fix
Implements the structural-separation fix proposed in the issue: both delivery sites now insert a genuine
role:"user"message immediately after the last tool result, instead of appending marker text to that tool message's own content. A model cannot fabricate a message withrole:"user"in the API request itself -- role assignment is set by the runtime, not model output.Role alternation is preserved:
assistant -> tool -> user (steer) -> assistantis a standard, provider-supported sequence.STEER_MARKER_OPEN/STEER_MARKER_CLOSE/STEER_CHANNEL_NOTE/format_steer_marker()are left completely unchanged (verified byte-for-byte via diff) to preserve prompt-cache stability, matching the design constraint0f45509daestablished.Complementary to #82208
That PR (open, different author) independently fixes a related but distinct symptom: it stops the system prompt from shipping a complete, delivery-shaped exemplar block in
STEER_CHANNEL_NOTEthat a model could mistake for an already-delivered empty steer. This PR doesn't touch that exemplar text and doesn't changeformat_steer_marker()'s output shape -- the two fixes are independent and non-overlapping.Verification
Updated existing tests to assert the new behavior. Verified as a genuine fix by reverting the change in isolation and confirming 3 of 4 tests fail with the marker text visibly present in tool message content.
60/61 pass across the two directly affected test files (1 pre-existing unrelated skip); 49/49 across four more related test files (no regression).