What Boardwalk is
Boardwalk runs agent workflows: TypeScript or Python programs that call models, run on
schedules or webhooks, and keep a permanent record of every run. A workflow is plain code, so you
author it in your own editor, with your own packages and tests, and ship it with one deploy.
It is the control plane for agent workflows: the place an org builds, versions, triggers, and
runs them, with durability, secrets, budgets, and full run history handled for you. It is code-first,
not a no-code builder and not a chatbot. You write the program; Boardwalk runs it unattended and
keeps the trace.
A workflow is a typed function
A Boardwalk workflow is a run function plus a small descriptor. No YAML, no node editor, no
framework to subclass. Two files: the function does the work, and workflow.jsonc tells the
platform how to deploy it.
src/index.ts:
import { agent, secrets } from "@boardwalk-labs/workflow";
export default async function run(): Promise<string> {
const token = await secrets.get("GITHUB_TOKEN");
const issues = await fetch("https://api.github.com/issues", {
headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.text());
return await agent(`Summarize these open issues for a morning digest:\n${issues}`);
}
workflow.jsonc:
{
"$schema": "https://boardwalk.sh/schemas/workflow.json",
"slug": "morning-digest",
"triggers": [{ "kind": "cron", "expr": "0 9 * * 1-5" }], // when it runs
"permissions": { "secrets": [{ "name": "GITHUB_TOKEN" }] }, // what it may read
}
- The signature is the contract, Lambda-style:
run(input, context) → Output. input is the
trigger's payload — annotate it (run(input: Payment)) and the deploy derives its schema, so the
dashboard's run form and callers know the shape; leave it bare for raw JSON. The return value is
the run's output, persisted and handed to whoever called the run. context (param 1) is
read-only run metadata (ids, trigger, actor, attempt); declare it only when you need it.
- Capabilities are imports —
agent, secrets, sleep, and the rest, like import boto3 in a
Lambda. Nothing that acts is passed into your function.
- The descriptor is policy, read as data: triggers, permissions, budget — what the control plane
must know without running your code. It declares no model and no I/O shapes.
A workflow is a package: the directory holding those files. The code that ships is whatever the
entry imports, plus a skills/ folder and README.md by convention. Python is a peer, not a port:
the same shape is a module-level async def run(input: Lead) -> Score (Pydantic models as the
contract) beside the same workflow.jsonc.
How the pieces fit
agent(prompt, opts?) is the LLM step. It runs an agent loop (a model plus its tools) and
returns the final text, or schema-validated JSON. The engine's coding tools (read, write, edit,
ls, grep, glob, bash, webfetch, and more) are on by default, so a plain agent(prompt) already
works in the run's workspace. Scope them with builtins.
- The workflow names no model; each
agent() call chooses one. model is optional and picked
per call: omit it to use Boardwalk's managed inference, or name one to pin it. provider defaults
to the managed lane, or names your own keys. A workflow that does no model work names no model.
- Durable primitives do everything else, in plain code:
secrets.get, sleep, phase(),
artifacts.write, workflows.call (invoke another workflow and wait; workflows.run fires one
without waiting), and humanInput (pause for a person). parallel([...]) runs independent work
at once; shell runs a command in the workspace; auth.idToken/auth.apiToken mint short-lived
credentials; usage.get() reads live budget state; computer.openBrowser() opens a real in-VM
browser your program sets up and can hand to an agent (see equip-agents).
- Each
agent() can be equipped per call, on top of the default built-in tools, with reusable
skills, inline tools, mcp servers, and persistent memory. See the equip-agents skill.
Where a workflow runs
Deploy it to Boardwalk and the platform runs it — on its schedule, webhook, or API, with durability
and model routing handled for you. Iterate with boardwalk check (validate the package locally),
boardwalk deploy . --org <org> --run (deploy + trigger a real run), and boardwalk runs <id> --logs.
There is no local run mode: unit tests call run(input) directly over installTestHost stubs, and
live execution is a real run, pointed at a dev environment when you're iterating.
A hosted run can also be pinned to your own machine with a self-hosted runner
("runs_on": { "kind": "self-hosted" } in workflow.jsonc). See boardwalk-use-cli.
Gotchas that trip up a newcomer
Get these right before writing code:
- Write plain TypeScript —
Date.now() and Math.random() just work. There is no determinism
gate. On the hosted fleet a sleep or humanInput snapshots the whole machine and resumes the
exact heap, so a wait loses nothing. A crash (or a wait on a substrate without snapshots) restarts
the program from the top, Lambda-style, so make side effects safe to re-run: use idempotent keys
or upserts, and put must-not-repeat work behind workflows.call, which re-attaches to a finished
child instead of running it twice. An already-answered humanInput gate is never re-asked.
- Secrets never reach the model. Your program reads them with
secrets.get (the trusted layer),
and the SDK redacts known secret values from all agent() context. Never console.log a secret,
since run logs are kept.
- A long
sleep is not billed. It releases the machine and resumes later, so idle time is free.
Use it instead of a polling loop.
- Set
budget.max_usd. A runaway agent loop spends real money, and the budget is the backstop.
A breach pauses the run for approval — never a silent kill — and a program can watch its own spend
with usage.get().
Watch the words
The top-level unit you build is a workflow. The word agent means the inner LLM loop it calls
via agent(), not you (the coding agent reading this) and not a Claude Code subagent.
Where to go next
- To author a workflow well (typed I/O, match the model, keep it legible, guardrails, surviving
restarts): use
write-good-workflows.
- To build a workflow that iterates until a goal is reached (find/fix/verify, drain a queue, poll
until healthy, a nightly maintainer): use
write-good-loops.
- To give an
agent() skills, tools, MCP servers, or memory, and structure a multi-file package: use
equip-agents.
- To scaffold, run, validate, deploy, trigger, or inspect a workflow, and for every
boardwalk
command (secrets, environments, inference providers, self-hosted runners): use boardwalk-use-cli.
The full authoring contract (every primitive, the descriptor fields, the run-event format) is in the
@boardwalk-labs/workflow package's SPEC.md.
1---2name: boardwalk-overview3description: Orientation for an agent that is new to Boardwalk: what the platform is and how it fits together, before driving the CLI or writing a workflow. Boardwalk runs agent workflows, which are TypeScript or Python programs (not YAML, a GUI, or a chatbot) that call models on a schedule, webhook, or on demand. Use when a user mentions Boardwalk and the agent lacks the mental model. Points to boardwalk-use-cli (scaffold, run, ship) and write-good-loops (iterating loops).4---56# What Boardwalk is78Boardwalk runs **agent workflows**: TypeScript or Python programs that call models, run on9schedules or webhooks, and keep a permanent record of every run. A workflow is plain code, so you10author it in your own editor, with your own packages and tests, and ship it with one deploy.1112It is **the control plane for agent workflows**: the place an org builds, versions, triggers, and13runs them, with durability, secrets, budgets, and full run history handled for you. It is code-first,14not a no-code builder and not a chatbot. You write the program; Boardwalk runs it unattended and15keeps the trace.1617## A workflow is a typed function1819**A Boardwalk workflow is a `run` function plus a small descriptor.** No YAML, no node editor, no20framework to subclass. Two files: the function does the work, and `workflow.jsonc` tells the21platform how to deploy it.2223`src/index.ts`:2425```ts26import { agent, secrets } from "@boardwalk-labs/workflow";2728export default async function run(): Promise<string> {29 const token = await secrets.get("GITHUB_TOKEN");30 const issues = await fetch("https://api.github.com/issues", {31 headers: { Authorization: `Bearer ${token}` },32 }).then((r) => r.text());3334 return await agent(`Summarize these open issues for a morning digest:\n${issues}`);35}36```3738`workflow.jsonc`:3940```jsonc41{42 "$schema": "https://boardwalk.sh/schemas/workflow.json",43 "slug": "morning-digest",44 "triggers": [{ "kind": "cron", "expr": "0 9 * * 1-5" }], // when it runs45 "permissions": { "secrets": [{ "name": "GITHUB_TOKEN" }] }, // what it may read46}47```4849- **The signature is the contract**, Lambda-style: `run(input, context) → Output`. `input` is the50 trigger's payload — annotate it (`run(input: Payment)`) and the deploy derives its schema, so the51 dashboard's run form and callers know the shape; leave it bare for raw JSON. The **return value is52 the run's output**, persisted and handed to whoever called the run. `context` (param 1) is53 read-only run metadata (ids, trigger, actor, attempt); declare it only when you need it.54- **Capabilities are imports** — `agent`, `secrets`, `sleep`, and the rest, like `import boto3` in a55 Lambda. Nothing that acts is passed into your function.56- **The descriptor is policy, read as data**: triggers, permissions, budget — what the control plane57 must know without running your code. It declares no model and no I/O shapes.5859A workflow is a **package**: the directory holding those files. The code that ships is whatever the60entry imports, plus a `skills/` folder and `README.md` by convention. Python is a peer, not a port:61the same shape is a module-level `async def run(input: Lead) -> Score` (Pydantic models as the62contract) beside the same `workflow.jsonc`.6364## How the pieces fit6566- **`agent(prompt, opts?)` is the LLM step.** It runs an agent loop (a model plus its tools) and67 returns the final text, or `schema`-validated JSON. The engine's coding tools (read, write, edit,68 ls, grep, glob, bash, webfetch, and more) are on by default, so a plain `agent(prompt)` already69 works in the run's workspace. Scope them with `builtins`.70- **The workflow names no model; each `agent()` call chooses one.** `model` is optional and picked71 per call: omit it to use Boardwalk's managed inference, or name one to pin it. `provider` defaults72 to the managed lane, or names your own keys. A workflow that does no model work names no model.73- **Durable primitives do everything else, in plain code:** `secrets.get`, `sleep`, `phase()`,74 `artifacts.write`, `workflows.call` (invoke another workflow and wait; `workflows.run` fires one75 without waiting), and `humanInput` (pause for a person). `parallel([...])` runs independent work76 at once; `shell` runs a command in the workspace; `auth.idToken`/`auth.apiToken` mint short-lived77 credentials; `usage.get()` reads live budget state; `computer.openBrowser()` opens a real in-VM78 browser your program sets up and can hand to an agent (see `equip-agents`).79- **Each `agent()` can be equipped per call**, on top of the default built-in tools, with reusable80 `skills`, inline `tools`, `mcp` servers, and persistent `memory`. See the `equip-agents` skill.8182## Where a workflow runs8384Deploy it to Boardwalk and the platform runs it — on its schedule, webhook, or API, with durability85and model routing handled for you. Iterate with `boardwalk check` (validate the package locally),86`boardwalk deploy . --org <org> --run` (deploy + trigger a real run), and `boardwalk runs <id> --logs`.87There is no local run mode: unit tests call `run(input)` directly over `installTestHost` stubs, and88live execution is a real run, pointed at a dev environment when you're iterating.8990A hosted run can also be pinned to your own machine with a self-hosted runner91(`"runs_on": { "kind": "self-hosted" }` in `workflow.jsonc`). See `boardwalk-use-cli`.9293## Gotchas that trip up a newcomer9495Get these right before writing code:9697- **Write plain TypeScript — `Date.now()` and `Math.random()` just work.** There is no determinism98 gate. On the hosted fleet a `sleep` or `humanInput` snapshots the whole machine and resumes the99 exact heap, so a wait loses nothing. A crash (or a wait on a substrate without snapshots) restarts100 the program from the top, Lambda-style, so make side effects safe to re-run: use idempotent keys101 or upserts, and put must-not-repeat work behind `workflows.call`, which re-attaches to a finished102 child instead of running it twice. An already-answered `humanInput` gate is never re-asked.103- **Secrets never reach the model.** Your program reads them with `secrets.get` (the trusted layer),104 and the SDK redacts known secret values from all `agent()` context. Never `console.log` a secret,105 since run logs are kept.106- **A long `sleep` is not billed.** It releases the machine and resumes later, so idle time is free.107 Use it instead of a polling loop.108- **Set `budget.max_usd`.** A runaway agent loop spends real money, and the budget is the backstop.109 A breach pauses the run for approval — never a silent kill — and a program can watch its own spend110 with `usage.get()`.111112## Watch the words113114The top-level unit you build is a **workflow**. The word **agent** means the inner LLM loop it calls115via `agent()`, not you (the coding agent reading this) and not a Claude Code subagent.116117## Where to go next118119- To author a workflow well (typed I/O, match the model, keep it legible, guardrails, surviving120 restarts): use **`write-good-workflows`**.121- To build a workflow that iterates until a goal is reached (find/fix/verify, drain a queue, poll122 until healthy, a nightly maintainer): use **`write-good-loops`**.123- To give an `agent()` skills, tools, MCP servers, or memory, and structure a multi-file package: use124 **`equip-agents`**.125- To scaffold, run, validate, deploy, trigger, or inspect a workflow, and for every `boardwalk`126 command (secrets, environments, inference providers, self-hosted runners): use **`boardwalk-use-cli`**.127128The full authoring contract (every primitive, the descriptor fields, the run-event format) is in the129`@boardwalk-labs/workflow` package's `SPEC.md`.