Docs for SCORPIOX CODE @ 77c49df

Scheduled Callbacks & Autonomous Agent Loops

SCORPIOX CODE can keep working after you stop typing. A scheduled callback is a timer the agent sets for itself: when the delay runs out, the timer's message lands in the conversation exactly like something you typed, and the agent loop wakes up and starts a fresh turn — no keystroke required. That one mechanism is what turns a single request into an autonomous loop: poll a deployment until it goes green, re-check a flaky upload, babysit a long build, or grind through a self-directed work queue for hours.

You meet callbacks from two sides:

Docs for SCORPIOX CODE @ 77c49df.

The whole idea in one line: SetCallback stores a message plus a delay (1–3600 s) and a repeat count in one of eight timer slots; when a timer comes due and the agent is idle, its message re-enters the conversation as a new turn and the timer re-arms itself — which is how SCORPIOX CODE keeps a session running on its own.


What a callback actually is

Think of it as a self-addressed reminder. The agent writes down what to check next and when to check it, then goes idle. When the deadline arrives, the message is injected as a new user turn and the agent wakes up, does that unit of work, and — if the job is not finished — schedules the next reminder. Because the agent picks both the message and the cadence, it can chain steps indefinitely without a human in the loop.

PropertyValue
Active timersUp to 8 at once (slot IDs 0–7). A ninth schedule attempt fails.
Delay per fire1–3600 seconds.
Message sizeUp to 2048 characters.
Repeat countHow many times the timer fires before it retires itself. Default 20, -1 for forever.
Re-armingAfter each fire the timer re-arms with the same delay, so it ticks on a steady cadence.
When it can fireOnly while the agent is idle and callbacks are not paused.

Three properties are worth internalizing early:


The SetCallback tool

SetCallback is a built-in tool, enabled by default. It is exposed to the model like every other tool, and it does three things through one parameter: action.

ParameterTypeRequiredDescription
actionstringyesset, list, or cancel. Omitted means set.
messagestringfor setThe text delivered back into the conversation when the timer fires.
delay_secondsintegerfor setSeconds to wait before firing. Must be between 1 and 3600.
repeat_countintegernoHow many times to fire before the timer retires. Default 20; -1 fires forever.
callback_idintegerfor cancelWhich slot to cancel. Omit to cancel all.

set — schedule a timer

{
  "action": "set",
  "message": "Check the deploy job; report success or failure, then re-arm if still running.",
  "delay_seconds": 30,
  "repeat_count": 40
}

Success reads like this:

Callback scheduled: fires every 30s, repeats 40 times.

With repeat_count: -1:

Callback scheduled: fires every 30s, repeats infinitely.

Refusals are explicit, so the agent can correct itself:

SituationMessage
No messageError: missing message parameter
Delay out of rangeError: delay_seconds must be between 1 and 3600 (got 7200)
All 8 slots takenError: all callback slots full (max 8)

list — inspect active timers

{ "action": "list" }
Active callbacks (2/8):
  [0] "Check the deploy job; report success or failure, then re-arm if still..." - fires in 0m12s (39 left)
  [3] "Check disk usage on /tmp" - fires in 1m00s (infinite)

Each line carries the slot ID, a preview of the message, the time until the next fire, and how many fires remain. With nothing scheduled it returns No active callbacks.

cancel — stop timers

Cancel one slot by ID:

{ "action": "cancel", "callback_id": 0 }
Cancelled callback slot 0: "Check the deploy job; report success or failure, then r..."

Or clear everything by omitting the ID:

{ "action": "cancel" }
Cancelled 2 callbacks.

Bad IDs fail loudly rather than silently: Error: callback_id 5 out of range (max 7) for a slot that does not exist, and Error: callback slot 3 is not active for one that already fired out or was cancelled.


How firing works

The scheduler is deliberately boring, which is what makes it safe to lean on.

Fires happen when the loop is idle. The terminal UI checks the timer table only when no agent run is in flight. If a deadline arrives while the agent is still working, the fire is deferred about five seconds and you see [Callback] Deferred 5s — agent busy in the transcript. Nothing is lost; the timer simply waits for a free moment.

Overdue timers re-base instead of burst-firing. When an agent run finishes, every timer whose deadline passed during the run is pushed forward by one full delay. A 30-second timer scheduled mid-run does not fire instantly the moment the run ends — it fires a full 30 seconds later. The corrected countdown is written back, so anything watching the session sees the real next-fire time.

Repeats re-arm from the moment they fire. delay_seconds: 30 with repeat_count: 40 is 40 fires spaced 30 seconds apart, counted from each fire. When the count reaches zero the slot retires itself — the last fire still delivers its message.

The fired message is visible as a turn. You see [Callback] Timer fired in the transcript, then the message itself as the next user turn. The agent answers it like any other prompt.

Everything an agent run does still applies. Callback-driven runs poll usage live in the status bar (so a multi-hour auto-pilot round does not freeze the token figures) and handle context compaction the same way an interactive run does — a long self-running session compacts when it reaches the context threshold instead of dying on a context-limit error.


The /callbacks command

/callbacks is your control surface — the way to see and manage timers without asking the agent.

CommandEffect
/callbacksToggle the popup open or closed
/callbacks onShow the popup
/callbacks offHide the popup
/callbacks pauseFreeze every timer — nothing fires until you resume
/callbacks resumeUnfreeze, and re-base overdue timers so nothing fires instantly

The four sub-arguments are offered as you type, so you never have to memorize them.

The popup

The popup is a draggable overlay (grab its title bar to move it, click [X] to close). One line per active timer:

 Callbacks (2 active)
 [0] "Check the deploy job; report s..."  0m12s (38)
 [3] "Check disk usage on /tmp"           1m00s (inf)

Each line shows the slot index, a truncated message preview, the time until the next fire, and the fires remaining (inf when the timer repeats forever). Empty table reads No active callbacks. Pausing changes the title to Callbacks (2 active) PAUSED.

Pausing is not cancelling. pause freezes the firing clock and resume starts it again with the schedule intact; only cancel removes a timer. Pause is session-local — it lives in the running process, so it does not survive a restart.

The status bar

Even with the popup closed, the status bar shows a compact live indicator whenever timers are active: [CB:2 2:14] means two active callbacks, the next one due in 2 minutes 14 seconds. The same numbers appear in the session's stats.json under a callbacks block (active_count, next_fire_ms, remaining_s), which is what scripts and dashboards should read — see Token Usage Observability.


Where timers live

Timer state is stored in the session folder — .scorpiox/sessions/<session>/callbacks.json — as a small JSON document listing every slot with its message, delay, repeat count, next-fire time, and a revision counter that bumps on every change. Three consequences fall out of that:

The embedded-engine form factor (host applications that load the agent as a library) uses the same file, checked once a second by a status thread; a fired timer's message is queued through the exact path a host-supplied message takes.


Recipes

Poll a deployment without burning tokens on a tight loop. Have the agent start the deploy, then schedule delay_seconds: 120, repeat_count: 30 with a message like "Check the deploy status; if it succeeded or failed, cancel this callback and report; otherwise say nothing new." The agent checks once every two minutes instead of hammering the endpoint.

Watchdog a long build. delay_seconds: 600, repeat_count: -1 with "Confirm the build is still progressing; if it stalled, diagnose and cancel this callback." Cancel it when the build is done, or let it run and check the popup.

Auto-pilot a work queue. One timer, delay_seconds: 60, repeat_count: -1, message "Pick the next item from TODO.md, do it, mark it done. If nothing is left, cancel this callback." The agent works one item per round and shuts itself down when the queue empties.

A single-shot nudge. repeat_count: 1 turns any timer into a one-shot: fire once, deliver the message, retire. Useful for "remind me to rerun the tests in 20 minutes."

Related: when the opt-in loop-detection guard is enabled (LOOP_DETECTION=1) and the agent repeats an identical command, the warning it receives explicitly points at SetCallback as the preferred alternative — schedule a check-back instead of polling in a tight loop.


Gotchas