Steering and pausing
Redirect a running turn, freeze and thaw an agent's container, and stop agents or whole runs.
alineod has four ways to intervene in a live agent. By default each acts on one named agent. Pause, resume
and stop take "scope": "subtree" to act on the agent and everything under it. Steer's subtree form goes to the
parent only, so it can redirect its own children.
| Action | Route | Effect |
|---|---|---|
| Steer | POST /agents/:id/steer | Redirects the current turn, without restarting it. |
| Pause | POST /agents/:id/pause | Freezes the agent's container in place. |
| Resume | POST /agents/:id/resume | Thaws a paused container; work continues where it stopped. |
| Stop | POST /agents/:id/stop | Aborts the turn and closes the sandbox. |
Steer
curl -s -X POST localhost:4600/agents/a_c01d9e3a/steer \
-H 'content-type: application/json' \
-d '{"message": "Skip the tests directory — focus only on src/auth."}'The route returns 202 once the agent's harness has accepted the message, and an agent.steered event is
recorded on the run stream.
When the message lands
A steer message is delivered after the tool calls in the current turn finish and before the model's next
call. It does not interrupt a tool call that is already running — a long build or test run completes first.
This is the harness's own steering behaviour (Pi's steer RPC), which alineod passes through unchanged.
- To add work after the current turn instead, send a new turn with
POST /agents/:id/prompt. - To cut an agent off immediately,
stopit.
Steering a subtree
Steer never broadcasts: the same words rarely fit a parent and its workers. To redirect an agent and the work
it has delegated, steer the parent with "scope": "subtree":
curl -s -X POST localhost:4600/agents/a_7b2e44d1/steer \
-H 'content-type: application/json' \
-d '{"message": "Security review is the priority now; drop performance.", "scope": "subtree"}'The parent gets one message:
- your text;
- a table of its direct children, showing each one's
agentId,sandboxId, spec, state, outcome and what it was asked to do; - instructions to re-plan and act on each child.
It decides what each child should now do (redirect it, stop it, leave it, or fork new ones), and writes each child a message for that child's own task. The children receive nothing directly.
How the message reaches the parent depends on its state. The response says which happened:
| Parent is | deliveredAs | Delivery |
|---|---|---|
| running | steer | Into its current turn, after its running tool call. |
| idle | turn | As a new turn. |
| paused | queued | Held in its inbox and delivered as soon as it's resumed. |
The parent acts on its children with the alineo CLI inside its sandbox, addressing each child by sandboxId:
alineo steer <child-sandbox-id> "Drop the performance section; the security review is the priority now."See alineo steer.
The parent's own actions aren't tracked by alineod yet
The CLI talks to OpenSandbox directly. So the steers and stops the parent issues aren't in alineod's event stream, and children it forks itself aren't part of alineod's tree: subtree pause, stop and steer won't reach them. Routing the CLI through alineod needs per-agent credentials, which are planned.
Errors
| Status | When |
|---|---|
404 | No such agent. |
409 | The agent isn't live (not provisioned yet, or its sandbox is gone). |
502 | The agent's harness rejected or didn't accept the message. |
Pause and resume
curl -s -X POST localhost:4600/agents/a_c01d9e3a/pause
# … later
curl -s -X POST localhost:4600/agents/a_c01d9e3a/resumePause freezes the agent's container — the harness process and any tool it's running are suspended. Resume thaws
it and it carries on from exactly where it stopped. A turn's stream simply goes quiet while paused; it doesn't
error. Each transition emits agent.state_changed with reason: "operator".
- Pausing an already-paused agent is a no-op.
- Resuming an agent that isn't paused returns
409. - While paused,
GET /agents/:idomitssessionStatsrather than waiting on the frozen harness. - Resume returns the agent to the state it had before the pause.
- Spawning a child under a paused agent is accepted: the child waits in
spawninguntil the parent resumes, then forks. If the parent is stopped instead, the child fails. - If alineod restarts while an agent is paused, the agent stays paused and resumes normally; a turn that was running when it was paused is followed to completion after the resume.
Pausing a subtree
Pass scope: "subtree" to pause or resume an agent together with everything under it:
curl -s -X POST localhost:4600/agents/a_c01d9e3a/pause \
-H 'content-type: application/json' -d '{"scope": "subtree"}'
# … later
curl -s -X POST localhost:4600/agents/a_c01d9e3a/resume \
-H 'content-type: application/json' -d '{"scope": "subtree"}'The response lists every member and what happened to it:
{
"asOf": 42,
"results": [
{ "agentId": "a_c01d9e3a", "outcome": "applied" },
{ "agentId": "a_5b1e0f22", "outcome": "skipped", "reason": "already-paused" },
{
"agentId": "a_9d7c3b10",
"outcome": "applied",
"reason": "pending: pauses as soon as it forks"
}
]
}- Order: pause goes parents first, so a parent stops spawning before its children freeze. Resume goes children first, and the agent you named wakes last. Members at the same depth are handled in parallel.
- Best-effort: one member failing (
"failed", with a reason) doesn't undo or block the others. - Who paused it: each paused agent records it in
pausedBy, which shows onGET /agents/:id. It'soperatorfor the agent you named, andcascadefor a descendant the subtree pause reached. A member that was already paused keeps its ownpausedByand is reportedskipped. - New and in-flight children:
- A child that hasn't forked yet (still waiting on
waitFor, or on its paused parent) is reportedappliedas pending. It joins the pause as soon as it forks. - A child spawned under a paused ancestor does the same: it forks, pauses immediately, and runs its first prompt only once it's resumed.
- A child that hasn't forked yet (still waiting on
- Membership drift: after the sweep, the subtree is resolved once more, and anything spawned during the operation gets the same treatment.
A scope of "agent" (the default, or no body at all) acts on the named agent only, exactly as above.
Pauses don't end turns
A paused turn's stream goes quiet, and a pause can outlast ALINEOD_PROMPT_INACTIVITY_MS (default
180 s). alineod then follows the turn by polling the agent instead of reading its stream, and
paused time doesn't count toward ALINEOD_TURN_MAX_MS, so the turn finishes normally after resume
with its full result.
Runtime differences
On Docker-backed OpenSandbox, pause is a true in-place freeze. On Kubernetes, pause and resume are snapshot-based and in-memory state does not survive.
Stop
curl -s -X POST localhost:4600/agents/a_c01d9e3a/stop \
-H 'content-type: application/json' -d '{"mode": "abort"}'Stop aborts the agent's current turn, closes its sandbox, and ends it with outcome aborted. Its handle settles,
so anything waiting on it is released. The body is optional. mode accepts abort (default) or drain; drain
currently behaves the same as abort.
- A finished agent keeps its sandbox open so it can be prompted again or spawned from. Stopping it closes that
sandbox and emits
agent.released, and its recorded outcome, e.g.success, is kept. - A paused agent is closed directly, without an abort, since its frozen harness can't answer.
- A child that hasn't forked yet, one still waiting on
waitForor on a paused parent, is cancelled: it never forks.
Stopping a subtree
curl -s -X POST localhost:4600/agents/a_7b2e44d1/stop \
-H 'content-type: application/json' -d '{"scope": "subtree"}'This stops the agent and every descendant, leaves first, so no parent is torn down under a child still using
it. Like subtree pause, it returns one result per member: applied, skipped (already ended and closed), or
failed with a reason.
To stop everything at once, delete the run:
curl -s -X DELETE localhost:4600/runs/r_3f9a1c20This aborts and closes every live agent in the run. The run's ledger is kept.