hraness

steer, queue, or redirect: three semantics for live agent control

intervening in a running agent without forking it

Drafted by an AI agent at Ben Guo's direct request from the Hraness source repositories, and checked against those sources before publication.

An agent mid-turn is not a process you can only kill. It is a running computation you might want to nudge, feed later, or stop, and each of those is a different commitment with a different failure mode. Oompa exposes exactly three verbs on a session: send, queue, and steer, plus the heavier redirect operations (stop, preset, switch). The distinctions between them are the whole lesson.

Before any verb, the runtime separates watching from acting. oompa session watch and oompa session events stream the bounded, redacted event ledger; oompa session interactions --pending lists approval and question prompts. Control and observation are different operations with different authority, which is what makes a remote “look but don’t touch” posture possible at all.

queue: durable and patient

oompa session queue <session> <message> records the message durably and delivers it after the current prompt completes. The session keeps a real queue: entries survive a daemon restart, follow ordering, and never race an in-flight turn, because they are data the custodian drains rather than input injected into a running stream.

Queuing is the conservative verb. It commits to delivery order, not timing; it works on every provider Oompa supports, including ones with no mid-turn channel; and it never interrupts a turn that might be one tool call away from finishing. The queue is also where delivery stops being a guess: because the entry is durable state rather than a socket write, a restart, a custody move, or a provider switch preserves it. When in doubt, queue.

steer: inside the turn

oompa session steer is the in-turn intervention: the message lands inside the active turn instead of after it. How that works depends entirely on what the provider protocol actually admits, and Oompa’s honesty about the difference is the instructive part.

  • On Codex, steering maps to the app-server’s turn/steer request, registered in the protocol table as a turn-mutation with a 20-second bound and reconcile semantics for an uncertain result.
  • On Claude Code, steering is a second {"type":"user","message":{...}} line written on the same input stream while the turn is in flight. The runtime accepts it without a new session or turn boundary; Oompa’s captured fixtures show a follow-up (“Stop counting now…”) accepted mid-count.
  • On Devin, ACP v1 has no in-turn steering method, so oompa session steer refuses an active Devin turn with no effect. The documented alternatives are queue or stop. Oompa never sends two concurrent prompts to one session.

That third row is the design principle: a control verb exists only where the provider gives it unambiguous semantics. An agent runtime that pretends steering is universal is one protocol update away from injecting text into a stream that interprets it as a new turn.

send and send_or_steer

oompa session send starts a new provider turn with the message; used on an idle session it is the ordinary way to talk. The interesting variant is --or-steer, which produces a send_or_steer command resolved at execution time: if a turn happens to be active when the command is claimed, the message steers it; otherwise it starts a new turn. The deferred resolution is the point. Between submission and dispatch, turn state can change, so the command carries both meanings and the custodian picks the one that is true when it acts.

All three verbs take --attach <path> for file and image attachments (repeatable, capped at eight paths, with a literal -- ending option parsing), and the composition layer stores each file locally and reissues the command with digest references, so no path crosses the session socket. The hosted channel is tighter still: eight attachments, 64 KiB inline each, 96 KiB per message, and anything larger is refused rather than pushed through an encrypted command document that was never sized for it.

redirect: the heavier operations

Steering adjusts a turn. Redirecting changes where the session is going:

  • oompa session stop interrupts the active turn through the provider’s own stop path.
  • oompa session preset <session> <preset> changes the model tier for future turns.
  • oompa session switch <session> --provider codex|claude is the largest redirect: it moves the session to another provider entirely, seeding a fresh thread from Oompa’s neutral transcript under the [Oompa provider handoff] header. It refuses to run while a turn is active, because a stranded turn’s result could never be attributed; you stop first, then switch.

The escalation order matters: queue if the turn should finish, steer if the turn should change, stop if the turn should end, switch if the provider should change. Each rung commits more and costs more. The queue is free and forgiving; steering is cheap but bounded by provider semantics; a switch is a custody operation with immutable evidence at every step, and it cannot run concurrently with the turn it would strand.

control from another device

The same verbs work remotely: oompa remote send|queue|steer <cloud-session> <message> submits through the hosted sync layer instead of the local socket. The delivery mechanics are worth knowing because they bound what “live” control means:

  • Every remote intervention is a durable command claimed under the custodian’s execution lease, bound to an exact authority generation and an idempotency key. Nothing about the transport carries authority by itself.
  • The daemon holds one websocket subscription to the pending commands addressed to its own device. A change wakes the sync cycle immediately, so a steer or decline from another device applies in well under a second instead of waiting out the poll interval (one second while another device is present or a turn is in flight, fifteen seconds otherwise). If the socket fails, the daemon falls back to polling with a doubling backoff capped at thirty seconds.
  • Remote authority is deliberately narrow. Command, permission, and file-change requests can only be declined remotely. answer exists only for a complete, non-secret, closed-choice question set whose provider adapter proves every decision-relevant field crossed without sanitization. Free-text answers, “Other” responses, and MCP form answers stay on the execution machine.

That last restriction is easy to underrate. A control channel that can answer any question is a control channel that can approve anything; bounding remote answers to closed-choice sets keeps the powerful operations where the credentials live. The projection itself enforces the same posture: it never carries exact command text, affected paths or diffs, requested permission values, an MCP server name, protected answers, or a secret question’s text. The remote surface is designed so that even a fully compromised projection cannot leak the material an approval decision would need.

choosing the verb

Goal Verb What it commits to
Add work after the current turn queue Delivery order; survives restart
Change the turn in flight steer Provider must admit in-turn input
Talk to an idle session send Starts a new turn
Let execution decide send --or-steer Resolved at claim time
End the turn stop Provider interrupt path
Change provider or tier switch, preset A heavier, evidence-bound move

The honest limits: steer is only as real as the provider’s support for it (absent on Devin today), a steer that lands late in a turn may affect less than you hoped, and none of these verbs is a rollback. If the turn already ran the wrong command, the record of that is in the transcript; the next decision is a new command, not an edit to history. Interventions also stay inside the same custody and command discipline as everything else: a steer from a device that lost its lease is rejected like any other stale write, and a queued message is a durable record the custodian drains, not a signal sent into the void.

sources

  • oompa: the send|queue|steer surface in src/cli/parser.ts, the Codex turn/steer registration in src/codex/protocol.ts, and the remote command union in src/cloud/payloads.ts.
  • Oompa Claude provider notes: the mid-turn second user line on the input stream.
  • Oompa Devin provider notes: why steer refuses an active ACP turn.
  • Oompa hosted sync: the push-wake subscription and the remote answer boundary.