Tell the model when a message was a steer

A message typed during a turn reaches the model at the next model call if
the turn has one left, and otherwise as the opening line of the next turn --
Claude's read out of the fifo after the turn ended, Codex's requeued when
turn/steer is refused. Read there it is indistinguishable from a reply, so
the model treats the answer it just gave as seen.

Both drivers now compose the text the CLI receives through
driver::message_body, which prefixes a note saying the message was written
without having seen the rest of that turn. The transcript still holds the
words that were typed; only the CLI's copy carries the note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-13 22:30:32 -04:00
1 parent 59ebd75b46
commit 3af2502982
4 files changed
+107 -18

No files matched your search

+13 -5
View File
@@ -44,6 +44,14 @@ struct Waiting {
/// The id Codex echoes on its user-message item.
#[serde(default)]
client_id: String,
/// Typed while a turn was already running, so the CLI is told as much --
/// see `super::driver::message_body`. Recorded at the moment it was typed
/// rather than read off the turn state when it is dispatched, because a
/// steer Codex refuses as `activeTurnNotSteerable` is requeued and sent as
/// the start of the next turn, which is precisely the case the note exists
/// for.
#[serde(default)]
steering: bool,
text: String,
#[serde(default)]
attachments: Vec<AttachmentRef>,
@@ -202,10 +210,12 @@ impl Driver for CodexDriver {
return;
}
let id = state.running.then(super::random_hex).unwrap_or_default();
let steering = state.running;
state.running = true;
state.waiting.push_back(Waiting {
id: id.clone(),
client_id: format!("ai-app-{}", super::random_hex()),
steering,
text: text.clone(),
attachments: attachments.clone(),
});
@@ -463,7 +473,7 @@ fn sandbox_policy(mode: &str) -> Value {
}
fn input_for(inner: &Inner, message: &Waiting) -> Result<Vec<Value>> {
let mut text = message.text.clone();
let mut files = Vec::new();
let mut input = Vec::new();
for attachment in &message.attachments {
let path = attachment_path(&inner.session_dir, attachment)?;
@@ -477,12 +487,10 @@ fn input_for(inner: &Inner, message: &Waiting) -> Result<Vec<Value>> {
input.push(inline_image(&path, media_type)?);
}
} else {
if !text.is_empty() {
text.push_str("\n\n");
}
text.push_str(&format!("Attached file: {}", path.display()));
files.push(path);
}
}
let text = super::driver::message_body(&message.text, &files, message.steering);
if !text.is_empty() {
input.insert(0, json!({"type": "text", "text": text}));
}