Runs & observability
Every run is a permanent record. While it's in flight you can watch it live; after it ends the same timeline replays, with its phases, agent turns, tool calls, output, artifacts, and token and cost totals. This page is the model behind that: what a run is, the event log it streams, and the ways to read it.
The run lifecycle
A run is one execution of a workflow. Its status moves forward through a small set of states:
queued is waiting for a runner, pending is a claimed runner starting up. A run that crashesrestarts from the top rather than failing outright, so a transient blip doesn't end it; each restart increments context.attempt, so the program knows which attempt it is on. Cancel a run from the dashboard, boardwalk cancel <runId>, or POST /v1/runs/:id/cancel; re-run one with the same input from the Retry button or POST /v1/runs/:id/retry. The three suspend states cover a long sleep, a human-input gate waiting on a person, and a running workflows.call() child.
The event log
A run emits a stream of events as it executes: lifecycle transitions, the phase() markers your program sets, each agent turn and the tools it calls, anything the program logs, and the value run returned. That stream is the run log, stored durably and replayable. Read a snapshot of it over REST for a first paint:
GET /v1/runs/{runId}/events
// → { "events": [ { "cursor": 1, "type": "...", "data": { ... } }, ... ], "done": false }Each event carries a monotonic cursor; pass the last one you saw back as ?since=to read only what's new, which is how a resumed stream avoids replaying the whole log.
Channels
Every event belongs to exactly one channel, so you can ask for just the altitude you want:
| Channel | Carries |
|---|---|
lifecycle | Status transitions: queued, running, terminal. |
phase | The phase() markers your program sets, for grouping the timeline. |
output | The value runreturned: the run's result. |
log | Program stdout and stderr. |
agent | Agent turns, streamed text, reasoning, and every tool call and result. |
The default view is the quiet trio (lifecycle, phase, output): enough to follow what a run is doing without the firehose. Ask for agent and log when you want to see the model think and the tools fire.
Watching a run
The same log, four ways to read it:
- Dashboard: the run page live-tails by default and replays afterward, with the channels as toggles.
- CLI:
boardwalk runs <id> --logsprints a snapshot;--followlive-tails until the run ends. Add--verboseor--stream agent,logto widen the channels.boardwalk runwaits and prints the run's output when it finishes. - API:
GET /v1/runs/:id/eventsfor a snapshot, or the live stream for a tail. - MCP:
get_run_outputreturns the same snapshot to a connected agent client.
Live tail
The live view is Server-Sent Events: events arrive as they happen, and a reconnect resumes from the last cursor rather than from the top, so a dropped connection never loses or duplicates the timeline. The CLI's --follow and the dashboard both use it; you rarely build a poll loop, but a conditional GET on the run row (see polling) is there if you need one.
The inbound delivery log
Not every inbound delivery produces a run. The Inbound deliveries log on the Connections page records each delivery to a provider connection and its outcome, including why one produced no run (an unmatched event, a filtered scope), and any delivery can be replayed. The raw payload stays on the row for debugging.
Output & artifacts
A run's result is whatever its run function returned: a single JSON-serializable value, shown on the run page, sent to watchers, and returned to a parent that called the workflow with workflows.call(). When the function declares a return type, the value is validated against the derived output schema before it is persisted, so the result always matches the declared contract. For files a run produces (a report, an image, a diff), write an artifactinstead: artifacts are listed on the run, downloadable with a signed URL, and outlive the run's working directory.
Watches & notifications
A watchsubscribes you to a run's or a workflow's terminal outcome, so you hear when it finishes without polling. Set one from the dashboard, or over the API:
PUT /v1/runs/{runId}/watch # this run's outcome
PUT /v1/workflows/{id}/watch # every run of this workflowManage all of yours under GET /v1/me/notifications. A workflow can also notify on its own terms by declaring notifications in its workflow.jsoncdescriptor (email or webhook on completion, failure, cancellation, or a breached budget), which is the right tool when the workflow's author, not a watcher, owns who gets told.
Usage metrics
Across runs, the usage view rolls up what your org spent: runs, compute minutes, tokens in and out, credit, the share of work that ran without a human, and the cache-hit rate. Read it for the whole org or one workflow:
boardwalk usage --org acme --days 30
GET /v1/orgs/acme/usage?days=30
GET /v1/orgs/acme/workflows/{id}/usage?days=30Per-run token and cost totals sit on the run row itself, so the number you see on a run is the number that was metered. See Pricing for how a run is priced.