Skip to content

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, then claude -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 show grok agent stdio, session/new, session/prompt, and streamed session/update chunks. (docs.x.ai) ACP itself defines one prompt turn at a time plus session/cancel; after completion, send another session/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.