# Steering and pausing
URL: /docs/alineod/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.

| 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 [#steer]

```bash
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 [#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 [#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"`:

```bash
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`:

```bash
alineo steer <child-sandbox-id> "Drop the performance section; the security review is the priority now."
```

See [alineo steer](/docs/cli/commands/steer).

<Callout type="warn" title="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.
</Callout>

### Errors [#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 [#pause-and-resume]

```bash
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 [#pausing-a-subtree]

Pass `scope: "subtree"` to pause or resume an agent together with everything under it:

```bash
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:

```json
{
  "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.

<Callout type="info" title="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.
</Callout>

<Callout type="info" title="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.
</Callout>

## Stop [#stop]

```bash
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 [#stopping-a-subtree]

```bash
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:

```bash
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.
