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, so you can wait on them, stream them or get a webhook.
Start a run
bashcurl -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"}'
tsimport { B3OS } from "@b3os/sdk";const b3os = new B3OS();// .wait() sends ?wait on the create, then follows the run's events until it endsconst done = await b3os.routines .createRun("nightly-digest", { input: { topic: "pricing" }, durability: "ephemeral" }) .wait();console.log(done.status, done.result, done.timing);
pythonfrom b3os import B3OSb3os = 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"))
textRun 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:
bashcurl -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.
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.