Steer Lane Consult
Short Answer¶
Use real same-turn steering only for Codex app-server. codex exec is not steerable mid-run. Claude stream-json input can accept later user messages, but they queue as later turns; use Claude Agent SDK if you need interrupt control. agy --print has no steer lane. Grok has ACP (grok agent stdio) with session/prompt and session/cancel, but no Codex-style append-to-active-turn.
1. Codex¶
Codex app-server exposes the behavior you want directly.
Public model: thread -> turn -> streamed items. The app-server lifecycle says: start/resume thread, call turn/start, then call turn/steer to append user input to the currently in-flight turn without creating a new turn. It also documents turn/interrupt separately. Source: Codex app-server docs describe turn/steer as appending input to the active in-flight turn, with no new turn/started notification. (developers.openai.com)
codex exec: no mid-run steering surface. It reads stdin only as initial instructions when prompt is omitted or - is used; --json only streams output events. Source: non-interactive mode documents codex exec --json as JSONL output events, not bidirectional input. (developers.openai.com)
Recommended Codex launch for fleet:
codex app-server --listen ws://127.0.0.1:4500
For remote use, add WebSocket auth:
codex app-server --listen ws://0.0.0.0:4500 --ws-auth capability-token --ws-token-file C:\secrets\codex-ws-token.txt
Handshake and run:
{"method":"initialize","id":0,"params":{"clientInfo":{"name":"fleet_dispatch","title":"Fleet Dispatch","version":"0.1.0"}}}
{"method":"initialized","params":{}}
{"method":"thread/start","id":1,"params":{"cwd":"C:\\repo","model":"gpt-5.4","approvalPolicy":"never","sandbox":"workspaceWrite"}}
{"method":"turn/start","id":2,"params":{"threadId":"thr_123","input":[{"type":"text","text":"Initial task"}]}}
Steer active turn:
{"method":"turn/steer","id":3,"params":{"threadId":"thr_123","expectedTurnId":"turn_456","input":[{"type":"text","text":"Steer: stop broad refactor; fix only failing auth test."}]}}
Cancel/interrupt active turn:
{"method":"turn/interrupt","id":4,"params":{"threadId":"thr_123","turnId":"turn_456"}}
Important timing: turn/steer is same-turn injection, not process kill. It will not magically abort a shell command already running; it becomes useful at the next point Codex can incorporate new input in its agent loop.
2. Claude Code Headless¶
Claude Code has streaming input:
claude -p --input-format stream-json --output-format stream-json --verbose --replay-user-messages
CLI reference confirms --input-format stream-json, --replay-user-messages, and that messages sent while Claude is working stay queued and run as their own turn when the current one ends. (code.claude.com)
Input shape:
{"type":"user","message":{"role":"user","content":"Initial task"},"parent_tool_use_id":null}
{"type":"user","message":{"role":"user","content":"Steer: focus only on the failing test."},"parent_tool_use_id":null}
So: yes, stdin can stay open and accept later user messages. No, it is not equivalent to Codex turn/steer; it is queued-turn input, not active-turn append. Claude Agent SDK streaming mode explicitly supports queued messages and interruptions. (code.claude.com) The Python SDK exposes interrupt(); after interrupt, drain the interrupted result before reading the next query. (docs.anthropic.com)
Best Claude fleet options:
- For “continue after current turn”: stream JSON stdin, keep pipe open.
- For “stop current work and redirect”: use Agent SDK
interrupt()or kill process, thenclaude -p --resume <sessionId> "<new instruction>". - Hooks/MCP are not good steer lanes; they are lifecycle/tool-control surfaces, not external user-message injection.
3. agy And Grok¶
agy 1.1.3 local help exposes --print, --prompt-interactive, --continue, and --conversation, but no stream-json input, stdin control protocol, or same-turn steer. Treat as no headless steer surface. Fallback: cancel/kill and resume with agy --conversation <id> -p "<recovery prompt>".
Grok has two different surfaces:
grok -p/--single: no stdin steer; one prompt, optional--output-format streaming-json.grok agent stdio: ACP JSON-RPC. xAI docs showgrok agent stdio,session/new,session/prompt, and streamedsession/updatechunks. (docs.x.ai) ACP itself defines one prompt turn at a time plussession/cancel; after completion, send anothersession/prompt. (agentclientprotocol.com)
So Grok ACP supports structured control, cancel, and next-turn prompts, not Codex-style same-turn steering.
4. Fleet Steer Lane¶
Use one signed marker lane for all agents, but map to provider capability.
Marker path:
<dispatchRoot>/<target>/steer/<jobId>/<sequence>.json
Payload:
{
"jobId": "job-123",
"sequence": 7,
"createdUtc": "2026-07-17T18:22:00Z",
"mode": "append|interrupt_then_append|replace",
"message": "Focus on failing test only. Do not refactor unrelated code.",
"hmac": "base64..."
}
Capability map:
[Flags]
public enum SteerCaps
{
None = 0,
SameTurnSteer = 1,
QueueNextTurn = 2,
Interrupt = 4,
Resume = 8
}
Routing:
switch (agent.Kind, caps, steer.Mode)
{
case ("codex-appserver", var c, _) when c.HasFlag(SteerCaps.SameTurnSteer):
await codex.TurnSteerAsync(threadId, activeTurnId, steer.Message);
break;
case ("claude-stream", var c, "append") when c.HasFlag(SteerCaps.QueueNextTurn):
await claude.WriteUserMessageAsync(steer.Message);
break;
case ("claude-sdk", var c, "interrupt_then_append") when c.HasFlag(SteerCaps.Interrupt):
await claude.InterruptAsync();
await claude.DrainInterruptedResultAsync();
await claude.QueryAsync(steer.Message);
break;
case ("grok-acp", var c, "interrupt_then_append") when c.HasFlag(SteerCaps.Interrupt):
await grok.SessionCancelAsync(sessionId);
await grok.WaitCancelledAsync(sessionId);
await grok.SessionPromptAsync(sessionId, steer.Message);
break;
default:
await KillAndResumeAsync(job, steer);
break;
}
Fallback Policy¶
Kill-and-resume-with-appended-context is sane, but label it as degraded steering.
Use it when:
- agent has no live input protocol;
- steer message supersedes current task;
- current run is wasting expensive time;
- user says “stop doing X”.
Resume prompt should include:
Previous headless run was stopped for steering.
New user steering:
<message>
Preserve useful completed work. First inspect current git diff/status. Do not repeat already completed steps unless needed. Continue from existing workspace state.
Tradeoffs: loses in-memory state, may drop partial hidden context, can duplicate work, can leave partial file edits, and costs extra tokens. Mitigate by persisting stdout/stderr JSONL, session id, last known turn id, git diff, process exit reason, steer sequence, and resume command.