Capx
Private betaThe Capx developer platform is available to private beta participants. Join the waitlist for access.
Guides

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.

Top-level fields
namestringrequired
Human-readable name. Must be unique within the company.
versioninteger
Monotonically increasing version number. Defaults to 1.
triggerobject
When and how the playbook is invoked.
inputsmap
Named parameters the playbook expects at invocation time.
stepslistrequired
Ordered list of step definitions.
Minimal playbook
yaml
name: 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.

Scheduled trigger
yaml
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.

Manual trigger
yaml
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.

Webhook trigger
yaml
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.

Event trigger
yaml
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.

Declaring inputs
yaml
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.

Step fields
idstring
Unique step identifier within the playbook.
agentstring
Which agent from the roster executes this step.
toolstring
The tool to invoke (e.g. send_email, generate_copy, search_web).
promptstring
Free-form prompt for the agent instead of a tool call.
withmap
Key-value arguments passed to the tool or prompt.
rubricobject
Grading criteria for the step output.
ifstring
Conditional expression. Step is skipped if it evaluates to false.
gotostring
Jump to a specific step ID after this step completes.
approvalstring
Set to required to pause and wait for human approval.
timeoutstring
Max duration (e.g. 5m, 1h). Step fails if exceeded.
Step with tool call
yaml
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.

Rubric fields
criteriastring
Natural-language description of what good output looks like.
score_mininteger
Minimum acceptable score (1-10). Defaults to 6.
max_retriesinteger
How many times to retry before escalating. Defaults to 2.
escalate_tostring
Who to escalate to if retries are exhausted. Defaults to founder.
Tip
Rubric grading uses the same agent adapter that executed the step. If the step ran on Claude, rubric evaluation also runs on Claude. This keeps evaluation consistent with generation.
Rubric with auto-escalation
yaml
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: founder

Conditional 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.

Conditional step
yaml
- 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.

Goto for error handling
yaml
- 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_campaign

Composition

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.

Calling another playbook
yaml
- id: onboard_customer
  agent: strategist
  tool: run_playbook
  with:
    playbook: customer-onboarding
    inputs:
      email: "${{ inputs.customer_email }}"
      plan: "${{ inputs.plan_tier }}"
Note
Composed playbooks inherit the parent's execution context (company, governance rules, cost limits) but run with their own step counter and retry state.

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.

Note
Active executions always run against the version that was current when the execution started. Updating a playbook mid-run does not affect in-flight executions.
ActionCommand
Deploy a new versioncapx playbook push playbooks/onboarding.yaml
List versionscapx playbook versions onboarding
Roll back to version 2capx playbook rollback onboarding --version 2
View diff between versionscapx 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.

playbooks/weekly-content.yaml
yaml
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

playbooks/customer-onboarding.yaml
yaml
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: support

Best 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.
Warning
Avoid circular goto chains. The runtime detects infinite loops and will kill the execution after 50 iterations of the same step, but it is better to design your flow to converge.

Next steps