Docs for SCORPIOX CODE @ 94275ad

Sending Messages to a Running Agent

An agent that is in the middle of a turn is not frozen. You can still reach it — stop it, steer it, or leave a note for it to read when it is done. SCORPIOX CODE gives you one shared submission path for all of it, and it works the same whether you are typing at the keyboard, dropping a file on disk, calling the API, or embedding the agent in your own application.

This page covers the three submit modes, exactly how each behaves in the terminal, the drop-in session inbox, remote control through SCORPIOX BOT, and the public embedding API.

Docs for SCORPIOX CODE @ 94275ad.

The three submit modes

Every message you send to a running agent is classified as one of three modes. The mode is not a transport choice — it is a timing choice: when should the agent read this.

Mode When the agent is idle When the agent is busy Use it for
auto Starts a new turn. Behaves like interrupt. The default. "Just send it; do the sensible thing."
interrupt Starts a new turn. Injected into the current run — the agent reads it at the next tool-call boundary, mid-turn. Steering: "keep going, but now do X instead."
queue Starts a new turn. Stashed and held. Delivered as a fresh run once the current turn finishes. "Don't bother it now — run this next."

Two facts fall out of the table immediately:

interrupt messages accumulate. If you interrupt twice before the agent reaches a tool-call boundary, both notes are joined and delivered together at the next boundary. queue messages likewise accumulate — every queued message is joined and sent as one follow-up run when the turn ends.

In the TUI

The terminal gives you the three modes with two keys and one prefix.

Enter

Pressing Enter submits with auto. So:

There is no "are you sure the agent is busy?" prompt. Enter-while-busy is a feature, not a mistake — it is the fastest way to redirect a running turn.

The ~ prefix: queue it

If you type a message while the agent is busy and you want it to wait its turn rather than cut in, prefix it with a tilde:

~ once this test passes, update the changelog

The leading ~ (and any spaces after it) are stripped, and the remainder is submitted as queue. The agent keeps running its current turn, and when that turn ends, SCORPIOX CODE sends the queued text as a fresh run. You will see a [Queued N] … line confirming it is waiting, and a [Sending queued message(s)] line when it is finally dispatched.

Queue multiple notes by sending several ~-prefixed messages in a row — they are all held and delivered together when the turn completes.

ESC: pure cancel

The ESC key is not a submit mode. Pressing ESC while the agent is running cancels the agentic loop entirely — the turn stops, the agent returns to the prompt, and nothing queued or interrupted is sent as a replacement. Use it to stop, not to say.

You want… In the TUI
Stop it dead, say nothing ESC
Keep it going, but redirect Just type and press Enter (interrupt)
Let it finish, then do this Type ~ your note and press Enter (queue)

The drop-in session inbox

The TUI, the API, and any external program all share one other input channel: a folder on disk. Every session has an inbox directory, and the agent polls it as part of its normal loop.

.scorpiox/sessions/<session-id>/inbox/
    <stem>.json          ← a message the agent has not read yet
    <stem>.png           ← optional image sidecar (same stem)
    done/
        <stem>.json      ← messages the agent has already consumed

A message is a small JSON file. The agent looks for the text field (or content, the accepted alias) and an optional mode field:

{ "mode": "queue", "text": "Once the build is green, run the full test suite." }

mode accepts auto, interrupt, or queue. If you omit it, it defaults to auto.

The handshake: write .tmp, then rename

The inbox is a coordination point between two processes, so the delivery is made atomic. You do not write <stem>.json directly. You write the fully-formed message to a temporary name and rename it into place:

<stem>.json.tmp   →   <stem>.json

The agent only ever sees complete files, never a half-written one. Use any stem you like — a timestamp, a sequence number, a UUID — as long as it is a safe filename (letters, digits, _, -, .). The agent sorts pending stems and processes one per poll pass, in name order, so the stem doubles as a priority/sort key.

Images

A message can carry an image. Put an image file in the inbox with the same stem as its JSON and one of these extensions: png, jpg, jpeg, gif, webp. The agent reads it in alongside the text. There is a hard cap — an image larger than about 8 MB is ignored, and a JSON larger than about 64 KB is skipped — so keep payloads in the message, not in the file.

Consumption

Once the agent has read a message, it moves the file (and any image sidecar) into inbox/done/. The done/ folder is your audit trail of what the agent has actually acted on. An empty inbox/ is a no-op, so it is safe to point the poller at a session that is about to start.

The SCORPIOX_INBOX gate

The whole inbox channel is behind a single config flag, SCORPIOX_INBOX, which is on by default. Set it to 0 to disable polling entirely. As with every other setting, the value follows the standard configuration cascade — built-in default, then global, user, project, profile, then the real environment variable. See Configuration and Profiles for the cascade and where each file lives.

Because the inbox is just files, it is the most portable input channel: a build script, a cron job, another machine, or another agent can reach a running session by dropping one file.

From SCORPIOX BOT

SCORPIOX BOT is the layer that drives a session without a keyboard. To send a message to a session that is already alive, you hit the same inbox over HTTP:

POST /inbox?id=<session-id>&mode=auto
Content-Type: text/plain

Fix the null check in auth.c and run the tests

The mode query parameter is the same auto / interrupt / queue set as everywhere else. The body is plain text — the SCORPIO BOT layer writes it into the session inbox for you, so the atomic-rename handshake is handled for you rather than something you have to do by hand. A tilde prefix on the body is read as queue, which is the same ~ shorthand the TUI uses.

The write is atomic, and the route waits up to two seconds for the agent to actually consume the message before it answers, so the response tells you whether delivery happened:

{ "ok": true, "inbox": true, "stem": "1759000420_000000123", "mode": "auto", "session": "fix_auth", "consumed": true }

"consumed": false means written, not yet picked up — not a failure. The agent was mid-turn; the file is in the inbox and will be read at the next poll pass. For the full remote-control surface — session dispatch, the live terminal stream, escape/interrupt keystrokes, and the fleet dashboard — see Remote Agent Control and Fleet Management with SCORPIOX BOT.

The embedding API

When you embed the agent in your own application through the public DLL, the same three modes are three exported functions. These are the public entry points — you call them from your host (C# P/Invoke, Swift, or any FFI host) and the agent handles the rest.

Export What it does
sx_interrupt() Cancels the agent immediately — the programmatic equivalent of pressing ESC in the TUI.
sx_interrupt_message(message) Injects a message into the running turn. The agent reads it at the next tool-call boundary — a steer, not a stop. Returns -1 if the agent is not busy.
sx_enqueue(message) Queues a message for after the current turn. If the agent is already idle, it is sent immediately as a new turn.
[DllImport("sx.dll")] static extern int sx_interrupt();
[DllImport("sx.dll")] static extern int sx_interrupt_message(string message);
[DllImport("sx.dll")] static extern int sx_enqueue(string message);

sx_interrupt() and sx_interrupt_message() are the two halves of steering: the first says stop, the second says stop and say this first. sx_enqueue() is the "do this next" path — the same end-of-turn delivery the TUI's ~ prefix uses.

How this compares to other harnesses

The question every agent harness has to answer is: what happens to a message the user sends while the agent is already working? The honest answers differ in one dimension — where the waiting message lives.

OpenCode v2

OpenCode's v2 model is the most developed of the public alternatives. It separates steer (a note the agent should fold into the current turn) from queue (a note to run after the current turn), and it does not keep that state in volatile memory. Queued and steered messages are written into a durable, database-backed queue, and delivery is coordinated by a leader / queue-management layer (leader-q) that owns which message goes out and when. That coordination survives process restarts and lets a client inspect or mutate the pending queue. For the "stop it" case, OpenCode exposes a dedicated interrupt key that halts the active turn.

SCORPIOX CODE

SCORPIOX CODE takes the opposite trade. The interrupt and queue state is in-memory: two small, process-local buffers — one for the next tool-call boundary, one for end-of-turn — drained by the agent's own loop. There is no separate coordinator process and no database. What you get in exchange:

The consequence of the trade: SCORPIOX CODE's live interrupt/queue state is volatile — it lives as long as the process. If the process dies, in-flight notes in the buffers are lost, but anything already written to inbox/ is not, because the filesystem is the source of truth for cross-process delivery. OpenCode's database-backed queue makes the live state durable at the cost of a persistent store and a leader to manage it.

Dimension OpenCode v2 SCORPIOX CODE
Where waiting messages live Durable database-backed queue In-memory buffers + filesystem inbox
Coordination Leader / queue-management layer (leader-q) The agent's own loop polls buffers + inbox
Cross-process delivery Via the durable queue Via the inbox/ drop-in folder
"Stop it" Dedicated interrupt key ESC in the TUI, sx_interrupt() in the API
Durability of live notes Survives restart (database) Lost if the process dies; inbox files survive
Per-surface queues Central queue None — one shared submit path

Choosing a mode