{
  "slug": "pilot/playbook-reference",
  "title": "Playbook Reference",
  "description": "Complete YAML schema for Cuitty Pilot playbooks — inputs, steps, actions, assertions, and captures.",
  "url": "https://cuitty.com/docs/pilot/playbook-reference",
  "markdown_url": "https://cuitty.com/docs/pilot/playbook-reference.md",
  "json_url": "https://cuitty.com/docs/pilot/playbook-reference.json",
  "frontmatter": {
    "title": "Playbook Reference",
    "description": "Complete YAML schema for Cuitty Pilot playbooks — inputs, steps, actions, assertions, and captures.",
    "order": 2,
    "section": "Pilot",
    "updatedAt": "2026-06-01"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "playbook-reference",
      "text": "Playbook Reference"
    },
    {
      "depth": 2,
      "slug": "top-level-structure",
      "text": "Top-level structure"
    },
    {
      "depth": 2,
      "slug": "inputs",
      "text": "Inputs"
    },
    {
      "depth": 2,
      "slug": "outputs",
      "text": "Outputs"
    },
    {
      "depth": 2,
      "slug": "steps",
      "text": "Steps"
    },
    {
      "depth": 3,
      "slug": "selectors",
      "text": "Selectors"
    },
    {
      "depth": 2,
      "slug": "browser-actions-14",
      "text": "Browser actions (14)"
    },
    {
      "depth": 2,
      "slug": "assertions",
      "text": "Assertions"
    },
    {
      "depth": 2,
      "slug": "capture-sources",
      "text": "Capture sources"
    },
    {
      "depth": 2,
      "slug": "executor-actions",
      "text": "Executor actions"
    },
    {
      "depth": 3,
      "slug": "terraform-tf_",
      "text": "Terraform (tf_*)"
    },
    {
      "depth": 3,
      "slug": "git-git_",
      "text": "Git (git_*)"
    },
    {
      "depth": 3,
      "slug": "store-persist_-legacy-prefix",
      "text": "Store (persist_* legacy prefix)"
    },
    {
      "depth": 3,
      "slug": "shell-shell_",
      "text": "Shell (shell_*)"
    },
    {
      "depth": 3,
      "slug": "http-http_",
      "text": "HTTP (http_*)"
    },
    {
      "depth": 3,
      "slug": "composite",
      "text": "Composite"
    },
    {
      "depth": 2,
      "slug": "example",
      "text": "Example"
    },
    {
      "depth": 2,
      "slug": "see-also",
      "text": "See also"
    }
  ],
  "body_markdown": "# Playbook Reference\n\nA playbook is a YAML document that describes an automated workflow. Pilot validates every playbook against a Zod schema before execution.\n\n## Top-level structure\n\n```yaml\nplaybook:\n  id: <dotted.id>          # e.g. cloudflare.dns.add-a-record\n  version: 1               # integer, incremented on each edit\n  target: <provider>       # cloudflare, aws, vercel, etc.\n  title: Human-readable title\n  description: Optional longer description\n  executor: browser        # default executor for all steps (optional)\n  executor_config: {}      # executor-specific config map (optional)\n  inputs: []               # input definitions\n  outputs: []              # output definitions\n  preconditions: []        # conditions checked before run starts\n  steps: []                # ordered list of steps (min 1)\n```\n\nRequired fields: `id` (dotted lowercase string), `version` (integer), `target`, `title`, `steps` (min 1). Optional: `description`, `executor` (default `browser`), `executor_config`, `inputs`, `outputs`, `preconditions`.\n\n## Inputs\n\nEach input defines a variable the caller must supply. Referenced in steps as `{{name}}`.\n\n```yaml\ninputs:\n  - name: zone\n    type: string\n    required: true\n  - name: proxied\n    type: boolean\n    default: true\n```\n\nFields: `name` (pattern `^[a-z_][a-z0-9_]*$`), `type` (one of `string`, `number`, `boolean`, `ipv4`, `ipv6`, `url`, `email`, `secret`), `required` (default `true`), `default`, `description`.\n\n## Outputs\n\nOutputs reference values captured during the run: `{ name, type, source }` where `source` is e.g. `capture.record_id`.\n\n## Steps\n\nEach step is an atomic action. Steps run in order unless the composite executor orchestrates them differently.\n\n```yaml\nsteps:\n  - id: goto-dns\n    action: navigate\n    executor: browser        # override playbook-level executor\n    url: \"https://example.com\"\n    timeout_ms: 15000\n    ai_fallback: true\n    destructive: false\n    breakpoint: false\n    intent: Human-readable goal for this step.\n    observe: [text, a11y, screenshot]\n```\n\nRequired: `id` (pattern `^[a-z][a-z0-9-]*$`, unique within playbook), `action`. Optional: `executor` (overrides playbook default), `selector`, `url`, `value`, `timeout_ms` (default 15000), `ai_fallback` (default false), `destructive` (default false), `breakpoint` (default false), `intent`, `observe` (`[\"text\", \"a11y\", \"screenshot\"]`), `capture_as`, `assert`.\n\n### Selectors\n\nA selector can be a plain string or an object with fallback chain:\n\n```yaml\nselector:\n  primary: \"button:has-text('Save')\"\n  fallbacks:\n    - \"[data-testid='save-btn']\"\n    - \"role=button[name=/save/i]\"\n```\n\n## Browser actions (14)\n\nThese are the default executor's actions:\n\n| Action | Required fields | Description |\n|--------|----------------|-------------|\n| `navigate` | `url` | Navigate to a URL. Supports `{{input}}` interpolation. |\n| `click` | `selector` | Click an element. |\n| `fill` | `selector`, `value` | Type a value into an input field. |\n| `select` | `selector`, `value` | Choose a dropdown option. |\n| `set_toggle` | `selector`, `value` | Set a checkbox or toggle to true/false. |\n| `wait_for` | `selector` | Wait until an element is visible. |\n| `assert` | `assert` array | Verify page state without acting. |\n| `capture` | `capture_as`, `from` | Extract a value from the page or network. |\n| `capture_secret` | `capture_as`, `from` | Like `capture`, but the value is masked in logs. |\n| `ai_decide` | `allowed_outcomes` | Let the AI model choose from a list of outcomes. |\n| `scroll` | `selector` (optional) | Scroll the page or a specific element. |\n| `hover` | `selector` | Hover over an element. |\n| `press` | `value` | Press a keyboard key (e.g. `Enter`, `Tab`). |\n| `upload` | `selector`, `value` | Upload a file via a file input. |\n\n## Assertions\n\nPost-step assertions verify expected page state:\n\n```yaml\nassert:\n  - visible: \"[data-testid='dns-records-table']\"\n  - text_present: \"Record added\"\n  - url_matches: \"/dns\"\n  - network_complete:\n      url_pattern: \"/dns_records\"\n      method: POST\n      status: 200\n```\n\n| Assertion | Description |\n|-----------|-------------|\n| `visible: <selector>` | Element is visible on page |\n| `hidden: <selector>` | Element is not visible |\n| `text_present: <text>` | Text appears in page body |\n| `text_absent: <text>` | Text does not appear |\n| `url_matches: <pattern>` | Current URL matches pattern |\n| `network_complete` | A matching network request completed |\n\n## Capture sources\n\nThe `from` field on `capture` / `capture_secret` supports three source types:\n\n```yaml\n# From a DOM element\nfrom:\n  selector: \".result-id\"\n  attribute: textContent    # optional, defaults to textContent\n\n# From a network response\nfrom:\n  network_response:\n    url_pattern: \"/dns_records\"\n    method: POST\n    json_path: \"$.result.id\"\n\n# From OCR on a screenshot region\nfrom:\n  screenshot_ocr:\n    region: \"top-right\"\n```\n\n## Executor actions\n\nActions prefixed with the executor name are routed automatically:\n\n### Terraform (`tf_*`)\n\n`tf_init`, `tf_validate`, `tf_plan`, `tf_apply`, `tf_destroy`, `tf_output`, `tf_import`, `tf_state`\n\n### Git (`git_*`)\n\n`git_clone`, `git_branch`, `git_commit`, `git_push`, `git_pr_create`, `git_pr_merge`, `git_tag`, `git_release`\n\n### Store (`persist_*` legacy prefix)\n\n`persist_provision`, `persist_migrate`, `persist_sync`, `persist_backup`, `persist_restore`, `persist_proxy_start`, `persist_proxy_stop`\n\n### Shell (`shell_*`)\n\n`shell_exec`, `shell_script`, `shell_assert`\n\n### HTTP (`http_*`)\n\n`http_request`, `http_assert`, `http_poll`\n\n### Composite\n\n`run_playbook`, `parallel`, `conditional`, `loop`\n\nSee [Executors](/docs/pilot/executors) for full field documentation on each action.\n\n## Example\n\nA condensed playbook that adds a DNS A record on Cloudflare:\n\n```yaml\nplaybook:\n  id: cloudflare.dns.add-a-record\n  version: 1\n  target: cloudflare\n  title: Add an A record to a DNS zone\n  inputs:\n    - { name: zone, type: string, required: true }\n    - { name: record_name, type: string, required: true }\n    - { name: ip_address, type: ipv4, required: true }\n  outputs:\n    - { name: record_id, type: string, source: capture.record_id }\n  steps:\n    - id: goto-dns\n      action: navigate\n      url: \"https://dash.cloudflare.com/?to=/:account/{{zone}}/dns\"\n      ai_fallback: true\n    - id: fill-name\n      action: fill\n      selector: \"[name='dns-record-name']\"\n      value: \"{{record_name}}\"\n    - id: save\n      action: click\n      selector: \"button:has-text('Save')\"\n      assert: [{ text_present: \"Record added\" }]\n    - id: capture-id\n      action: capture\n      capture_as: record_id\n      from:\n        network_response:\n          url_pattern: \"/dns_records\"\n          method: POST\n          json_path: \"$.result.id\"\n```\n\nFor a longer example using `ai_decide` with multiple outcomes and `breakpoint` on destructive steps, see the R2 playbook in `playbooks/cloudflare/r2.enable-and-create-buckets.yaml`.\n\n## See also\n\n- [Executors](/docs/pilot/executors) — detailed executor reference\n- [JavaScript SDK](/docs/pilot/sdk/javascript) — run playbooks programmatically\n- [Quickstart](/docs/pilot/quickstart) — record and replay your first workflow",
  "links_out": [
    "/docs/pilot/executors",
    "/docs/pilot/sdk/javascript",
    "/docs/pilot/quickstart"
  ]
}