Playbook reference
Everything the plugin reads from your repository, and the rules it checks before a playbook can run.
Folders and layout
The plugin reads two folders in the workspace: .paseo/plugins/playbooks/ and .paseo/playbooks/. Both are loaded and merged. Each folder is self-contained: a playbook uses the roles/ and defaults/main.yaml of its own folder. The same playbook id in both folders is an error on both files.
.paseo/plugins/playbooks/
├── defaults/
│ └── main.yaml # optional: agent defaults for this folder
├── pr.yaml # a playbook; its id is the file name ("pr")
├── release.yaml
└── roles/
└── reviewer/
├── role.yaml # title, readonly, agent
└── instructions.md
- Every top-level
*.yamlor*.ymlfile is a playbook. Shared settings live in subfolders, so no file name is reserved. - Ids (playbooks, roles, steps, inputs, outputs) use lowercase letters, digits,
-and_, starting with a letter. - Playbooks are read from the workspace's own folder, so a branch or worktree can carry its own changes. Start stores a copy of the resolved playbook in the run, so editing files does not change runs in progress.
defaults/main.yaml
agent: # defaults for every step in this folder
provider: claude
model: claude-opus-5-5
mode: bypassPermissions
manager: # settings of the manager agent when Start creates a new one
model: claude-sonnet-5-5
mode: default
timeout: 1h # default step timeout| Key | Meaning |
|---|---|
agent | provider, model, mode, thinking for steps. See agent settings. |
manager | Agent settings for the manager created by Start. When agent.provider is set, it inherits agent.provider and agent.model (not the mode). Without a model it uses Claude Opus. |
timeout | Default step timeout, such as 30m or 2h. No timeout when absent. |
An invalid defaults/main.yaml makes every playbook of its folder invalid.
Roles
A role is a folder roles/<id>/ with two files. A playbook refers to it by its folder name, and several playbooks can share it.
| File / key | Meaning |
|---|---|
instructions.md | Required and non-empty. The system prompt of every step that uses the role. The plugin appends the step protocol: allowed outcomes, required outputs, and how to call report_step_result. |
role.yaml title | Display name. Defaults to the id. |
role.yaml readonly | true if the role must not edit files. Default false. See readonly roles. |
role.yaml agent | Agent settings for this role. |
Playbook fields
| Field | Meaning |
|---|---|
title | Display title. Defaults to the file name. |
description | Shown in the Start form. Also returned to agents by list_playbooks and validate_playbooks. |
hidden | true moves the playbook to the collapsed "Hidden playbooks" section at the bottom of the Playbooks panel, for one only meant as a sub-playbook or after-task (a retro, say). It can still be started from there or by start_playbook, and used by other playbooks; while it has runs of its own, its card is in the main list. Default false. |
agent | Agent settings for all steps of this playbook. |
inputs | Typed variables. They make up the Start form, and start_playbook takes them. See variables. |
outputs | Typed run results, resolved when the run completes. value maps them from step outputs or playbook inputs; without it they are matched by name. |
concurrency | Maximum active runs of this playbook in the Paseo project, across all worktrees, and also the number of runs that may be inside it as a sub-playbook (see Sub-playbooks). Unlimited when absent. |
steps | At least one step. |
after | Tasks run once the run has ended, without changing its result. See after-tasks. |
Step fields
A step is an agent step (role), a human gate (gate) or a sub-playbook (playbook), exactly one of them. The order of steps in the file does not matter.
| Field | Applies to | Meaning |
|---|---|---|
id | all | Unique in the playbook. end, fail and outputs are reserved. |
needs | all | Steps that must complete first. A step without needs is ready when the run starts. Steps that are ready at the same time run in parallel. |
role | agent | Role folder name under roles/. |
gate | gate | Message shown to the human. |
choices | gate | The gate's buttons, in order. Each key is an outcome with label, comment and color. See gates. |
prompt | agent | Template for the step's first message. See prompt templates. |
inputs | agent | A list of names (each matched automatically), or a map of name to a source template or ~ (matched automatically). |
outputs | agent | Typed values the step must return with any outcome except failure; a failure report skips output validation. All of them are required then. |
outcomes | agent | Allowed outcomes. Default [success, failure]. |
on | all | Per outcome: complete, end, fail, or { goto: <step>, max_loops: N }. |
retries | agent | Extra attempts allowed after failure. Default 0. |
timeout | agent | Duration such as 30m or 2h. Defaults to defaults/main.yaml. |
agent | agent | Agent settings for this step only. |
Outcomes and flow
- The step agent reports an outcome with
report_step_result. The manager then decides the final outcome withcomplete_step, and may correct the outputs. - One rule for agent steps and gates: the first listed outcome (a gate's first choice) completes the step by default. Steps whose
needsare now all completed become ready. failuremakes the step ready again while attempts remain (retries), then fails the run. Attempts lost in a daemon restart do not count.- Every other outcome needs an
onentry (complete,end,fail,gotoor, for agent steps,ask); without one, validation fails. The one exception is a gate withoutchoices: its defaultrejectedfails the run. endends the playbook it is written in: the run at top level, the block when the playbook is nested (see sub-playbooks), the task inside an after-task. The run completes when every step has completed (or was skipped), or on a top-levelend; its outputs are then resolved.failfails the run at once.- A timeout interrupts the step agent's turn. Retries apply only after the manager completes the step with the
failureoutcome.
Gates
A gate becomes ready like any step. The manager opens it with request_gate: a one-sentence summary shown on the gate, and details (your gate: text, then the manager's notes) collapsed under it. Its buttons come from choices, in order: each key is an outcome, with a label, comment: none | optional | required and color: primary | neutral | success | warning | danger (Paseo theme colours; by default the first choice is primary and the others neutral). Without choices, a gate offers approved (Approve) and rejected (Reject, optional comment, colour danger). The first choice completes the gate by default. A gate that declares choices needs an on entry for every other choice; only the default Reject fails the run without one. Each button shows where it leads (→ End, → End Release inside a block, → Implement, → Fail run). A choice that takes a comment opens a text box with Send; the comment goes to the manager and, on a goto, to the target as feedback. Decisions come from the panel, or by chat through the manager's resume tool. The first decision wins.
- id: pr_approval
needs: [babysit]
gate: The PR is ready. Approve to finish, or request changes to send it back.
choices:
approved: { label: Approve }
rejected: { label: Request changes, comment: required, color: danger }
on:
rejected: { goto: implement }
Loops
gotomust point to the step itself or to a step it needs, directly or not. Loops are the only backward edges.- A
gotoresets the target and every step downstream of it to pending, clears their outputs, and interrupts any of their agents still running. The target becomes ready again. - The summary of the step that looped, or the gate comment, is added to the target's next prompt as "Feedback to address".
on: { <outcome>: { ask: "...", goto: <step>, max_loops: N } }(agent steps) lets a step ask a human. Completing the step with that outcome opens a gate with yourasktext and the step's summary (the question); the human's answer is required and becomes the feedback ofgoto's target, which defaults to the step itself. The report needs no outputs, andmax_loopsworks as forgoto. Example:outcomes: [success, needs_input]withon: { needs_input: { ask: "The investigation needs your input." } }.max_loopscounts how many times that edge fired. At the limit the plugin opens a "loop limit" gate: approve allows one more loop, reject fails the run.
Variables
| Key | Meaning |
|---|---|
type | string (default), number, boolean or enum. No objects or lists. |
values | Allowed values, for enum only (required there). |
required | Playbook inputs: the run cannot start without a value. |
default | Value used when none is given. |
description | Shown in the Start form and to agents. |
Numbers and booleans sent as text ("42", "true") are accepted. A report that misses a declared output or has the wrong type is refused, with the reason, so the agent fixes it in the same turn. The one exception is a failure report, which skips output validation, so a failed step may leave its outputs empty.
Where a step input comes from
- An explicit source wins:
"{{ playbook.inputs.x }}"or"{{ steps.<id>.outputs.y }}", where the step must be one this step needs, directly or not. - Otherwise the input takes the output with the same name from a step it needs, directly or not.
- Failing that, it takes the playbook input with the same name.
- If several earlier steps output that name, or nothing matches, the playbook is invalid until you map the input explicitly. Nothing is chosen silently.
Playbook outputs without value are matched the same way, from any step with a same-named, type-compatible output. Any type can feed a string. The detail sheet shows where each value came from, for example pr_url ← implement.pr_url (auto).
Prompt templates
Templates use plain {{ path }} substitution, with no conditionals or loops. A step prompt may use only {{ inputs.<name> }} (a declared input; the inputs: map uses the other forms, {{ playbook.inputs.x }} and {{ steps.<id>.outputs.y }}), {{ run.id }} and {{ attempt }}; steps of an after-task may also use {{ run.status }} and {{ run.failure }}. Missing or empty values render as (none). If a prompt has no placeholders, the plugin lists the step's inputs after it. The manager's brief and any loop feedback are appended to the prompt.
Sub-playbooks
A step with playbook: <id> runs another playbook of the same folder in place, as a building block. The loader expands it: its steps become pr.investigate, pr.implement… in the same run, under the same manager, and the panel draws a labelled frame around them.
steps:
- id: pr
playbook: pr # inputs match by name, or map them in `inputs`
- id: approve
needs: [pr]
gate: Release it?
on:
rejected: { goto: pr } # restarts the whole block, with the comment as feedback
- id: release
needs: [approve]
playbook: release # gets pr_url from the pr block's outputs
- The block's first steps take its
needs; steps that need the block wait for its last steps. - Its inputs are the sub-playbook's inputs, auto-matched by name or mapped in
inputs; unmatched ones use their default. Its outputs are the sub-playbook's outputs, usable by name or as{{ steps.pr.outputs.pr_url }}. - Loops inside the sub-playbook stay inside. A
gototo the block restarts it from its first step; the sub-playbook must have exactly one. endends the playbook it is written in, so inside the sub-playbook it ends the block, not the run: the step completes, the block's other unfinished steps becomeskipped(running ones are interrupted; the panel dims them), and the steps that need the block go on. In nested blocks it ends only the innermost one. The block's outputs are what its completed steps produced.failstill fails the whole run. Agototo the block runs it again.prompt,outputs,outcomes,timeout,agent,choicesandondo not apply to a playbook step. A playbook that includes itself, directly or not, is an error.- The sub-playbook's manager settings are ignored; its gates stay gates of the run.
- The sub-playbook's
concurrencyapplies to the block. Its slots are shared by every run in the project, including top-level runs of that playbook. A run takes a slot when the block's first steps become ready and keeps it until all the block's steps complete or are skipped (also while a gate inside waits) or the run ends. Until then those steps arequeued("waiting for a free slot" in the panel) and the manager is woken when they are ready. Agotothat restarts the block's first steps releases the slot and queues again.
Sub-playbooks are the only way to combine playbooks: one run and one graph, so a gate or a loop can sit between two playbooks. There is no chaining from one run to another.
After-tasks
after: lists tasks that run once the run has ended, whatever its outcome: a retro that reflects on the run, a notification. Each task is an agent step (role and prompt), a gate, or a sub-playbook (playbook: <id>), with a trigger.
after:
- id: retro
playbook: retro
inputs:
run_id: "{{ run.id }}" # run values are always mapped explicitly
- id: notify
role: reviewer
trigger: manual # default: auto
inputs:
status: "{{ run.status }}"
prompt: "Post a summary of run {{ run.id }} ({{ inputs.status }})."
- They belong to the same run: same run file, same manager, drawn below End in the panel graph, each in its own frame.
- They never change the run's status, which is fixed when the main graph ends. Inside an after-task,
endcompletes the task (it ends the playbook it is written in);fail, exhausted retries or a rejected gate fail the task only. In a sub-playbook used as an after-task, a nested block'sendstill ends only that block. auto(the default) starts when the run completes or fails, not when it is canceled.manualshows a Start button on the finished run, in any end state; each manual task runs once. The manager is woken with the task's ready steps.- No
needs: each task starts on its own, and several may run at once. - Inputs may also use
{{ run.id }},{{ run.status }},{{ run.failure }},{{ run.outputs.<name> }}(the run's outputs; partial on a failed run) and the outputs of any main step. By name, an input matches the playbook's outputs, then the playbook's inputs, as elsewhere. Run values never match by name: writerun_id: "{{ run.id }}". Prompts may use{{ run.status }}and{{ run.failure }}. - A sub-playbook's own
afteris ignored when it is nested: only the outermost playbook's after-tasks run. A sub-playbook used as an after-task keeps its steps, gates and loops; itsconcurrencyis ignored, and after-tasks hold no concurrency slot. - While after-tasks run, the run is "wrapping up": its dot pulses in its final colour, it stays active in its workspace, and Cancel cancels the after-tasks without changing the run's status. Archiving the worktree or losing the manager does the same.
- The "both may edit files" rule applies between after-tasks, not between an after-task and a main step.
Agent settings
provider, model, mode and thinking merge field by field. Precedence: step > role > playbook > defaults/main.yaml > the manager's own provider and model. With no model at all, Claude steps use Claude Opus.
Agent modes
A playbook may use any agent mode, bypassPermissions included: steps that run shell commands then work without asking. Playbooks come from the repository, so any branch can ship one; only start playbooks from repositories you trust. An unset mode uses the provider's default.
Readonly roles and concurrency
- Before a
readonlystep starts, the plugin records the checkout'sHEADandgit status --porcelain, including untracked files. When the step ends it compares them. Any change fails the attempt with "readonly role modified the checkout". Build output in ignored files does not count. - Steps share the run's worktree. Two steps that can run at the same time (neither needs the other) must not both be able to edit files: make one role
readonlyor order them withneeds.
Validation
The Playbooks panel and the validate_playbooks tool report, per file:
- YAML syntax errors and values of the wrong type (unknown keys are ignored)
- duplicate step ids, reserved ids, invalid names
- a step with both or neither of
roleandgate, or gate-only fields on agent steps - missing roles (
role.yamlorinstructions.md), unknownneeds, cycles inneeds onkeys that are not outcomes, extra outcomes without anonentry,gototargets that are not earlier steps- two steps that can run together and both edit files
- prompts using anything other than
inputs,run.idandattempt(after-tasks may also userun.statusandrun.failure) needson an after-task,triggeron a step, an after-task id that clashes with a step id- unresolved, ambiguous or type-incompatible inputs and outputs
enumwithoutvalues, invalid durations- a
nextkey (chaining was removed; use a sub-playbook step) - the same playbook id in both folders
YAML pitfall
Inside a flow mapping ({ ... }), a comma ends the value. Quote text that contains a comma, or use block style:
outputs:
changes: { type: string, description: "What you changed, per file" }