> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Runs

Start a routine run, wait for it or follow its steps live, send signals to waiting steps, cancel it, and read its steps and history.

A run is one execution of a routine version. Runs are [jobs](/concepts/jobs), so you can wait on them, stream them or get a webhook.

## Start a run

**curl**

```bash
    curl -s "https://beta-api.b3os.org/v1/routines/nightly-digest/runs?wait=30" \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"input": {"topic": "pricing"}, "durability": "ephemeral"}'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    // .wait() sends ?wait on the create, then follows the run's events until it ends
    const done = await b3os.routines
      .createRun("nightly-digest", { input: { topic: "pricing" }, durability: "ephemeral" })
      .wait();
    console.log(done.status, done.result, done.timing);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    done = b3os.routines.create_run(
        "nightly-digest",
        {"input": {"topic": "pricing"}, "durability": "ephemeral"},
        wait=True,  # sends ?wait on the create, then follows the run's events until it ends
    )
    print(done["status"], done.get("result"), done.get("timing"))
    ```
  **MCP**

```text
    Run the nightly digest about pricing and give me the result.
    ```

    The agent calls `routine_run` with `routine: "nightly-digest"` and `input: {"topic": "pricing"}`. On the remote MCP server the tool also takes `durability`, `version` and `wait`; on the local one it waits up to `wait_s` seconds and returns the output, or the run ID to pass back as `job_id`.

| Field | Meaning |
|---|---|
| `input` | Validated against the routine's `inputs` schema; its defaults are applied |
| `version` | Run this version (`ver_` ID or number) instead of the active one |
| `durability` | Override the routine's default: `ephemeral`, `standard` or `strict` |
| `webhook` | Where to post the run's events |

With `?wait=` the response is the finished run if it ends in time. An `ephemeral` run that finishes within the wait never touches the disk, which suits a routine an agent calls like a tool. Send `Accept: text/event-stream` to follow it live instead.

## The run

```json
{
  "id": "run_01m5t5fr06dx6azc42f4wbzf5s",
  "object": "routine_run",
  "status": "succeeded",
  "routine_id": "rtn_01m5t6vhkw4a8fa4tb5w4ffz37",
  "version_number": 2,
  "durability": "ephemeral",
  "trigger": { "type": "api" },
  "input": { "topic": "pricing" },
  "result": { "summary": "Prices did not change today." },
  "steps": [
    { "id": "page", "uses": "vision/scrape@1", "status": "succeeded", "attempt": 1, "duration_ms": 91.2 },
    { "id": "summary", "uses": "infer/chat@1", "status": "succeeded", "attempt": 1, "duration_ms": 1218.4 }
  ],
  "progress": { "completed": 2, "total": 2, "unit": "steps" }
}
```

This example is abridged and its values are illustrative. `trigger` says how the run started: `api`, `cron`, `interval`, `webhook`, `action`, or `routine` for a run started by another routine. `steps` covers the top-level steps; `GET /v1/routines/runs/{run_id}/steps` lists every step execution in start order, with each map item (`item_index`), each retry (`attempt`), and their outputs and errors.

## Follow it live

`GET /v1/routines/runs/{run_id}/events` streams the lifecycle events plus:

| Event | When |
|---|---|
| `step.started` | A step begins |
| `step.retrying` | A step failed and will try again |
| `step.waiting` | A `wait` step is waiting for a signal or a time |
| `step.succeeded`, `step.failed`, `step.skipped` | A step ends |
| `run.log` | A `core/log@1` step wrote a line |

Events are kept with the run's history, so you can reconnect with `Last-Event-ID` and pick up where you left off.

## Signals

A `wait` step with a `signal` name waits for you to send that signal:

```bash
curl -s "https://beta-api.b3os.org/v1/routines/runs/$RUN_ID/signal" \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "approval", "payload": {"approved": true, "by": "ops@example.com"}}'
```

The waiting step completes with the `payload` as its outputs. A signal sent before its step starts waiting is buffered, up to 16 per name, and consumed in order. Signaling a run that already finished, or whose buffer for that name is full, fails with [`run_not_waiting`](/errors#run_not_waiting).

## Cancel

`POST /v1/routines/runs/{run_id}/cancel` tells in-flight steps to stop. Steps whose effects already happened are not undone. The run ends `canceled`, and steps that have not started are skipped unless their `if` calls `always()`, which is how a cleanup step still runs.

## History and versions

Runs are kept for 30 days. `GET /v1/routines/{routine}/runs` lists a routine's runs, newest first, filterable by status. Each run pins the version it started with, so publishing a new version or rolling back never changes a run in progress. `GET /v1/routines/{routine}/versions` lists the versions, and `GET .../versions/{version}` returns one with its full definition.

## Pause a routine

`PATCH /v1/routines/{routine}` with `{"paused": true}` stops all of its triggers; runs you start through the API still work. `{"paused": false}` resumes them, except for a routine paused because its publishing key was revoked, which needs a new version published with a valid key.