Writing Playbooks
Playbooks are the core unit of work in Capx. They define what your agents do, when they do it, how their output is graded, and what happens next. Every autonomous action your company takes is driven by a playbook.
What is a playbook?
A playbook is a YAML file that describes a repeatable, multi-step workflow executed by one or more agents. Think of it as a standard operating procedure that your AI cofounder and agent roster can run without human intervention, unless a step explicitly requires approval.
Playbooks live in your company's playbooks/ directory. When a trigger fires (a schedule, a webhook, a manual invocation, or an internal event), the Capx runtime reads the playbook, resolves inputs, and begins executing steps sequentially.
Playbook structure
Every playbook has five top-level fields. Only name and steps are strictly required, but in practice you will almost always declare a trigger and version as well.
namestringrequiredversionintegertriggerobjectinputsmapstepslistrequiredname: hello-world
version: 1
steps:
- id: greet
agent: strategist
tool: send_message
with:
channel: general
body: "Hello from your AI cofounder."Triggers
The trigger field tells the runtime when to execute the playbook. If omitted, the playbook can only be invoked manually or called from another playbook. Four trigger types are available:
Scheduled
Runs on a cron schedule. The runtime evaluates the expression in UTC. Standard five-field cron syntax is supported, plus common aliases like @daily, @hourly, and @weekly.
trigger: type: scheduled cron: "0 9 * * 1-5" # Weekdays at 09:00 UTC
Manual
The playbook only runs when a founder or an agent explicitly invokes it. Useful for one-off workflows like generating a quarterly report or onboarding a new customer.
trigger: type: manual
Webhook
Fires when an external HTTP request hits the company's webhook endpoint with a matching event value. The request body is passed as an input to the playbook.
trigger: type: webhook event: stripe.payment_received
Event
Fires when another playbook or agent emits a named internal event. This is how you chain playbooks together reactively.
trigger: type: event name: customer.signed_up
Inputs
Inputs are typed parameters that the playbook receives at invocation time. They can come from a webhook payload, from the calling playbook, or from a manual invocation via the CLI or API. Inputs are referenced in step definitions using the ${{ inputs.name }} syntax.
inputs:
customer_email:
type: string
required: true
description: "Email of the new customer"
plan_tier:
type: string
required: false
default: "starter"
description: "Which plan the customer signed up for"Step definitions
Each step is an object with an id, an agent assignment, and either a tool call or a prompt for open-ended reasoning. Steps execute in order unless redirected by conditional logic.
idstringagentstringtoolstringsend_email, generate_copy, search_web).promptstringwithmaprubricobjectifstringgotostringapprovalstringrequired to pause and wait for human approval.timeoutstring5m, 1h). Step fails if exceeded.steps:
- id: draft_email
agent: marketer
tool: generate_copy
with:
type: email
audience: "new signups"
tone: professional
rubric:
score_min: 7
criteria: "Clear CTA, no jargon, under 200 words"
- id: send_email
agent: marketer
tool: send_email
with:
to: "${{ inputs.customer_email }}"
subject: "Welcome aboard"
body: "${{ steps.draft_email.output }}"Rubric grading
Rubrics let you define quality thresholds for agent output. When a step includes a rubric, the runtime evaluates the output against the criteria and assigns a score from 1 to 10. If the score falls below score_min, the step is retried (up to max_retries) or escalated.
criteriastringscore_mininteger6.max_retriesinteger2.escalate_tostringfounder.rubric:
criteria: |
The blog post must:
- Be between 800 and 1500 words
- Include at least 3 concrete examples
- End with a clear call to action
- Score 8+ for readability (Flesch-Kincaid)
score_min: 7
max_retries: 3
escalate_to: founderConditional logic
Steps support two flow-control mechanisms: if guards and goto jumps. Together, they let you build branching workflows without leaving YAML.
If guards
The if field accepts expressions that reference inputs, previous step outputs, or built-in variables. If the expression evaluates to false, the step is skipped entirely.
- id: send_premium_guide
agent: marketer
tool: send_email
if: "${{ inputs.plan_tier == 'premium' }}"
with:
to: "${{ inputs.customer_email }}"
subject: "Your premium onboarding guide"
body: "${{ steps.generate_premium_guide.output }}"Goto jumps
The goto field redirects execution to a named step, skipping any steps in between. This is useful for error handling and retry loops. Use sparingly to keep playbooks readable.
- id: check_result
agent: strategist
prompt: "Evaluate whether the campaign draft meets our brand guidelines."
rubric:
score_min: 8
criteria: "On-brand, correct tone, no banned phrases"
- id: route_decision
agent: strategist
prompt: "If the rubric score was below 8, output REVISE. Otherwise output APPROVE."
goto:
REVISE: draft_campaign
APPROVE: publish_campaignComposition
Playbooks can invoke other playbooks as a step using the run_playbook tool. This enables you to build a library of small, focused playbooks and compose them into larger workflows. The child playbook runs to completion before the parent continues.
- id: onboard_customer
agent: strategist
tool: run_playbook
with:
playbook: customer-onboarding
inputs:
email: "${{ inputs.customer_email }}"
plan: "${{ inputs.plan_tier }}"Versioning
Every playbook has an integer version field. When you update a playbook, bump the version number. The runtime keeps a history of all versions and lets you roll back to any previous version via the CLI or API.
| Action | Command |
|---|---|
| Deploy a new version | capx playbook push playbooks/onboarding.yaml |
| List versions | capx playbook versions onboarding |
| Roll back to version 2 | capx playbook rollback onboarding --version 2 |
| View diff between versions | capx playbook diff onboarding --from 2 --to 3 |
Full example: weekly content pipeline
The following playbook runs every Monday at 09:00 UTC. It researches trending topics, drafts a blog post, grades it against a rubric, and publishes it if it passes. If the rubric score is too low after retries, it escalates to the founder for review.
name: weekly-content-pipeline
version: 3
trigger:
type: scheduled
cron: "0 9 * * 1"
inputs:
target_audience:
type: string
default: "developers"
word_count:
type: integer
default: 1200
steps:
- id: research
agent: marketer
tool: search_web
with:
query: "trending topics for ${{ inputs.target_audience }} this week"
max_results: 10
- id: pick_topic
agent: strategist
prompt: |
Review the research results and pick the single most promising topic
for a blog post targeting ${{ inputs.target_audience }}.
Output only the topic title and a one-sentence rationale.
rubric:
score_min: 7
criteria: "Topic is timely, relevant, and not already covered on our blog"
- id: draft_post
agent: marketer
tool: generate_copy
with:
type: blog_post
topic: "${{ steps.pick_topic.output }}"
audience: "${{ inputs.target_audience }}"
word_count: "${{ inputs.word_count }}"
rubric:
criteria: |
- Between 800 and 1500 words
- Includes 3+ concrete examples
- Ends with a call to action
- No jargon without definition
- Engaging opening paragraph
score_min: 7
max_retries: 3
escalate_to: founder
- id: publish
agent: engineer
tool: publish_post
with:
title: "${{ steps.pick_topic.output }}"
body: "${{ steps.draft_post.output }}"
status: published
- id: notify
agent: strategist
tool: send_message
with:
channel: general
body: "New blog post published: ${{ steps.pick_topic.output }}"Full example: customer onboarding
name: customer-onboarding
version: 2
trigger:
type: event
name: customer.signed_up
inputs:
email:
type: string
required: true
plan:
type: string
default: "starter"
steps:
- id: welcome_email
agent: marketer
tool: send_email
with:
to: "${{ inputs.email }}"
subject: "Welcome to the team"
template: welcome
rubric:
score_min: 8
criteria: "Warm tone, correct plan name, no broken links"
- id: setup_account
agent: engineer
tool: provision_account
with:
owner_email: "${{ inputs.email }}"
plan: "${{ inputs.plan }}"
timeout: "2m"
- id: premium_guide
agent: marketer
tool: send_email
if: "${{ inputs.plan == 'premium' }}"
with:
to: "${{ inputs.email }}"
subject: "Your premium quick-start guide"
template: premium_guide
- id: schedule_checkin
agent: support
tool: create_task
with:
title: "Day-3 check-in with ${{ inputs.email }}"
due_in: "3d"
assignee: supportBest practices
- Keep playbooks small and composable. A playbook with more than 10 steps is usually better split into two or three smaller playbooks connected by events or composition.
- Name your step IDs descriptively. They appear in the activity feed, cost ledger, and replay logs.
- Use rubrics on any step that produces creative or analytical output. Rubrics are cheap (they cost one additional inference call) and dramatically reduce the rate of low-quality output reaching downstream steps or end users.
