Format reference
The shape of a .playbook.json document, generated from the shipped OpenPlaybook 1.11 schemas: the envelope, the playbook, its manifest, triggers, the seven step kinds, and guardrails.
The document envelope
An OpenPlaybook file is one JSON object: a format-version envelope around the playbook, plus optional declarations of what the playbook needs (connections, MCP servers, named secrets) and where it came from (provenance).
The playbook
The playbook object carries the work: identity and manifest, plain-English instructions, embedded tool definitions, triggers, the step graph, and guardrails.
Manifest
Identity and policy: the slug and name, the author, the category, the connectors and permissions the playbook requires, and its visibility and pricing tier.
Triggers
A trigger starts a run. The trigger union has 5 kinds:
A schedule trigger carries a cron expression; a webhook trigger names its source and event (and may carry typed filters); manual, dependency, and agent_finding cover operator-started runs, run-after-run chaining, and agent-initiated work. The per-kind fields are in the spec repository.
Steps
A step is one node in the control-flow graph. Control flow is explicit: linear kinds carry a next edge naming the following step; decision steps carry branches. The validator enforces unique step ids, resolvable edges, and an acyclic, fully reachable graph. There are 7 kinds:
agent
One unit of model-driven work. Declares a plain-English purpose, the tools it may call (allowedTools), an optional output schema, an approval policy, and a failure mode. Declares an agent, a role, or both.
tool
A scripted, deterministic tool call: one named tool, a fixed input expression, no model in the loop.
decision
Explicit branching. Each branch carries a `when` expression and a `next` edge; `default` catches the rest. The graph must stay acyclic and fully reachable.
wait
Parks the run until a duration or condition passes. Conforming runtimes treat waits as explicit transitions, never wall-clock polling in a test context.
map
Fan-out over a list: runs an item step (or sub-playbook) per element, with concurrency and item caps.
approval
A human gate. The run holds until an approver decides or the gate times out; an expired gate is terminal, never an implicit approve.
terminator
Ends the run, with a reason. Every path through the graph ends at one.
Guardrails
The run's hard limits. Cost caps are authored in the document's own denomination (cents at the format level); a Level 3 runtime meters in its own denomination on top.
A complete document
This is a real document from the spec's conformance corpus (vector approval-expiry): a drafted email held at an approval gate. The tutorial walks it end to end.
{
"opbVersion": "1.6",
"requires": {
"connections": [],
"mcpServers": [],
"secrets": []
},
"playbook": {
"id": "pvr_D17KJBXF1JN52X4RR9XXQQ8825",
"playbookId": "plb_429B94127Y0TRRXKVC2FQ9RR25",
"opbVersion": "1.6",
"version": "1.0.0",
"status": "published",
"manifest": {
"id": "plb_429B94127Y0TRRXKVC2FQ9RR25",
"slug": "conf-approval-expiry",
"name": "Conformance: approval expiry",
"description": "A held email draft whose gate expires with no decision terminates the run.",
"author": {
"type": "porchops",
"id": "porchops",
"displayName": "PorchOps"
},
"category": "operations",
"requiredConnectors": [],
"requiredPermissions": [
"email.draft"
],
"visibility": "private",
"pricingTier": "free",
"premiumPriceCents": null
},
"instructions": "Draft the apology email, hold it at the gate; if the operator never decides, the gate expires and the run ends.",
"configSchema": {},
"tools": [],
"triggers": [
{
"kind": "webhook",
"source": "payments",
"event": "charge.failed"
}
],
"steps": [
{
"id": "draft",
"kind": "agent",
"role": "support",
"purpose": "Draft a short apology email to the customer email address named in the trigger payload, as a single email_draft artifact.",
"allowedTools": [],
"approvalPolicy": {
"mode": "auto"
},
"failureMode": "halt_run",
"next": "gate"
},
{
"id": "gate",
"kind": "approval",
"approver": "operator",
"timeout": "7d",
"onTimeout": "cancel",
"purpose": "Hold the drafted email for the operator's decision before the run may continue.",
"next": "done"
},
{
"id": "done",
"kind": "terminator",
"reason": "The operator approved (never reached in this vector).",
"purpose": "End the run after approval; unreachable - the gate expires first."
}
],
"guardrails": {
"maxRunDurationSeconds": 300,
"maxLlmCostCents": 0,
"requiresApprovalFor": []
}
}
}