Skip to main content
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

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_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.
  • 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_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.
  • 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:
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.

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 for its options and output.