> ## 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.

# Routines

Durable workflows written like GitHub Actions with CEL expressions, started by API, cron, webhooks or pack triggers, next to the tools they call.

A routine is a workflow you write once and run many times: a list of steps that call B3OS products, HTTP endpoints, marketplace actions and other routines, with expressions between them. Routines run inside the B3OS daemon on the node, so a step that scrapes a page or runs a command in a sandbox calls that product in memory.

```yaml nightly-digest.yaml
name: nightly-digest
description: Scrape the news page and summarize it
triggers:
  - type: cron
    id: nightly
    cron: "0 0 6 * * *"          # seconds first: 06:00:00 every day
    tz: America/Los_Angeles
    catch_up: latest
steps:
  - id: page
    uses: vision/scrape@1
    with:
      url: https://news.example.com
      formats: [markdown]
  - id: summary
    uses: infer/chat@1
    with:
      model: ${{ vars.model }}
      messages:
        - role: user
          content: "Summarize: ${{ steps.page.outputs.markdown }}"
vars:
  model: b3os/your-chat-model     # an ID from GET /v1/infer/models
output:
  summary: ${{ steps.summary.outputs.choices[0].message.content }}
```

## Publish and run

**curl**

```bash
    # Publish: creates the routine on first use, a new version after that
    curl -s -X PUT https://beta-api.b3os.org/v1/routines/nightly-digest \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/yaml" \
      --data-binary @nightly-digest.yaml

    # Run it now and wait up to 30 seconds for the result
    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": {}}'
    ```
  **TypeScript**

```ts
    import { readFile } from "node:fs/promises";
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    // A string is sent as YAML; an object as JSON
    await b3os.routines.publishRoutine("nightly-digest", await readFile("nightly-digest.yaml", "utf8"));
    const done = await b3os.routines.createRun("nightly-digest", { input: {} }).wait();
    console.log(done.status, done.result);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    with open("nightly-digest.yaml") as f:
        b3os.routines.publish_routine("nightly-digest", f.read())  # a str is sent as YAML, a dict as JSON
    done = b3os.routines.create_run("nightly-digest", {"input": {}}, wait=True)
    print(done["status"], done.get("result"))
    ```
  **MCP**

An agent connected to the [B3OS MCP server](/sdks/mcp) runs a published routine with the `routine_run` tool:

    ```text
    Run the nightly-digest routine now and show me the summary.
    ```

    The tool takes the routine's name and an `input` object and waits for the output; if the run is still going when the wait ends, it returns the run ID to ask about later.

The CLI does the same with `b3os routine deploy` and `b3os routine run`; `b3os routine logs` follows a run.

## What a routine can call

| Callable | Does | Effect class |
|---|---|---|
| `vision/scrape@1` | A scrape; `with` is the API body, outputs are the response | read |
| `render/screenshot@1` | A screenshot | read |
| `sandboxes/exec@1` | A command in an existing sandbox (`sandbox`) or a fresh one (`template`) | write |
| `infer/chat@1` | A chat completion on the node's model | read |
| `core/http@1` | An HTTP request through the egress guard | read for `GET` and `HEAD`, otherwise write |
| `core/transform@1`, `core/log@1` | Reshape a value; write a line to the run's event stream | pure |
| `routine/<rtn_id>@<ver_id>` | Another routine, pinned to a version | the strictest of its steps |
| `<namespace>/<pack>/<action>@<version>` | A [marketplace action](/actions) | the action's own |

`map`, `wait` and `sleep_ms` steps fan out over a list, wait for a signal or a time, and pause. [Definitions](/routines/definitions) covers every field and expression.

## Versions

Publishing compiles the definition and stores it as an immutable version (`ver_`) that becomes active at once. Triggers switch to it atomically, and runs already in progress keep the version they started with. `POST /v1/routines/{routine}/rollback` makes an earlier version active again. `PUT` takes an ID or a name and creates the routine when the name is new, so deploying is idempotent.

A version runs as the key that published it, with that key's scopes copied at publish time. If the key is revoked or expires, the routine's triggers pause (`paused_reason: run_as_revoked`) until a new version is published with a valid key.

## Durability

Each run has a durability tier, set in `defaults.durability` or per run:

| Tier | Recorded | Use it for |
|---|---|---|
| `ephemeral` | Nothing on disk; a crash loses the run | Short tool-call routines an agent waits on |
| `standard` (default) | Each effect is durable before the steps after it run | Most workflows |
| `strict` | The intent and the result of every effect step | Steps whose effects must never repeat |

Durable runs are recovered after a process crash or restart by replaying the node's journal. They do not survive the loss of the node.

## Billing and scopes

A step bills its product's unit: a `vision/scrape@1` step bills `vision.page`. Steps with effects and no product meter of their own, such as `core/http@1`, bill `routines.effect_step`. The beta allows 50,000 steps a day per organization.

| Scope | For |
|---|---|
| `routines:write` | Create, publish, validate, import, roll back, pause and delete routines |
| `routines:run` | Start, signal and cancel runs |
| `routines:read` | List and read routines, versions, runs, steps and events |

## Guides

**[Definitions](/routines/definitions)**

Steps, expressions, contexts, map, wait and retries.
  **[Runs](/routines/runs)**

Start, follow, signal and cancel runs.
  **[Triggers](/routines/triggers)**

Cron, interval, signed webhooks and pack triggers.
  **[Limits and FAQ](/routines/limits)**

Caps, retention and common questions.

Moving from the original B3OS workflows? `POST /v1/routines/import` converts a workflow's JSON node tree into a routine definition and reports what did not map.