> ## 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, 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` 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 an app

> Start your web app or API server once per attempt, let the world drive it with mail, messages, scheduled calls and time, and check what it did.

Use this guide when the AI feature lives inside an app, such as a web app or an API server with its workers, rather than in one command. `ancha run` starts the app once per attempt against a fresh world. The world drives it: people send mail and messages, the app's own scheduled routes are called, and time passes. Checks read the world's end state and the calls the app made.

The same guide ships with the CLI: `ancha docs apps`.

## Configure ancha.yaml

```yaml ancha.yaml theme={null}
agent:
  start: ./ancha/start.sh
  url: "http://127.0.0.1:{{agent.port}}/api/slack/events"
  ready_url: "http://127.0.0.1:{{agent.port}}/api/health"
  startup_timeout: 180s
  idle: 20s
  setup_calls: true
  clock: node
  env:
    APP_SLACK_TOKEN: "{{slack.token}}"
scenes: [ancha/scenes/*.yaml]
trials: 3
parallel: 1
```

| Field | What it does |
| - | - |
| `agent.start` | Runs once per attempt and starts the app on `$ANCHA_AGENT_PORT`. |
| `agent.url` | Where the app receives the tools' events, such as Slack events or Gmail push messages. |
| `agent.ready_url` | Ancha polls it until it answers 2xx. |
| `agent.startup_timeout` | The start script's whole budget: reset, seed and boot. |
| `agent.idle` | How long the app may look quiet while it still works. Ancha can't see the app's model calls. A scene with `skip_idle` jumps the clock only after this much quiet. |
| `agent.setup_calls` | The app may call the world while it starts, for subscriptions or lookups, before the clock starts. |
| `agent.clock` | `node`: `Date` in the app's Node processes follows the world's clock, for flows that wait days. Needs `start`. |
| `agent.env` | Variables for the app, rendered per attempt, such as `{{slack.url}}`, `{{slack.token}}`, `{{gmail.url}}`, `{{gmail.token}}` and `{{gmail.address}}`. See [Templates](https://docs.ancha.io/reference/ancha-yaml.md#templates). |

`{{agent.port}}` is reserved for the attempt, so the app's health route and event routes need nothing Ancha-specific. `clock: node` covers Node processes only. Apps in other runtimes keep real time: give them short thresholds in their seeded settings instead.

`ancha run --dry-run` checks this file and every scene and starts nothing.

## Write the start script

The script gets a base set from your shell (`PATH`, `HOME`, the locale and the like), Ancha's variables such as `ANCHA_AGENT_PORT` and `ANCHA_TRIAL`, the variables of every tool in the scene and `agent.env`. `ancha connect <tool> --json` lists a tool's variables. Your shell's other variables don't reach it.

* Set database and cache URLs and the app's test secrets in the script.
* For a real key, such as the app's model key, add its name to `agent.pass_env` in `ancha.yaml`, or run with `--pass-env NAME`. Its value stays in your shell and out of the script.

Every attempt is a new world with new URLs and tokens, so every attempt starts the app clean:

1. Restore the database from a template copy: `CREATE DATABASE app_trial TEMPLATE app_base`, with `app_base` migrated once.
2. Flush the caches and queues the tests own.
3. Seed what the flow needs with the app's own seeds, factories or test helpers. Store the world's token where the app keeps its OAuth tokens.
4. `exec` the server. Start workers, such as job queues, from the same script. Ancha stops the whole process group after the attempt.

Keep these in mind:

* Old rows, caches and sync cursors, such as a stored history ID or a last-seen timestamp, make an app silently do nothing in a new world.
* A world's IDs repeat from attempt to attempt, as its seed does. Clear the app's dedupe keys and locks at the start of every attempt. Run attempts one at a time (`parallel: 1`) unless each attempt gets its own database and cache namespace.
* Seed what a real account has, through the app's own functions: the rules and settings its onboarding or settings page creates. A test helper's shortcut, such as a missing default rule or a rule without the ID the settings page would store, changes what the app does.
* A production build boots faster than a development server, and boot time counts against `startup_timeout`. `.ancha/runs/<run>/trial-*/agent.log` has the script's and the app's output.

## Point the app at the tools

* Use the app's base-URL setting if it has one. `ancha connect <tool> --lang ts` or `--lang py` prints the SDK settings.
* If the SDK has no such setting, add a development-only initializer that reads the tool's variable and does nothing when it is unset. Keep app changes that small and say why in a comment.
* Google clients keep only the host of a `rootUrl` set on the client. Ancha routes those calls by their token.
* Gmail reaches an app the way it does in production: the app calls `users.watch`, and the world then pushes Pub/Sub messages to `agent.url`. If the app registers its watch on a schedule, call that route with an `app_calls` entry early in the scene. The world gives the mailbox's address as `GMAIL_ADDRESS`, and `GET /gmail/v1/users/me/profile` returns the same address. See [Gmail](https://docs.ancha.io/tools/gmail.md).
* Identifiers a scene relies on, such as an inbox token, a cron secret or an API key the world sends, should be fixed values your seed creates, because a scene is static.

## Write scenes for the app

```yaml ancha/scenes/stale-question.yaml theme={null}
id: stale-question
title: A customer's question gets an answer in the thread, and a reminder two days later if nobody resolved it
twins: [slack]
start_time: 2026-11-02T15:00:00Z
timeout: 3d
skip_idle: true
seed:
  slack:
    users:
      - { ref: bot, name: support-bot, is_bot: true }
      - { ref: dan, name: dan, real_name: Dan Ito }
    channels:
      - { ref: support, name: support, members: [$bot, $dan] }
world_rules:
  actors:
    - at: 5s
      slack.post: { ref: question, channel: $support, user: $dan, text: "How do I export my invoices?" }
  app_calls:
    - at: 2d1h
      path: /api/cron/stale-questions
      headers: { authorization: Bearer test-cron-secret }
checks:
  - id: answered_in_thread
    state: { slack.message: { channel: $support, from: $bot, in_thread_of: $question } }
  - id: no_deletes
    trace: { not_called: "* /api/chat.delete" }
```

* `start_time` on a Monday suits apps that count business days. `skip_idle: true` jumps the clock while the app and the world are quiet.
* `world_rules.actors` act in a tool at a time, for example `slack.post` or `gmail.send`. See [World rules](https://docs.ancha.io/reference/test-file.md#world-rules).
* `world_rules.app_calls` call the app: its cron routes, or a customer message into its API. Each takes `at`, `method` (default `GET`), `path`, `headers` and `body`, and goes to the origin of `agent.ready_url`.
* `ancha twins seed <tool> --json` prints each tool's seed fields, checks and actors.

### People who answer

Add `people` to the scene for someone who reads what reaches them and answers after a simulated delay. A person with a Gmail account reads mail delivered to that mailbox and replies in the thread:

```yaml theme={null}
people:
  - ref: tom
    name: Tom
    role: a client at another company who answers email himself, briefly, within a day or two
    accounts: { gmail: tom@client.example }
    replies_after: 1d..2d
```

The address must be a mailbox in `seed.gmail.mailboxes`. People call a model: export `OPENROUTER_API_KEY`, or set `ANCHA_PEOPLE_BASE_URL` (ending in `/v1`) and `ANCHA_PEOPLE_API_KEY` for another OpenAI-compatible endpoint that serves OpenRouter's model names. See [People](https://docs.ancha.io/reference/test-file.md#people).

### Make every check able to fail

Run a scene once against a build with the feature broken, or also check the state in between, such as the label the app should have removed. A check can pass for the wrong reason: a reminder that never comes because the scheduled call ran too early proves nothing.

## Turn a production trace into a test

1. `ancha import --offline <trace file>` reads an Ancha Trace IR file, or an OTLP JSON export whose spans carry the bodies of the app's calls to its tools. It writes `scenes/from-traces/trace-<id>/scene.yaml` and `provenance.json` and uploads nothing.
2. It lists the questions one trace can't settle, each with the default the test uses. A default repeats what production did, so answer the ones about what went wrong: add `--answer <id>=<value>` for each and import again with `--force`.
3. Run the scene against the build that failed in production. It should fail the way production did. Then fix the app and run again.

* A trace holds the app's calls, not what started each one. The import lists the calls production made before the first event the test replays: the app's own setup, such as a watch or its labels. The test has them only if the app makes them when it starts, or the start script or an `app_calls` entry does.
* When a test doesn't fail the way production did, `ancha traces diff <trace file> <run>/<trial>` lists the calls only production made, the ones only the attempt made, and the first call where their orders part. A call production made and the attempt didn't, such as labels created at setup or a settings read, usually points at what the seed lacks.

See [`ancha import`](https://docs.ancha.io/cli/import) for its options and output.


## Related topics

- [Test file (scene.yaml)](https://docs.ancha.io/reference/test-file.md)
- [Changelog](https://docs.ancha.io/changelog.md)
- [For coding agents](https://docs.ancha.io/coding-agents.md)
- [Gmail](https://docs.ancha.io/tools/gmail.md)
- [ancha import](https://docs.ancha.io/cli/import.md)


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