Skip to main content

Playbooks

A playbook is a versioned template of nodes; a case fills one version of it for one subject.

Overview​

A playbook declares what has to be known before a goal can be reached: each node holds inputs, and each input is either written by a person or an integration (manual) or read from a field of an earlier node. Those sources are the graph's edges. A case is one subject — a client, an application, a claim — filled against the version the playbook was at when the case was created. Every write to a case is an append-only entry, and each node's state is computed from the entries and executions on every read. A node runs its executor — an agent, tool, decider, workflow or orchestration — as the author of the case's version, on its own once its inputs are set, and again whenever one of them changes.

An orchestration is a pipeline that runs once and ends, and a workflow task holds one state; a case keeps a value per field and answers what is still missing before a node can be reached.

See the Permissions Reference for the IAM action strings for this module.

Data Model​

Playbook​

FieldTypeDescription
idstringPublic identifier (pbk_…)
project_idstringOwning project
namestringHuman-readable name, unique per project
descriptionstring | nullOptional
versionintegerThe definition's version; see Versions
definitionobject{ nodes }, 1 to 200 nodes
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-update timestamp

Node​

FieldTypeDescription
idstringStable across versions. Letters, digits and underscores, starting with a letter or underscore
titlestringHuman-readable label
descriptionstringOptional
kindstringcollect (its output is its inputs), execute (its executor produces the output), confirm (a person confirms its inputs)
finalbooleanA goal of the playbook. Default false
inputsarrayUp to 50 inputs
executorobjectRequired on an execute node, refused on any other: { type, id, input? }, type one of agent, tool, decider, workflow, orchestration; input maps each field of the executor's input to JSON Logic over the node's inputs
transformationsarrayFinal nodes only: { name, format, tool }, a tool that renders the node's output as a file of format; see Exports

Input​

FieldTypeDescription
namestringUnique in its node; same character rules as id
schemaobjectThe JSON Schema a value must satisfy
requiredbooleanThe node cannot be reached while a required input is unset
descriptionstringWhat to ask for; returned with the input when it is missing
sourcestring | objectmanual, or { node_id, field } with field output[.path] or input.<name>[.path] of an earlier node

Case​

FieldTypeDescription
idstringPublic identifier (pbk_case_…)
project_idstringOwning project
playbook_idstringThe playbook
playbook_versionintegerThe version the case was created on and is evaluated against
subjectstringWho or what the case is about
revisionintegerThe case's counter; every entry takes the next value
automation_statusstringpaused while a pause of the case or of its project stands, else running while an execution is queued or runs its executor, else waiting while every execution in flight waits on a task or run it started, else idle
pause_requested_atstring | nullWhen the case was paused
nodesarrayOn a single-case read: every node's id, state, missing, blocked_by, run, inputs and output

Entry​

FieldTypeDescription
idstringPublic identifier (pbk_ent_…)
revisionintegerThe case revision it took
node_idstringThe node's ID
kindstringinput, output, confirmation or rejection
inputstring | nullThe input written, on an input entry
valueanyThe value written
basisobject | nullThe revision of each input an output, confirmation or rejection was decided on
playbook_versionintegerThe version the case was on when it was written
created_by_principal_type / created_by_principal_idstring | nullWho wrote it: user or api_key and its id; null for an output the case wrote
on_behalf_ofobject | nullThe tool call it was written for (On-behalf-of)

Execution​

FieldTypeDescription
idstringPublic identifier (pbk_exe_…)
case_idstringThe case
node_idstringThe node that ran
playbook_versionintegerThe version the case runs on
executorobject | null{ type, id }; null for a collect node
statusstringqueued until a worker claims it, running, then completed or failed
resultstring | nullOnce settled: current, or superseded when the node's inputs changed while it ran
run_idstring | nullThe generation, decision, task or orchestration run it started
inputanyWhat the executor was given
outputanyWhat the executor answered; null until it succeeds
output_revisioninteger | nullThe revision of the output entry it wrote; null when it wrote none
errorobject | nullWhy it failed
created_by_principal_type / created_by_principal_idstring | nullWho asked for the run; null when the case started it
on_behalf_ofobject | nullThe tool call it was written for (On-behalf-of)
finished_atstring | nullISO 8601 settle timestamp

Export​

FieldTypeDescription
idstringPublic identifier (pbk_exp_…)
case_idstringThe case
node_idstringThe final node rendered
revisionintegerThe output or confirmation entry rendered
playbook_versionintegerThe version whose transformation rendered it
transformationstringThe transformation's name
formatstringThe transformation's format
tool_idstringThe tool called
statusstringrunning, then completed or failed
stalebooleanrevision is no longer the node's current output, or the node is no longer done
file_idstring | nullThe file; null until it succeeds, and once the file is deleted
errorobject | nullWhy it failed
created_by_principal_type / created_by_principal_idstring | nullWho asked for the export
on_behalf_ofobject | nullThe tool call it was written for (On-behalf-of)
finished_atstring | nullISO 8601 settle timestamp

Upgrade​

FieldTypeDescription
idstringPublic identifier (pbk_upg_…)
case_idstringThe case
from_versionintegerThe version the case was on
to_versionintegerThe version it moved to
diffobject{ added, removed, changed }: node IDs added and removed, and each node, by node_id, whose executor or inputs changed, every input with its change (added, removed, source, schema, required)
created_by_principal_type / created_by_principal_idstring | nullWho upgraded the case
on_behalf_ofobject | nullThe tool call it was written for (On-behalf-of)

Key Concepts​

Definition checks​

A create or update is refused with 400 PLAYBOOK_VALIDATION_FAILED when the definition holds a duplicate node or input, an input reading a missing node or a field that node does not declare, an execute node without an executor or another kind with one, transformations on a non-final node, two transformations of one name on a node, or a cycle. meta.problems lists every one as a validation problem { code, path, message }: DUPLICATE_NODE, DUPLICATE_INPUT, MISSING_NODE, UNKNOWN_FIELD, EXECUTOR_REQUIRED, EXECUTOR_FORBIDDEN, TRANSFORMATIONS_NOT_FINAL, DUPLICATE_TRANSFORMATION or CYCLE, with path naming the node, input or transformation (definition.nodes[0].inputs[1].source.node_id). A transformation format outside the export formats is 400 VALIDATION_FAILED. POST /api/v1/playbooks/validate answers the same check without writing.

Versions​

version starts at 1 and increments on every write that changes definition; each version's definition is archived. A rename, a new description or a rewrite of the definition the playbook already holds leaves it alone. A case runs on the version it was created on, so editing a playbook never reshapes a case in flight until it is upgraded.

PATCH /api/v1/playbooks/{playbook_id} takes expected_version (or If-Match) and answers 409 VERSION_CONFLICT on a stale one. POST /api/v1/playbooks/{playbook_id}/versions/{version}/restore writes an archived definition back as a new version. A playbook with cases cannot be deleted (409 PLAYBOOK_HAS_CASES).

Node states​

Computed from the case's entries and executions on each read, never stored:

StateWhen
blockedA required input is unset or refused by its schema, or reads a node that is not done
readyEvery required input is set and current, and there is no output yet
runningAn execution is queued or runs its executor
waitingIts execution waits on the workflow task or orchestration run it started
doneThe output exists and every input still has the revision it was produced from
staleThe output exists and an input changed since
failedThe last execution failed on the current inputs
rejectedA confirm node's inputs were rejected and have not changed since

missing names the required inputs a node lacks; blocked_by names the nodes it reads that are not done.

A waiting node names what it waits on in run, read live on every case read and null in every other state: { "type": "task", "id", "state", "automation_status" } for a workflow task, { "type": "orchestration_run", "id", "status" } for a run. A task whose state's dispatch failed with no on_failure reads automation_status: "failed": it waits on a person to move it, and the node settles when it closes.

Writing inputs​

POST /api/v1/playbook-cases/{case_id}/inputs writes a batch of manual inputs, all or none. Each write names the revision of the field it read in expected_revision, null for a field read as unset:

  • a field written since is refused with 409 PLAYBOOK_INPUT_CONFLICT, meta.conflicts naming each field with both revisions, and nothing in the batch is written;
  • an input the case's version does not declare, one that reads another node, or a value its schema refuses is 400 PLAYBOOK_INPUT_INVALID;
  • a value equal to the field's current one writes no revision;
  • a write made with a credential the case's own work runs as is 403 PLAYBOOK_SELF_WRITE (own work).

What a node still needs​

GET /api/v1/playbook-cases/{case_id}/missing takes a node_id and returns the manual inputs still unset or refused upstream of it, grouped in waves: nothing in a later wave can be reached before an earlier one's required inputs are filled. Each carries its schema and description, which is what an agent asks for, and required: an optional input is listed so the agent asks for it too, but it never blocks a node.

The cascade​

Every write that can make a node runnable — a case created, inputs written, an execution settled, a pause lifted — queues an execution for each node that is now ready or stale with every required input set. A confirm node never runs. Queued executions are run by the server's worker; a node reading another runs once that one is done, so a node reading two changed nodes runs once.

A collect node's output is its input values. Any other node starts its executor with the node's input values, mapped through the executor's input when it declares one:

ExecutorStarted asOutput
agentA generation with the input as JSONThe object its output schema asked for, else its text
toolA call, under the project's tool guardrailsThe tool's result
deciderA decisionanswers_by_name
workflowA task whose payload is the inputThe task's payload when it closes
orchestrationA run whose input is the inputThe run's output when it succeeds

When an execution settles, its output is written as an output entry and the nodes reading it run next:

  • a value equal to the node's current output writes no revision, so nothing reading it goes stale and the cascade stops there;
  • an execution whose node's inputs changed while it was queued or running settles as superseded, writes nothing, and the node runs again on the new inputs;
  • a failed executor, a failed decision or orchestration run, or an agent that answers with neither text nor an object fails the execution with its cause in error. The node is failed and is not run again until run by hand;
  • an agent, tool or decider whose server stopped while running it fails as PLAYBOOK_EXECUTION_INTERRUPTED and is never re-run on its own, so a call with side effects runs at most once.

A workflow task or an orchestration run settles its node when the task closes or the run ends. One that ends without an event — a run cancelled or expired — is read again every few minutes and settles the node then.

Running a node​

POST /api/v1/playbook-cases/{case_id}/nodes/{node}/run runs one node now; it is how a failed node is retried, and how a done node is recomputed. Only a ready, stale, failed or done node with every required input set runs; a confirm node, a blocked one, a running one and a waiting one answer 409 PLAYBOOK_NODE_NOT_RUNNABLE, meta carrying its state, missing and blocked_by. A paused case answers 409 PLAYBOOK_CASE_PAUSED.

Without wait the request answers 201 with the execution running and it settles in the background; poll GET /api/v1/playbook-cases/{case_id}/executions/{execution_id}. With wait: true it answers once the executor settles, or once it parks: a workflow task still open, or an orchestration run waiting on a person or a timer, settles the node when it closes or ends. See Sync and Async. GET /api/v1/playbook-cases/{case_id}/executions lists a case's executions, newest first.

Waiting on the cascade​

POST /api/v1/playbook-cases/{case_id}/inputs, POST /api/v1/playbook-cases/{case_id}/nodes/{node}/confirmation and POST /api/v1/playbook-cases/{case_id}/upgrade answer once the write is done; the executions it queues run in the background, and the case reads automation_status: running until they settle. With wait: true the answer waits until every execution the write queued, and every one those queue in turn, has settled or parked on a task or run, and returns the case as it then stands. A paused case answers at once. The wait is bounded by the execution lease (15 minutes); a case still running then answers as it stands. See Sync and Async.

Pausing a case​

POST /api/v1/playbook-cases/{case_id}/pause stops a case starting anything: nothing is queued, and nothing already queued starts. Executions already running settle, and input writes are still accepted. POST /api/v1/playbook-cases/{case_id}/resume lifts the pause and runs what became runnable meanwhile; a case that is not paused answers 409 PLAYBOOK_CASE_NOT_PAUSED. A paused project holds its cases' queued executions the same way: each case reads automation_status: paused with its own pause_requested_at unchanged, and running a node, exporting one or resuming a case answers 409 PROJECT_PAUSED until the project resumes. GET /api/v1/playbook-cases takes automation_status to list the cases running, waiting, paused or idle; repeat it to OR values. A case waiting spends nothing of its own: what runs is the task or run it started, which may wait on a person indefinitely.

Confirming a node​

A confirm node never runs: a person approves or rejects its inputs with POST /api/v1/playbook-cases/{case_id}/nodes/{node}/confirmation. expected_revisions maps each of the node's inputs to the revision read (nodes[].inputs[].revision on the case); an input that moved since is 409 PLAYBOOK_INPUT_CONFLICT and nothing is written.

decisionWritesThen
approveA confirmation entry: the node's input valuesThe node is done with them as its output, and the nodes reading it run
rejectA rejection entry with the reasonThe node is rejected, and its readers blocked, until its inputs change or it is approved

A confirmed node whose inputs change is stale, and holds everything reading it until it is confirmed again. Another node, or a confirm node with a required input unset or reading a node that is not done, is 409 PLAYBOOK_NODE_NOT_CONFIRMABLE.

Writes from a case's own work​

A case refuses input writes, confirmations and upgrades made with the run-as credential of its own work — the case, one of its executions in flight, or the task or orchestration run one started — with 403 PLAYBOOK_SELF_WRITE. An executor's result reaches its case only as the node's output.

Who a node runs as​

A node's executor, and the tool of each of its transformations, runs as the user or API key that wrote the case's playbook version, with that credential's permissions as they stand when it runs; an API key's own policies still bound it. Writing a definition therefore requires the start action of every executor it names — agents:CreateAgentGeneration, tools:CallTool, deciders:CreateDecision, tasks:CreateTask or orchestrations:StartRun — and tools:CallTool on every transformation's tool, and is refused with 403 PLAYBOOK_EXECUTOR_FORBIDDEN otherwise. Each run and each export checks it again, so a permission removed since stops the next one with the same error. A definition with executors or transformations written with a trigger or OAuth token, or a version whose author no longer exists, is 409 PLAYBOOK_AUTHOR_UNAVAILABLE.

A playbook declared in a formation (type: playbook, properties) is written the same way: the version a deploy writes runs as the user or API key that deployed it, and the deploy needs these start actions too. An executor's id and a transformation's tool may be a ref to an agent, tool, decider, workflow or orchestration declared beside it. Cases are never declared: a formation holding a playbook with cases cannot be deleted.

Exports​

POST /api/v1/playbook-cases/{case_id}/nodes/{node}/exports renders one revision of a final node as files, one export per transformation it names in transformations (every one when omitted). Each transformation's tool is called with case_id, subject, node_id, revision and that revision's output, and must answer a file — an http tool with execute.response_mode: base64 (see Tools) — whose content type is its format's:

formatContent type
csvtext/csv
docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
htmltext/html
jpgimage/jpeg
jsonapplication/json
mdtext/markdown
pdfapplication/pdf
pngimage/png
pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
txttext/plain
xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet

The file is kept in the project's files under /.system/playbooks/ and is never deleted by the case. An export never feeds a node: whatever a file shows is in the node's output, so every format of one revision agrees.

  • Without revision, the node's current output is exported, which needs the node done: otherwise 409 PLAYBOOK_NODE_NOT_EXPORTABLE, meta carrying its state. A revision names any earlier output or confirmation of the node (an entry); another is 400 VALIDATION_FAILED.
  • An export is stale once its revision is not the node's current output, or the node is not done.
  • A node without transformations, or a name it does not declare, is 400 VALIDATION_FAILED. A paused case is 409 PLAYBOOK_CASE_PAUSED.
  • A tool that fails or answers no file fails the export with PLAYBOOK_EXPORT_FAILED; a file of another content type with PLAYBOOK_EXPORT_FORMAT_MISMATCH. An export whose server stopped while rendering it fails as PLAYBOOK_EXPORT_INTERRUPTED and is not rendered again on its own.

Without wait the request answers 201 with every export running; poll GET /api/v1/playbook-cases/{case_id}/exports/{export_id}. With wait: true it answers once each has its file_id or its error; a tool call always waits. See Sync and Async. GET /api/v1/playbook-cases/{case_id}/exports lists a case's exports, newest first, optionally for one node_id or transformation.

Upgrading a case​

POST /api/v1/playbook-cases/{case_id}/upgrade moves a case to the playbook's current version, or to the version named, an earlier one included. Values carry over by node ID, and every node is read against the new version:

Change in the new versionEffect on the case
Node addedOpen
Node removedLeaves the structure; its entries stay
An input now reads another fieldThe node is stale, and the cascade runs it again
Input schema changedA value it refuses is missing again
Executor, title or descriptionNone; run the node to recompute it

The cascade then runs what became runnable, unless the case is paused. An execution running answers 409 PLAYBOOK_CASE_BUSY, meta.executions naming each; one queued but not started settles superseded. Each upgrade is recorded as an upgrade, listed by GET /api/v1/playbook-cases/{case_id}/upgrades, newest first; the version the case is already on records nothing. A version the playbook does not have is 400 VALIDATION_FAILED, and an upgrade from the case's own work is 403 PLAYBOOK_SELF_WRITE.

Entries​

GET /api/v1/playbook-cases/{case_id}/entries lists every value written to a case, newest first, optionally for one node_id. Entries are never rewritten; deleting the case deletes them.

Examples​

Create a playbook​

soat create-playbook \
--project-id proj_… \
--name client-pricing \
--definition '{
"nodes": [
{ "id": "revenue", "title": "Annual revenue", "kind": "collect",
"inputs": [{ "name": "amount", "schema": { "type": "number" },
"required": true, "source": "manual" }] },
{ "id": "pricing", "title": "Pricing", "kind": "collect", "final": true,
"inputs": [
{ "name": "revenue", "schema": { "type": "number" }, "required": true,
"source": { "node_id": "revenue", "field": "output.amount" } },
{ "name": "margin", "schema": { "type": "number" }, "required": true,
"source": "manual" }
] }
]
}'

Create a case​

soat create-playbook-case --playbook-id pbk_… --subject 'ACME Ltda'

Ask what a node still needs​

soat get-playbook-case-missing --case-id pbk_case_… --node-id pricing
{
"case_id": "pbk_case_…",
"node_id": "pricing",
"waves": [
[{ "node_id": "revenue", "input": "amount", "schema": { "type": "number" } }],
[{ "node_id": "pricing", "input": "margin", "schema": { "type": "number" } }]
]
}

Write inputs​

soat write-playbook-case-inputs \
--case-id pbk_case_… \
--inputs '[
{ "node_id": "revenue", "name": "amount", "value": 4000000, "expected_revision": null },
{ "node_id": "pricing", "name": "margin", "value": 0.3, "expected_revision": null }
]'

Confirm a node​

soat confirm-playbook-case-node \
--case-id pbk_case_… \
--node approve \
--decision approve \
--expected-revisions '{ "amount": 3 }'

Retry a failed node​

soat run-playbook-case-node --case-id pbk_case_… --node pricing --wait true
{
"id": "pbk_exe_…",
"case_id": "pbk_case_…",
"node_id": "pricing",
"executor": { "type": "tool", "id": "tool_…" },
"status": "completed",
"result": "current",
"output": { "price": 12000 },
"output_revision": 5
}

Export a final node​

soat export-playbook-case-node \
--case-id pbk_case_… \
--node pricing \
--transformations '["proposal"]' \
--wait true
{
"data": [
{
"id": "pbk_exp_…",
"case_id": "pbk_case_…",
"node_id": "pricing",
"revision": 5,
"transformation": "proposal",
"format": "pdf",
"status": "completed",
"stale": false,
"file_id": "file_…"
}
]
}

Upgrade a case​

soat upgrade-playbook-case --case-id pbk_case_…
soat list-playbook-case-upgrades --case-id pbk_case_…

Pause a case​

soat pause-playbook-case --case-id pbk_case_…
soat resume-playbook-case --case-id pbk_case_…

Errors​

CodeStatusWhen
VALIDATION_FAILED400The definition's shape cannot be read, a missing subject, or an unknown node_id on missing or node on a run; on an export, a node without transformations, an undeclared transformation or a revision that is no output of the node
PLAYBOOK_VALIDATION_FAILED400The definition has graph problems; meta.problems lists them
PLAYBOOK_INPUT_INVALID400An input write names an undeclared or non-manual input, or a value its schema refuses
NAME_CONFLICT409Another playbook in the project has the name
VERSION_CONFLICT409expected_version is stale
PLAYBOOK_HAS_CASES409A delete of a playbook that has cases
PLAYBOOK_INPUT_CONFLICT409An input write's expected_revision is not the field's current one
PLAYBOOK_EXECUTOR_FORBIDDEN403The version's author may not start an executor the definition names
PLAYBOOK_AUTHOR_UNAVAILABLE409The version has no author a node can run as
PLAYBOOK_NODE_NOT_RUNNABLE409The node is a confirm node, blocked, running or waiting
PLAYBOOK_CASE_PAUSED409A run of a node in a paused case
PLAYBOOK_CASE_NOT_PAUSED409A resume of a case that is not paused
PROJECT_PAUSED409A run, an export or a case resume while the case's project is paused
PLAYBOOK_NODE_NOT_CONFIRMABLE409A confirmation of a node that is not a confirm node, or is blocked
PLAYBOOK_SELF_WRITE403An input write, confirmation or upgrade from the case's own work
PLAYBOOK_NODE_NOT_EXPORTABLE409An export of the latest revision of a node that is not done
PLAYBOOK_EXPORT_FAILED422On an export's error: its tool failed or answered no file
PLAYBOOK_EXPORT_FORMAT_MISMATCH422On an export's error: its file is not of the transformation's format
PLAYBOOK_EXPORT_INTERRUPTED500On an export's error: the server stopped while rendering it
PLAYBOOK_CASE_BUSY409An upgrade while one of the case's executions is running