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.
yamlname: nightly-digestdescription: Scrape the news page and summarize ittriggers: - type: cron id: nightly cron: "0 0 6 * * *" # seconds first: 06:00:00 every day tz: America/Los_Angeles catch_up: lateststeps: - 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/modelsoutput: summary: ${{ steps.summary.outputs.choices[0].message.content }}
Publish and run
bash# Publish: creates the routine on first use, a new version after thatcurl -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 resultcurl -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": {}}'
tsimport { readFile } from "node:fs/promises";import { B3OS } from "@b3os/sdk";const b3os = new B3OS();// A string is sent as YAML; an object as JSONawait 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);
pythonfrom b3os import B3OSb3os = 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 JSONdone = b3os.routines.create_run("nightly-digest", {"input": {}}, wait=True)print(done["status"], done.get("result"))
An agent connected to the B3OS MCP server runs a published routine with the routine_run tool:
textRun 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 | 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 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
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.