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
ancha.yaml
{{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_envinancha.yaml, or run with--pass-env NAME. Its value stays in your shell and out of the script.
- Restore the database from a template copy:
CREATE DATABASE app_trial TEMPLATE app_base, withapp_basemigrated once. - Flush the caches and queues the tests own.
- 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.
execthe server. Start workers, such as job queues, from the same script. Ancha stops the whole process group after the attempt.
- 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.loghas 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 tsor--lang pyprints 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
rootUrlset 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 toagent.url. If the app registers its watch on a schedule, call that route with anapp_callsentry early in the scene. The world gives the mailbox’s address asGMAIL_ADDRESS, andGET /gmail/v1/users/me/profilereturns the same address. See Gmail. - 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
ancha/scenes/stale-question.yaml
start_timeon a Monday suits apps that count business days.skip_idle: truejumps the clock while the app and the world are quiet.world_rules.actorsact in a tool at a time, for exampleslack.postorgmail.send. See World rules.world_rules.app_callscall the app: its cron routes, or a customer message into its API. Each takesat,method(defaultGET),path,headersandbody, and goes to the origin ofagent.ready_url.ancha twins seed <tool> --jsonprints each tool’s seed fields, checks and actors.
People who answer
Addpeople 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:
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.
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
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 writesscenes/from-traces/trace-<id>/scene.yamlandprovenance.jsonand uploads nothing.- 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. - 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_callsentry 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.
ancha import for its options and output.