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

> ## Agent Instructions
> These docs describe the Ancha CLI, npm package @ancha-labs/cli version 0.1.4, and the hosted workspace at app.ancha.io. Coding agents should start at https://docs.ancha.io/coding-agents. Run the CLI as `ancha` after `npm install -g @ancha-labs/cli`, or as `npx -y @ancha-labs/cli@0.1.4` without installing. Pass --json for machine-readable output, or set ANCHA_JSON=1. Every command ends with a `next:` line naming the next command. Exit codes: 0 pass, 1 a check failed or a comparison regressed, 2 usage, config or authentication error, 3 infrastructure error, 4 unimplemented endpoint with --strict.

# Test file (scene.yaml)

> The scene file format ancha run uses: tools, starter data with refs, the simulated clock, the task, living-world rules, people and checks, field by field.

A scene file defines one test case for `ancha run`: which tools exist, what data they start with, who else is in the world, what the agent is asked to do, and the checks that decide whether it succeeded. `ancha.yaml` lists scene files with globs, usually `scenes/**/scene.yaml`. The same file starts a world for your own harness with `ancha world up --scene`.

```yaml scenes/support-reply/scene.yaml theme={null}
id: support-reply
title: Answer the CSV export question
twins: [slack]
world: static
start_time: 2026-10-26T14:00:00Z
timeout: 2m
task: |
  Answer the customer question in Slack #support that has no reply from you yet.
  Reply in its thread.
seed:
  slack:
    team: { name: Acme }
    bot: $bot
    users:
      - { ref: bot, name: ancha, is_bot: true }
      - { ref: mike, name: mike, real_name: Mike Turner }
    channels:
      - { ref: support, name: support, members: [$bot, $mike] }
    messages:
      - ref: question
        channel: $support
        user: $mike
        at: -35m
        text: "Ridgeway Robotics cannot find last month's CSV export. Where should I point them?"
checks:
  - id: answered
    state:
      slack.message: { channel: $support, from: $bot, in_thread_of: $question, text_contains: "export" }
  - id: no_top_level_noise
    trace: { not_called: "POST /api/chat.delete" }
pass: [answered]
```

Validate with `ancha scene validate <file>`. `ancha scene schema` or `ancha schema scene` prints the JSON Schema for editor completion.

## Top-level fields

| Field | Required | Type | Meaning |
| - | - | - | - |
| `id` | Yes | string | Lowercase letters, digits and hyphens. Results use it as the scene's name. |
| `title` | Yes | string | A human title. |
| `twins` | Yes | list | The tools in the world, by key. See [Tool keys](#tool-keys). |
| `world` | No | `living` or `static` | Default `living`. A static world changes only when the agent acts and cannot have `world_rules`. |
| `start_time` | Yes | timestamp | When the simulated clock starts, in ISO 8601 with a timezone, such as `2026-10-26T14:00:00Z`. |
| `time_scale` | No | number | How fast simulated time runs compared with real time. Default `1`. |
| `timeout` | No | duration | The time limit per trial. Default `5m`. |
| `task` | No | string | What the agent is asked to do. It arrives in `ANCHA_TASK`. |
| `seed` | Yes | mapping | Starting data, one key per tool. See [Seeds](#seeds). |
| `world_rules` | No | mapping | What a living world does on its own. See [World rules](#world-rules). |
| `people` | No | list | People who answer the agent through the tools. See [People](#people). |
| `checks` | Yes | list | The conditions to evaluate. See [Checks reference](https://docs.ancha.io/reference/checks.md). |
| `pass` | No | list | IDs of the required checks. Default: every check. |

Durations are strings with units: `500ms`, `30s`, `5m`, `1h`, `1m30s`.

### Tests that name their world

The tests `ancha test new` and `ancha init --intent` write are version 2 tests. A version 2 test names its world, such as `world: acme-support` for the file `ancha/worlds/acme-support.yaml` that `ancha world gen` writes, or writes the world inline, and runs as one scene of the two. Its `deadline` is simulated time and its `timeout` the real time each attempt may take, whichever ends first. A test whose world file is missing says where it looked and how to generate one. `ancha schema test` and `ancha schema world` print both formats.

### Tool keys

`slack`, `github`, `datadog`, `pagerduty`, `hubspot`, `salesforce`, `gong`, `ga4`, `google-ads`, `linkedin-ads`, `chatgpt-ads`, `open-meteo`, `google-drive`, `gmail`, `amazon-s3`, `linear`, `okta`, `stripe`, `clerk`, `better-stack`, `notion`, `attio`, `dropbox`, `resend`, `microsoft-word`, `shopify`, `lob`, `paypal`, `jira` and `kalshi`. Each key in `twins` needs a seed under the same key. See [Tools](https://docs.ancha.io/tools/overview.md).

## Refs

Records in the seed can declare a ref with `ref: support`. Other fields, checks and rules point at them with `$support`, or `$channel.support` to also check the kind. People declare a ref too and are written as `laura`, `$laura` or `$person.laura`. Refs are unique across the whole scene, not per tool. Validation reports unknown refs, refs of the wrong kind and references that cross tools incorrectly.

## Seeds

Each tool's seed describes its starting state. History is written as offsets from `start_time` and must be at or before zero, for example `at: -35m`. Seeds are silent: they never trigger events or webhooks.

The fastest way to get realistic data is to copy a starter dataset. `ancha sandbox configuration --tools <tool> --json` prints the starter datasets; copy each `datasets[].seed` into your scene's `seed.<tool>`. The refs in the starter data, such as `$slack_support`, then work in your checks. Each [tool page](https://docs.ancha.io/tools/overview.md) describes its starter data.

### Slack

| Field | Meaning |
| - | - |
| `team` | `{ name }`. Default name `Acme`. |
| `bot` | Ref of the user your agent acts as. |
| `users` | `{ ref, name, real_name?, email?, is_bot? }` |
| `channels` | `{ ref, name, members: [user refs], is_private?, topic?, purpose? }`. Names are lowercase letters, digits, `-` and `_`. |
| `messages` | `{ ref?, channel, user, text, thread_of?, at? }`. `thread_of` makes it a reply. `at` defaults to `0s`. |

### GitHub

| Field | Meaning |
| - | - |
| `users`, `bot` | `{ ref, login, name?, email? }`, and the ref of the user the token acts as. |
| `repos` | `{ ref, name: owner/repo, default_branch?, private?, commits, branches?, labels?, pulls?, issues?, production?, check_runs? }` |
| `commits` | At least one: `{ ref, message, files: { path: content }, parents?, author?, at? }`. A file set to `null` deletes it. |
| `branches` | `{ name, head: commit ref }` |
| `labels` | `{ name, color?, description? }`. Repository labels; issues and pulls refer to them by name, ignoring case. `color` is six hex digits without `#`, default `ededed`. |
| `pulls` | `{ ref, title, head: commit ref, base?, body?, number?, merged?, draft?, user?, labels?, at? }`. `user` defaults to the bot. |
| `issues` | `{ ref, title, body?, number?, state?: open \| closed, user?, labels?, at?, comments?: [{ user?, body }] }` |
| `production` | The commit ref deployed to production. |
| `check_runs` | `{ name, commit, status, conclusion? }` |

A label named on an issue or pull that the repository does not declare is created, as GitHub does.

### Datadog

| Field | Meaning |
| - | - |
| `monitors` | `{ ref, name, query, message, tags?, state?: OK \| Alert }` |
| `dashboards` | `{ ref, title, layout_type: ordered \| free, widgets, description? }` |
| `events` | `{ ref?, title, text, at, tags?, alert_type? }` |
| `incidents` | `{ ref, title, state: active \| stable \| resolved, severity?, at }` |
| `metrics` | `{ name, tags, points: [{ at, value }] }` |
| `logs` | `{ ref?, at, service, status, message, tags? }` |

An empty `datadog: {}` is valid.

### PagerDuty

| Field | Meaning |
| - | - |
| `users` | At least one: `{ ref, name, email }`. Emails are unique, ignoring case. |
| `actor` | Ref of the user writes are attributed to. |
| `default_service` | The service for events without one. Required with several services. |
| `schedules` | `{ ref, name, entries: [{ user, start, end }] }`, with ISO timestamps and no overlaps. |
| `escalation_policies` | `{ ref, name?, levels: [{ targets, after? }] }`. Every level except the last needs `after` in whole minutes. |
| `services` | `{ ref, name, escalation_policy, slack_channel? }`. Each gets its own routing key. |
| `incidents` | `{ ref, service, title, status, at, assignees?, dedup_key? }` |

### Other tools

The other tools' seeds use the same shape as their starter datasets. Start from `ancha sandbox configuration --tools <tool> --json` and edit; `ancha scene validate` reports any field that does not fit.

## World rules

In a living world, `world_rules` makes things happen without the agent. All rules are optional.

When a command agent exits on its own before the last timed actor, the world plays out to that actor, never past the timeout, before the checks are graded. People stop acting at the exit.

| Rule | What it does |
| - | - |
| `actors` | One scripted action, at a time (`at: 2m`) or when a condition first holds (`when: <predicate>`, optional `after` and `once: true`). Actions: `slack.post`, `slack.react`, `github.comment`, `pagerduty.acknowledge`, `pagerduty.resolve`. |
| `faults` | `{ ref, starts_at?, starts_when?, resolved_when: <predicate>, recovery? }`. A problem that is active until its condition holds for the recovery delay. Give `starts_at` (a negative value means it began before the scene) or `starts_when`, a predicate that starts the fault the first time it holds, such as when production contains a bad commit. |
| `metrics` | `{ name, tags, baseline, noise?, effects?: [{ while: fault ref, value }] }`. A generated metric that changes while a fault is active. |
| `logs` | `{ ref?, every, service, status, message, tags?, while? }`. Logs emitted on a cadence, optionally only during a fault. |
| `monitors` | `{ ref, name, query, message, tags? }`. Datadog monitors evaluated against generated metrics. |
| `integrations` | Datadog to PagerDuty, Datadog to Slack, and PagerDuty to Slack notifications. A monitor message such as `@pagerduty-orders` pages a service. |
| `pipelines` | `{ repo, on: merge_to_default, workflow, duration, sets: production }`. A merge to the default branch deploys after `duration`. |

### Predicates

Rules, people and milestones use predicates with exactly one form:

| Predicate | True when |
| - | - |
| `{ all: [...] }`, `{ any: [...] }`, `{ not: ... }` | Combinations of other predicates. |
| `{ production_contains: [$repo, $commit] }` | The commit is in the deployed history. |
| `{ file_at: [$repo, production, path], equals: text }` | A file has exact content. Use `contains` for a substring. |
| `{ incident_status: <selector>, in: [triggered, acknowledged, resolved] }` | A PagerDuty incident has one of the statuses. |
| `{ monitor_state: $monitor, is: OK }` | A monitor's current state. |
| `{ message_exists: { channel, from?, matching? } }` | A Slack message exists. `matching` is a case-insensitive substring. |
| `{ issue_labeled: { repo, labels? } }` | Some issue (not a pull request) in the repository carries one of the labels, compared without regard to case. Without `labels`, any label counts. |
| `{ fault_active: $fault }` | A fault is active. |
| `{ elapsed: { gte: 30s } }` | Simulated time since the start. |
| `{ knows: [person, person.fact, ...] }`, `{ told: [...] }` | The person knows every listed fact. |
| `{ decided: [person, decision] }` or `[person, decision, option]` | The person made that decision. |
| `{ revealed: [person.fact, ...] }` | The owner shared the fact with the agent because it asked. |

Predicates work in `world_rules.actors[].when`, `faults[].starts_when` and `resolved_when`, `people[].knows[].when`, `people[].acts.works[].when`, and as milestones in `before` and `within` checks. A predicate used as a milestone is recorded from the start of the attempt, after every change, so it carries the time it first held.

`ancha scene simulate <file>` runs a living world without an agent so you can watch the rules play out. `ancha init --template incidents` writes five complete living-world scenes to learn from. See [`ancha scene`](https://docs.ancha.io/cli/scene) and [`ancha init`](https://docs.ancha.io/cli/init#incident-scenes).

## People

People are the other side of a conversation: a customer who holds back details until asked, a developer who knows the workaround, a manager who decides the priority. They live in the scene next to the tools, act through those tools as their own accounts, and take simulated time to answer.

A model writes what a person says. The engine owns everything that decides a verdict: who receives a message, which facts a person may share, what they decide, and when they answer. Checks read the engine's records, never the model's text, and nothing a person writes can end an attempt. See [Process checks](https://docs.ancha.io/reference/checks.md#process-checks).

```yaml theme={null}
people:
  - ref: laura
    name: Laura
    role: customer at Pike Street Dental; uses the patient-records product
    goal: Get CSV exports working again before Friday payroll.
    style: brief, friendly, not technical
    accounts: { slack: $slack_laura }
    listens: [dm, mentions, threads]
    replies_after: 3m..12m
    knows:
      - { id: workspace, value: Pike Street Dental, about: which workspace has the problem }
      - { id: beta_on, value: turned on the export beta yesterday, about: recent settings changes, match: semantic }
      - { id: blocked_staff, value: all 12 front-desk staff use the export, number: 12, about: how many people are affected }
      - { id: payroll_friday, value: payroll runs Friday, reveal: volunteer }
    can: [slack.post]
    acts: { patience: 2h, escalate_to: mike }
```

| Field | Meaning |
| - | - |
| `ref` | Declares the person. It shares the namespace of every other ref and is written `laura`, `$laura` or `$person.laura`. |
| `name`, `role` | Who they are. Required. |
| `goal`, `style` | What they want and how they write. Optional. |
| `accounts` | `{ slack?, github?, pagerduty? }`: users the seed declares. At least one. A person's account cannot be the agent's bot, and no two people share an account. |
| `listens` | What reaches them: `dm`, `mentions`, `threads` (the default), or `{ channels: [...] }` for a channel's top-level messages. |
| `replies_after` | Simulated time before each answer, `<min>..<max>` or one duration. Default `1m..5m`, sampled from the attempt seed. |
| `knows` | Facts they hold. See below. |
| `decides` | Decisions they make by rule. See below. |
| `can` | Actions they may take: `slack.post`, `github.comment`, `github.open_issue`, `github.approve`, `github.pr_merge`. Each needs the matching account. Default: `slack.post` with a Slack account, plus `github.comment` with a GitHub account. |
| `acts` | What they do when nobody addresses them. See below. |
| `model` | The model that plays this person, instead of the default. |
| `budget` | Model calls for their replies in one attempt, 0 to 64. Default 8. Past it they stop answering, but still hear what they are told. |

### Facts

| Field | Meaning |
| - | - |
| `id` | Unique among the person's facts and decisions. Referred to as `<person>.<id>`, for example `laura.beta_on`. |
| `value` | What the person knows, as they would say it. |
| `about` | What a message has to ask about to unlock the fact. Required unless `reveal` is `volunteer`. |
| `reveal` | `when_asked` (default): shared only when a message to the person asks about it, judged against `about`. `volunteer`: the person may bring it up. |
| `match` | How a message counts as telling someone the fact. `exact` (default): a normalized substring of `value`. `semantic`: a classifier verdict, recorded. |
| `needs` | Facts the person must know before sharing this one. Cycles are rejected. |
| `confidential` | A fact that must stay inside the company. Use a `never` check to assert it never reaches a customer. |
| `number` | A number that decision rules compare. |
| `when` | A predicate. The person holds the fact only from the first time it holds, for example once a fix is merged. Until then it is in no prompt, cannot be asked for and satisfies no rule. Others still learn it by what they are told, so telling the customer "it's merged" too early is visible to a `before` check. |

### Decisions

```yaml theme={null}
decides:
  - id: severity
    about: the priority of a customer issue
    options: [p1, p2]
    needs: [laura.blocked_staff]
    choose:
      - { option: p1, when: { fact: [laura.blocked_staff, gte, 10] } }
      - { option: p2 }
```

| Field | Meaning |
| - | - |
| `id`, `about`, `options` | The decision, what a request for it looks like, and at least two options. |
| `needs` | Facts the person must know before deciding. Without them, the person asks for them instead of deciding. |
| `choose` | Rules in order; the first whose `when` holds wins. Only the last rule omits `when`. |
| `choose[].when` | `{ fact: [<person>.<fact>, gte \| lte \| eq, <number>] }` (holds only if the deciding person knows the fact), `{ knows: <person>.<fact> }`, `all`, `any` or `not`. |

### Acts

| Field | Meaning |
| - | - |
| `patience` | Follow up in the same place when their last question has waited this long. |
| `escalate_to` | After a second follow-up, ask this person instead. Needs `patience`. |
| `works` | `{ after, when?, do, ref, about? }`: their own work, at `after` from the start, or from when `when` first holds. `do` is one of the `can` actions, `ref` what it acts on (a channel, an issue or pull request, or a repository), and the model writes only the text, about `about`. |
| `noise` | `<min>..<max>/<unit>` with unit `m`, `h` or `d`, such as `0..2/h`: background messages in the Slack channels they are in, at most 60 an hour. Noise never carries a locked fact. |

### How people run

* The people model runs in the world server. For local runs, export `OPENROUTER_API_KEY` in the shell that runs `ancha run`; a hosted workspace brings its own model access. The agent under test never receives the key. Without a model, people stay silent and the trace says so once.
* A message that reaches a person schedules their next step `replies_after` later. While a person thinks, the world's time stands still, so the reply lands exactly `replies_after` after the message.
* A person answers when a new message asks them something, asks them to confirm or choose, or needs their answer for their goal. A person who needs a fact before they can help always asks for it.
* Exact facts are matched in the message text when it arrives; semantic facts get one recorded classification. What reaches a person is matched even after their budget is spent, so a fact the agent relays still counts.
* In a world with people, when nothing is in flight, the clock jumps to the next scheduled job. A two-hour wait takes about a second of real time.
* Every model call is one line in the attempt's `people.jsonl`: the kind of call, what triggered it, the model that answered, a hash of the prompt, tokens and cost.

Agents that wait for people set `agent.input: per_event` in `ancha.yaml`: the command runs once per event addressed to the agent, with the event in `ANCHA_EVENT_JSON` and a one-line summary in `ANCHA_TASK`, one conversation at a time. The attempt ends when the world is quiet with nothing scheduled before the scene's `timeout`. See [ancha.yaml](https://docs.ancha.io/reference/ancha-yaml.md#command-agents).

## Reference companions

Next to a scene, `oracle.mjs` (or `oracle.ts`) is a scripted agent that should pass and `null.mjs` one that should fail. `ancha scene simulate <file> --oracle` and `--null` run them. Keep them, the scene and the checks away from the agent under test.


## Related topics

- [ancha init](https://docs.ancha.io/cli/init.md)
- [Troubleshooting](https://docs.ancha.io/troubleshooting.md)
- [ancha.yaml](https://docs.ancha.io/reference/ancha-yaml.md)
- [Tests](https://docs.ancha.io/concepts/tests.md)
- [ancha world gen](https://docs.ancha.io/cli/world-gen.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.