Concepts

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.

ActionRouteEffect
SteerPOST /agents/:id/steerRedirects the current turn, without restarting it.
PausePOST /agents/:id/pauseFreezes the agent's container in place.
ResumePOST /agents/:id/resumeThaws a paused container; work continues where it stopped.
StopPOST /agents/:id/stopAborts 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, stop it.

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 isdeliveredAsDelivery
runningsteerInto its current turn, after its running tool call.
idleturnAs a new turn.
pausedqueuedHeld 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

StatusWhen
404No such agent.
409The agent isn't live (not provisioned yet, or its sandbox is gone).
502The 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/resume

Pause 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/:id omits sessionStats rather 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 spawning until 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 on GET /agents/:id. It's operator for the agent you named, and cascade for a descendant the subtree pause reached. A member that was already paused keeps its own pausedBy and is reported skipped.
  • New and in-flight children:
    • A child that hasn't forked yet (still waiting on waitFor, or on its paused parent) is reported applied as 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.
  • 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 waitFor or 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_3f9a1c20

This aborts and closes every live agent in the run. The run's ledger is kept.