> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gainable.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Playbooks

> The trigger, steps, and guardrails recipe inside every agent

## What is a playbook?

A **playbook** is the recipe an agent runs when an [objective](/autopilot/objectives) needs attention. It has three parts:

1. **A trigger.** What starts the playbook. One of [schedule, data change, webhook, or user-triggered](/autopilot/triggers).
2. **Steps.** A sequence of [tool calls](/autopilot/tools), each one observed by the runtime and recorded in the [action log](/autopilot/agent-action-log).
3. **Guardrails.** Constraints that bound the playbook: rate limits, scopes, risk tier, allowed recipients, max recursion.

Think of an objective as the "why" and the playbook as the "how."

## The shape of a playbook

```yaml theme={null}
name: Nudge stalled deals
trigger:
  type: schedule
  cron: "0 9 * * 1-5"
steps:
  - tool: query_collection
    collection: deals
    filter: "stage != 'closed' AND days_since_stage_changed_at >= 14"
  - tool: query_collection
    collection: contacts
    filter: "id IN {{ step1.deals[*].contact_id }}"
  - tool: draft_for_approval
    template: stalled_deal_followup
    recipient: "{{ step2.contact.email }}"
    owner: "{{ step1.deal.owner }}"
guardrails:
  scope: app_wide
  risk_tier: draft_and_approve
  rate_limit_per_user: "10/day"
  exclude_recipients_in: [unsubscribes, do_not_contact]
```

This pattern, **trigger → steps → guardrails**, is the same for every playbook in every Gainable app. Once you understand it, you understand all of them.

## The playbook editor

Playbooks live in the [Autopilot](/autopilot/inbox) UI. The editor shows:

* **Trigger panel.** Pick the trigger type and configure it.
* **Step list.** Drag steps. Each step is a [tool](/autopilot/tools) call. Inputs can reference previous step outputs.
* **Guardrails panel.** Risk tier, rate limits, scopes, recipient exclusions.
* **Test panel.** Run the playbook against test data without lighting up real recipients.

You don't have to use the editor. Almost everything you can configure in it can be described in natural language during a build prompt. The editor is for fine-tuning after the fact.

## "Simulate on last 30 days"

Before turning a playbook live, run **Simulate on last 30 days**. The runtime replays your real data through the playbook against the last 30 days of activity and shows:

* How many times the trigger would have fired
* How many drafts would have been produced
* Per draft: the recipient, the message, the reasoning chain, the tool calls
* An estimate of approval workload (drafts per day, per user)

<Info>
  **Simulate is the safety net.** It's the difference between "the playbook looks reasonable" and "the playbook would have produced 240 emails to one customer last Tuesday." Always simulate before going live.
</Info>

The simulation runs in a sandbox. It does not call `send_email`, `send_slack`, `send_sms`, or `call_external` for real. It produces drafts you can read but never delivers them.

## Step references

Steps can reference each other's outputs. The reference syntax is `{{ stepN.field }}` or `{{ stepN.collection[*].field }}` for arrays.

```yaml theme={null}
steps:
  - tool: query_collection
    collection: deals
    filter: "amount > 10000 AND stage = 'proposal'"
    limit: 50
  - tool: query_collection
    collection: contacts
    filter: "id IN {{ step1.deals[*].contact_id }}"
  - tool: draft_for_approval
    template: proposal_followup
    recipient: "{{ step2.contact.email }}"
    context:
      deal_name: "{{ step1.deal.name }}"
      deal_amount: "{{ step1.deal.amount }}"
```

## Guardrails

Guardrails bound what a playbook can do regardless of its steps. They are the policy layer that exists outside the agent's reasoning.

| Guardrail                    | What it does                                                                                   |
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
| **Risk tier**                | One of `draft_and_approve`, `auto_with_undo`, `auto`. See [risk tiers](/autopilot/risk-tiers). |
| **Rate limit per user**      | Max drafts a single user can be the owner of in a window                                       |
| **Rate limit per recipient** | Max drafts that can target a single recipient in a window                                      |
| **Allowed recipients**       | Whitelist of domains or addresses                                                              |
| **Excluded recipients**      | Reference to a `do_not_contact` collection                                                     |
| **Scope**                    | `app_wide` or `personal` ([scopes](/autopilot/scopes))                                         |
| **Max steps**                | Hard cap on tool calls per playbook run                                                        |
| **Max recursion**            | Prevents a playbook from triggering itself unbounded                                           |

## Common playbook shapes

### Watch and notify

```yaml theme={null}
trigger:
  type: data_change
  collection: tickets
  on: [create]
  where: "priority = 'p0'"
steps:
  - tool: notify_user
    recipient: "on_call_user"
    message: "P0 ticket: {{ trigger.record.title }}"
```

### Watch and draft

```yaml theme={null}
trigger:
  type: data_change
  collection: deals
  on: [update]
  where: "stage = 'lost'"
steps:
  - tool: draft_for_approval
    template: lost_deal_postmortem
    owner: "{{ trigger.record.owner }}"
```

### Schedule and digest

```yaml theme={null}
trigger:
  type: schedule
  cron: "0 8 * * 1-5"
steps:
  - tool: query_collection
    collection: deals
    filter: "owner = {{ context.user_id }}"
  - tool: draft_for_approval
    template: daily_briefing
    owner: "{{ context.user_id }}"
```

### Inbound and route

```yaml theme={null}
trigger:
  type: webhook
  source: stripe
  event: invoice.payment_failed
steps:
  - tool: query_collection
    collection: customers
    filter: "stripe_id = {{ trigger.body.customer }}"
  - tool: draft_for_approval
    template: payment_failed
    recipient: "{{ step1.customer.email }}"
    owner: "{{ step1.customer.account_owner }}"
```

## Editing playbooks

Edit through chat or the editor:

```text theme={null}
Update the stalled-deal playbook to also cc the
sales manager when the deal is over $50,000.
```

```text theme={null}
Add a guardrail to the daily-briefing playbook:
max one draft per user per day.
```

```text theme={null}
Change the SLA-enforcer playbook trigger from
every hour to every 15 minutes.
```

## Best practices

<AccordionGroup>
  <Accordion title="Always simulate before going live">
    A 30-day simulation catches load problems, recipient explosions, and bad filters before they reach real users.
  </Accordion>

  <Accordion title="Keep playbooks single-purpose">
    "Nudge stalled deals" is a clear playbook. "Manage the sales pipeline" is a bundle of five playbooks pretending to be one.
  </Accordion>

  <Accordion title="Prefer draft_for_approval over auto-send">
    The default for outbound work is `draft_for_approval` for a reason. Graduate to `auto_with_undo` only after a clean approval history.
  </Accordion>

  <Accordion title="Use exclusions, not blanket disables">
    A `do_not_contact` collection lets you opt specific records out without breaking the whole playbook for everyone else.
  </Accordion>
</AccordionGroup>

## Learn more

<CardGroup cols={2}>
  <Card title="Triggers" icon="bolt" href="/autopilot/triggers">
    Schedule, data change, webhook, user-triggered
  </Card>

  <Card title="Tools" icon="screwdriver-wrench" href="/autopilot/tools">
    The fixed registry of tool calls
  </Card>

  <Card title="Risk tiers" icon="shield-check" href="/autopilot/risk-tiers">
    Draft-and-approve, auto-with-undo, auto
  </Card>

  <Card title="Recipes" icon="lightbulb" href="/autopilot/recipes">
    Worked examples of common playbooks
  </Card>
</CardGroup>
