ancha.Build plan spec.md

Ancha build plan

Make the other side a one-command job for coding agents.

Version 1 · 2026-10-30 · Product owner: Patrick · For the coding agent: read /spec.md

Contents
Work order for the coding agent that builds Ancha. Version 1, 2026-10-30. Product owner: Patrick. Written by Claude (session "ancha-product-strat") on Patrick's direction. Plain Markdown of this page: https://ancha-build-plan.pages.dev/spec.md

0. Read this first

You are the engineer on Ancha. This page is your work order from the product owner. Follow it in order. Where it conflicts with older plans in the repo (docs/HANDOFF.md, the plan14 ledger, cloud/NEXT.md), this page wins. Where it conflicts with a newer instruction from Patrick in chat, Patrick wins.

1. The point

1.1 The value test

Patrick, 2026-10-30, verbatim:

关键是要抓住一个点 也就是你不能亲自做的事情 或者比如 我让你做一个agent。 这个ancha 要提供的价值必须《= 你整个任务如果这一部分你自己做 占据全任务的30%。 也就是要从 30% 缩成3% 最好还是你自己完成不了的。

In English: when a coding agent builds an agent for someone, Ancha must take over a part of the job that would cost at least 30% of the whole task if the coding agent did it itself, and shrink it to about 3%. Best of all, it should be a part the coding agent cannot do by itself at all.

Judge every feature in this plan by that test. A feature that doesn't move a coding agent's work from about 30% toward about 3%, and doesn't do something the coding agent can't, waits.

1.2 Where the 30% is

The 10-28 lab. Claude built a billing-support agent and tested it the way a developer at a polished agent company would. The code is in ~/ancha-strategy/lab/ and the findings in its NOTES.md.

PieceLines
The agent (agent.py)~110
A fake of the agent's own tools (world.py)~185
The other parties: an LLM customer, manager and judge (sim.py)~80
Tests and fixtures (tests/)~350

Accounts. On 2026-10-30 a coding agent could not sign in to our own demo account: coding agents can't create accounts or type passwords, by policy and because of bot detection. The same holds for Slack workspaces, PagerDuty, Datadog, GitHub orgs, Salesforce sandboxes and Stripe. The official MCP servers for Slack, PagerDuty and Datadog are hosted services behind OAuth (secondary sources, checked 2026-10-30), so an agent built on them can't be pointed at a local mock by changing a base URL.

Fresh-user QA (session ancha-qa, 2026-10-31 UTC). With today's CLI, a new user who already has a world gets from ancha init to a planted business bug caught and a fix proven in about three minutes. The loop works once the world exists. Building the world is the cost.

1.3 The point, in one line

A coding agent can write the agent, and a mock of its own tools. It can't build the other side.

The other side is everything the agent deals with that the coding agent doesn't control:

Ancha builds the other side on demand and grades the agent against it. The coding agent writes what the agent must achieve and who is involved. Ancha does the rest.

1.4 The job, before and after

JobIf the coding agent does itWith AnchaWhat the coding agent writes
Tools to run againstneeds accounts it can't create; real ones can't be reset, copied or brokenstateful twins of the real APIs and MCP servers, fresh for every attempt, no accountsthe tool names
A believable companyhand-made fixtures, thin and inconsistent between toolsone seeded company shared by every toolpick a world, add a few records
The other peopleprompts that leak, decisions parsed from text, stop tokenspeople as world objects: facts gated by the engine, decisions as state, no stop tokens, every model call recorded5–10 lines per person
Time and eventsa hand-written router (it broke twice in the lab)a router and a simulated clock that skips idle time and delivers native eventsnothing
Faultsimpossible on real servicesfaults on the worldone line
Gradingassertions that miss the process; judges that contradict themselveschecks evaluated by the world, including the process (asked, relayed, approved before acting)3–8 checks
Many attemptsorchestration, isolation, cost, flaky rerunsattempts in sealed containers, in parallel; a failed attempt replays exactlyone number

1.5 The metric

North star: the share of a coding agent's effort that goes into setting up tests when it builds an agent.

§10 defines the benchmark. Build it in Phase 0 and rerun it at Gate 5 and Gate 9.

1.6 What we are not building

2. Product shape

2.1 Users

2.2 The journey

This sequence is the product. Commands that don't exist yet are built in the phases below. Names change only with Patrick's OK.

# The engineer tells their coding agent: "test our support bot with Ancha"
npx @ancha-labs/cli init       # detects tools; writes ancha.yaml, a world, a test, the coding-agent skill; ignores .ancha/
ancha login                    # the engineer approves once in the browser (skip it to stay local)
ancha test new "Laura says her CSV exports are empty; only Ben knows the workaround; Mike decides the priority"
ancha run                      # agent on this machine, world in the workspace; prints the pass rate and a console link
ancha explain --json           # why it failed: the check, the conversation, the calls
#   ...the coding agent fixes the agent...
ancha run --on cloud -n 20     # sealed; 20 attempts in parallel on the workspace machine
ancha compare                  # this run against the last good one
pytest                         # the same world as a fixture inside the team's own tests (Phase 7)
# CI: ANCHA_API_KEY=… ancha run --on cloud --min-pass-rate 0.95
# The engineer opens app.ancha.io: every run from every place, with attempts, conversations and checks

2.3 One object model

ObjectWhat it isDefined inConsole
Agentthe code under test: how it starts and how it receives workancha.yamlAgents
Worldtools, seeded company data, people, and rules (actors, pipelines, faults, metrics, monitors, integrations)ancha/worlds/<id>.yamlWorlds
Personsomeone in the world: role, goal, accounts, what they know, what they decide, when they answerinside a worldWorld → People
Testa world (or overrides), what starts an attempt, how many attempts, when one ends, the checksancha/tests/<id>.yamlTests
Runone execution of one or more tests, from any placethe workspaceRuns
Attemptone fresh copy of the world with the agent in it: conversation, calls, end state, check outcomesthe workspaceAttempt
Checka condition on the end state, the calls, or the processinside a testAttempt → Checks

Retire the second vocabulary: a scene becomes a test with an inline world; a trial becomes an attempt; oracle and null become reference agents; companion and fingerprint stay internal. Old files and flags keep working (§9.1).

2.4 Definitions in the repo, results in the workspace

2.5 Where the agent runs

--onThe agent runsSealedToday
local (default)as a process on this machineno: it sees this shell's environment and networkancha run
dockerin a local containeryesancha playground
cloudin a container on the workspace machineyesthe console's Run tasks
pytest or vitestinside the team's own testsas the test runner isnothing yet (Phase 7)

2.6 Workspaces

2.7 The console

The console is the human's window: every run from every source, attempts with a Conversation tab, checks with their evidence, Compare, Run again, and the people on the world page. It is not the operator's tool. The coding agent never needs it.

3. Rules

3.1 You may, without asking

3.2 Ask Patrick first

3.3 Never

3.4 Engineering rules that still hold

These come from the repo's AGENTS.md and still bind you:

3.5 Done means

For every item:

  1. Unit tests cover the new behavior (pnpm exec vitest run <file>), and pnpm typecheck is clean.
  2. The item's acceptance commands ran, and their output is in the ledger.
  3. For CLI changes: pnpm build, pack, install the tarball under a fresh temporary HOME outside the checkout, and run the commands there.
  4. For console changes: checked in a real browser at 1512 and 390 px wide, with screenshots in docs/plan/shots/.
  5. Docs are updated in the same change (after Phase 5, in the single docs source).
  6. Nothing is claimed that you didn't run.

3.6 Reporting

4. Phases

PhaseGoalSessionsGate
0Baseline: housekeeping, the benchmark, today's numbers1–2the baseline table
1People and the router3–4calibration numbers; the lab's three-party failure reproduced
2Process checks, explain, replay2a relay failure explained in one command
3Reference world 1: acme support1–2reference 20/20; every mutant caught
4One entry: files, one runner, targets, hosted parity, recording3–4the journey from a clean machine; runs from every target in the console
5Coding-agent onboarding: init, test drafting, skill, docs, CLI surface1–2first meaningful failing test in 5 minutes or less; benchmark B1 rerun
6Reference world 2 (acme on-call), fidelity, MCP3on-call matrix; fidelity table; MCP clients work unchanged
7pytest plugin and vitest helper1–2the same verdicts in pytest and in ancha run
8Console views1–2Conversation tab and process checks at 1512 and 390
9Benchmark rerun and report1the 30 → 3 table

5. Phase 0: Baseline

5.1 Housekeeping

  1. Save this plan in the repo as docs/plan/BUILD_PLAN.md (from https://ancha-build-plan.pages.dev/spec.md), so it's versioned with the code.
  2. Snapshot the hosted code. In ~/real_ancha/cloud, commit everything on branch plan/p0-snapshot with the message "Snapshot of the hosted code as deployed (gateway; tenant image v9)". Don't push.
  3. In ~/real_ancha, create plan/integration from main. Leave untracked deck sources (website/deck/) alone unless Patrick says otherwise.
  4. Rewrite the top of AGENTS.md:
    • ownership: Patrick owns the product; this plan is the active plan, linked;
    • keep the engineering rules (§3.4);
    • mark docs/HANDOFF.md and the plan14 ledger as history.
  5. Copy the fresh-user QA findings into docs/qa/2026-10-31-fresh-user.md. Take them from /private/tmp/claude-501/-Users-liuyizhou/6694c427-f06a-4873-9be0-bdcd23eb111f/scratchpad/qa/findings.md if it still exists; otherwise use Appendix B.
  6. Create docs/plan/LEDGER.md (§3.6).

5.2 Benchmark harness and baseline

Build the harness of §10 for task B1 only. Run arm A (no Ancha) and arm B-today (the current CLI: worlds and checks, no people), 3 runs each. Ask Patrick before spending more than the phase budget.

5.3 Gate 0

Report the baseline table for arm A and arm B-today: test-setup share, hidden-evaluation pass rate, lying results, wall time, cost.

6. Phase 1: People and the router

6.1 Goal

A world can contain people who:

The agent is woken by native events, and simulated time skips while everyone waits.

6.2 Step 1: seal the control plane (QA finding E2)

Do this first. Today the control API (/_ancha/*) shares the agent's host and port and needs no auth. From inside an attempt, GET /_ancha/envs lists every live environment, and GET /_ancha/envs/<id>/state returns another attempt's world. People's hidden facts will live in that state.

6.3 Step 2: people in the world file

Add the schema to packages/core/src/scene/schema.ts and packages/core/src/world/schema.ts, with the same strictness and shared validation. Example (the $slack_laura, $slack_eng, $github_ben and similar refs are created by the acme-support seed in Phase 3):

yaml
people:
  - ref: laura                     # world-unique; referred to as laura or $person.laura
    name: Laura
    role: customer at Pike Street Dental; uses acme's patient-records product
    goal: Get CSV exports working again before Friday payroll.
    style: brief, friendly, not technical
    accounts:                      # who this person is in each tool; must exist in the seed
      slack: $slack_laura
    listens: [dm, mentions, threads]
    replies_after: 3m..12m         # simulated time per reply, sampled from the attempt seed
    hours: 08:00-18:00 America/Los_Angeles   # optional; replies wait for the next window
    knows:
      - id: workspace
        value: Pike Street Dental
        about: which workspace or account has the problem
      - id: beta_on
        value: turned on the export beta yesterday
        about: recent settings changes or beta features
        match: semantic            # default exact (normalized substring); semantic uses a recorded classifier
      - id: blocked_staff
        value: all 12 front-desk staff use the export for payroll
        number: 12                 # optional; lets decision rules compare
        about: how many people are affected
      - id: payroll_friday
        value: payroll runs Friday and needs the export
        reveal: volunteer          # default when_asked
    can: [slack.post]
  - ref: ben
    name: Ben
    role: developer who owns exports at acme
    goal: Fix real bugs; get the details before digging in.
    accounts: { slack: $slack_ben, github: $github_ben }
    listens: [dm, mentions, { channels: [$slack_eng] }]
    replies_after: 10m..40m
    knows:
      - id: workaround
        value: turn the export beta off and run the export again
        about: a workaround or a fix for empty exports
        needs: [laura.beta_on]     # shared only once Ben knows Laura's beta is on
      - id: root_cause
        value: the beta exporter drops every row when a workspace has more than 50,000 patients
        about: the root cause
        confidential: true         # checks can assert it never reaches a customer
    can: [slack.post, github.comment]
  - ref: mike
    name: Mike
    role: support manager
    goal: Keep priorities honest.
    accounts: { slack: $slack_mike }
    listens: [dm, mentions]
    replies_after: 5m..30m
    decides:
      - id: severity
        options: [p1, p2]
        needs: [laura.blocked_staff]   # without it, Mike asks for it instead of deciding
        choose:                        # deterministic; the first match wins
          - option: p1
            when: { fact: [laura.blocked_staff, gte, 5] }
          - option: p2
        # alternative to choose: guideline: "P1 when five or more people are blocked." (decided by the model, recorded)
    can: [slack.post]

The engine enforces every field below. None of it depends on a prompt asking the model to behave.

(a) Accounts. A person is a user in each listed tool, created by the seed. Unknown refs fail validation.

(b) Routing. An action by the agent reaches a person when it matches their listens:

The router applies the same rules to decide which of the people's actions reach the agent's bot user. Each delivery is a row in world_inbox(person, source_kind, source_id, sim_at).

(c) Replies. For each person with unanswered inbox items, at sim_now + sample(replies_after) (seeded, then moved into hours), the engine calls the model once with:

There are no free-text control tokens. A person may stay silent (empty arrays). The engine writes their messages and actions through the twins as the person's account, so the agent sees native traffic.

(d) Fact gating. Before generating a reply, the engine asks a classifier which locked when_asked facts the latest message to this person asks about, giving it each fact's about text. The answer is a list of fact ids. Only those facts are unlocked and passed to the reply prompt.

(e) Decisions. When a message asks a person to decide (classifier, recorded), or a rule triggers a decision:

Text is never parsed for decisions.

(f) Timing. replies_after is a range of simulated time. Sampling uses the attempt seed, so the same seed gives the same delays. hours moves a reply to the next working window. away and patience can come later.

(g) Knowledge. world_knowledge(person, fact_ref, source, sim_at) records who knows what: their own facts, facts the agent told them, and facts another person told them. A message tells a person a fact when its text matches the fact: exact means a normalized substring of value; semantic means a classifier verdict, recorded. People use the facts they know.

(h) Actions and rules. People act only through can, as twin actions under their own account. Rule actors (world_rules.actors) stay, and their when predicates gain people predicates. Follow the predicate style of WorldPredicateSchema (packages/core/src/world/schema.ts) and packages/server/src/world/predicates.ts.

yaml
{ knows: [ben, laura.beta_on] }       # Ben knows the fact
{ decided: [mike, severity, p1] }     # Mike's recorded decision
{ told: [laura, ben.workaround] }     # alias of knows, for checks that read better

(i) Ending an attempt. An attempt ends at the first of these:

  1. the test's until predicate holds;
  2. the world is quiet (§6.5) and nothing is scheduled before the test's simulated deadline;
  3. the real-time timeout passes.

Nothing a person or the agent writes can end it.

(j) Recording. Each model call writes one line to people.jsonl in the attempt's artifacts:

json
{ "at_sim": "…", "person": "mike", "kind": "reply|gate|decide|match", "model": "…", "prompt_sha256": "…", "input_tokens": 0, "output_tokens": 0, "cost_usd": 0, "output": {} }

The console and explain read it.

(k) Cache and replay.

(l) Models. defaults.people_model in ancha.yaml (default claude-haiku-5-5) can be overridden per world or per person.

(m) Secrecy. Person specs, locked facts, decisions and people.jsonl must never be reachable by the agent under test: not through any route, variable or file in its container. Test this the same way as §6.2.

6.4 Step 3: waking the agent

Input modes are set by agent.input in ancha.yaml; the console's Connect form exposes the same choices.

"Addressed to the bot" uses the routing rules of §6.3 (b), applied to the bot user.

6.5 Step 4: simulated time that skips while everyone waits

6.6 Step 5: calibration

ancha people calibrate <world-file> [--n 50] --json runs probes against every person with the configured model:

ProbeWhat it doesTarget
leakn messages that don't ask about a fact; any reveal (a match on value) is a leak0 of n
recalln messages that ask about each factat least 96% revealed
needsask for a gated fact before its needs are known0 reveals
policydecisions with choosen of n follow the rule
guidelinedecisions with guideline, against a held-out labeled setreport the agreement
consistencythe reply never contradicts the recorded decision (classifier, recorded)n of n
stopnothing a person writes ends an attemptn of n

Publish the numbers for each reference world in docs/product/people.md.

6.7 Gate 1

7. Phase 2: Process checks, explain, replay

7.1 New check kinds

Add these to the test schema. The world evaluates them at capture, like the existing checks (packages/server/src/checks.ts, packages/server/src/world/checks.ts).

yaml
checks:
  - id: asked-laura-for-the-workspace
    revealed: [laura.workspace]                # Laura gave the agent the fact because it asked
  - id: ben-got-the-details
    knows: [ben, laura.beta_on]                # the agent relayed the fact to Ben
  - id: laura-got-the-workaround
    knows: [laura, ben.workaround]
  - id: answered-in-her-thread
    message: { to: laura, in_thread_of: $slack_laura_question, from: $slack_bot }
  - id: priority-before-label
    before: [{ decided: [mike, severity, p1] }, { called: "POST /repos/*/issues/*/labels" }]
  - id: issue-filed-with-details
    github.issue: { repo: $github_records, body_contains: [Pike Street Dental, beta], labels: [p1], count: 1 }
  - id: acked-fast
    within: { event: { pagerduty.incident: { ref: $pagerduty_orders, status: acknowledged } }, of: start, limit: 5m }
  - id: no-root-cause-to-customer
    never: { to: laura, fact: ben.root_cause }
  - id: polite
    judge: { rubric: "Replies to Laura are polite and plain.", votes: 3 }

Semantics:

Every outcome carries expected, actual and evidence: pointers to the messages, decisions, knowledge rows and trace entries that decided it.

7.2 Pass rate

7.3 explain v2

ancha explain <run> [--attempt <id>] --json returns, for each failed check:

json
{
  "check_id": "asked-laura-for-the-workspace",
  "kind": "revealed",
  "expected": "…",
  "actual": "…",
  "evidence": [{ "type": "message|decision|knowledge|call|gate", "sim_at": "…", "from": "…", "to": "…", "where": "…", "excerpt": "…", "id": "…" }],
  "conversation": ["…the 20 lines around the evidence…"],
  "people": { "mike": { "knows": ["…"], "decisions": ["…"] } }
}

7.4 replay

ancha replay <attempt-id> [--agent <cmd>] reruns the attempt with the same seed and the recorded people (--people replay:<id>), using the current agent code or --agent. It prints whether the run diverged and where (the first different agent action), and the new check outcomes.

7.5 Gate 2

On the lab scenario:

8. Phase 3: Reference world 1, acme support

8.1 Files

The world: examples/acme-support/ancha/worlds/acme-support.yaml.

The tests, in examples/acme-support/ancha/tests/:

  1. relay-export-bug.yaml. Laura reports empty CSV exports in #ext-pike-street. It passes when the agent:
    • asks Laura for the workspace and for recent changes (revealed);
    • tells Ben both (knows);
    • relays Ben's workaround to Laura in her thread;
    • files one GitHub issue that includes the workspace and the beta flag;
    • never tells Laura the root cause.
  2. severity-decision.yaml. Laura reports that the front desk can't print daily schedules. Mike decides P1 or P2 from how many staff are blocked, which only Laura knows. It passes when:
    • the agent asks Laura how many people are blocked;
    • it gives Mike that number;
    • Mike's decision exists before the issue is labeled;
    • the label matches the decision;
    • Laura is told the priority.
  3. follow-up-after-fix.yaml. Ben's fix merges two simulated days later (a rule actor). It passes when the agent tells Laura it's fixed after the merge, and not before.

The reference agent: examples/acme-support/agent/, a correct agent in Node 24 using @slack/web-api and @octokit/rest, with input: per_event and the model reached through the workspace key.

The mutants: examples/acme-support/mutants/, five copies of the reference, each with one function changed:

8.2 Twin and starter changes

8.3 Gate 3

A matrix with one row each for the reference and M1–M5, and one column per test. Each cell gives passed/attempts over 20 attempts and which check failed.

9. Phase 4: One entry

9.1 Files, version 2

ancha.yaml:

yaml
version: 2
agent:
  name: support-bot
  run: node agent.mjs                # how to start it, for every target
  input: per_event                   # command | per_event | events | http
  http: { port_env: PORT, task_path: /api/chat, ready_path: /ok }   # only for input: http
  container:                         # docker and cloud targets
    image: node:24-slim
    prepare: npm ci                  # built once per lockfile hash; connecting builds it, not the first run (QA F2)
  provider: anthropic                # the only model host a sealed agent may reach: anthropic | openai | openrouter | none
  env: [ANTHROPIC_API_KEY]           # names only; values come from this shell (local) or `ancha secrets` (cloud)
defaults:
  attempts: 5
  min_pass_rate: 0.9
  people_model: claude-haiku-5-5

The world file is §6.3 plus the existing tools, start_time, seed and world_rules. world_rules is renamed rules; accept both.

The test file:

yaml
version: 2
id: relay-export-bug
world: acme-support                  # or an inline world: block (old scenes become this)
start:                               # what starts an attempt
  message: { from: laura, in: $slack_ext_pike_street, text: "Hi! Our CSV exports have been empty since this morning." }
  # or: task: "plain text, for input: command"
attempts: 10
until: { all: [{ knows: [laura, ben.workaround] }, { called: "POST /repos/*/issues" }] }
deadline: 3d                         # simulated
timeout: 10m                         # real
checks: []                           # §7.1

9.2 One runner

9.3 Targets

9.4 Hosted parity

Gateway: POST /t/:slug/_cli.

Sidecar: /_sidecar/cli spawns the installed ancha.

CLI:

Don't call the console's /api/ from the CLI, and don't relax its same-origin and CSRF checks (backend/src/console/server.ts).

9.5 Recording every run

The run record, version 2:

{ id, started_at, target, source: cli|console|pytest|vitest|ci,
  git: { commit, dirty }, definitions: { agent_sha, world_sha, test_sha },
  tests, attempts, summary }

9.6 Repo-owned objects

9.7 Gate 4

From a clean temporary HOME:

  1. install the packed CLI;
  2. init in examples/acme-support/agent;
  3. login (Patrick approves, or uses a test workspace he provides);
  4. run --on local, then run --on docker, then run --on cloud -n 10.

Then send screenshots of the console's runs list, showing all three with their target and commit, and an attempt from each.

10. The benchmark: 30 → 3

10.1 What it measures

Whether a coding agent can build a multi-party agent and prove that it works, and how much of its effort goes into the proving.

10.2 Tasks

The tasks are bench/tasks/b1-support-relay.md (Phase 0) and bench/tasks/b2-on-call.md (Phase 6). Each task file contains:

10.3 Arms

10.4 Attribution

Classify every tool call in the transcript:

Report, per run, output tokens and wall time per class, and the final lines of code per class.

Test-setup share = test-setup ÷ (agent + test-setup + verify + other), on output tokens. Also show it on time.

10.5 Hidden evaluation

10.6 Output

Store each run in bench/results/<date>/<task>-<arm>-<n>/: the transcript, a repo snapshot, the attribution JSON and the hidden results. Summarize in bench/results/<date>/README.md:

taskarmrunstest-setup share (tokens)test-setup share (time)hidden pass ratelying resultswall timecost

10.7 Targets

11. Phase 5: Coding-agent onboarding

11.1 ancha init

11.2 ancha test new "<intent>"

It drafts a test from one sentence: people, facts, decisions and checks for the current world.

The coding agent reviews and edits the draft. This is the 3%.

11.3 Instructions for coding agents

Write one source, docs/product/coding-agents.md, and generate from it: .claude/skills/ancha/SKILL.md, an AGENTS.md section, and llms.txt at app.ancha.io (QA A2; ancha.io only with Patrick's OK). Keep it under 120 lines:

  1. What Ancha is, in two sentences: the other side.
  2. The loop: describe the scenario, ancha test new, ancha run, ancha explain --json, fix the agent, ancha run --on cloud -n 20, ancha compare, then give the human the ancha open link.
  3. The rule: fix the agent, not the world or the checks. The exception is a check that is wrong for the real API; then say so and show the evidence (QA D1 is why).
  4. The variables each tool gets, and the input modes.
  5. Exit codes and the explain JSON keys.

11.4 Docs, one source

11.5 CLI surface

11.6 npm

11.7 Gate 5

12. Phase 6: Reference world 2, fidelity, MCP

12.1 acme on-call

12.2 Fidelity numbers

12.3 MCP twins

Official MCP servers for Slack, PagerDuty and Datadog are hosted services behind OAuth (secondary sources, 2026-10-30). GitHub's server is open source and takes a host. An agent built on these servers can only be tested if Ancha serves the same tools.

13. Phase 7: pytest and vitest

python
# pip install ancha   (the package name is Patrick's call, §15)
import ancha

@ancha.attempts(10, min_pass_rate=0.9)
def test_relay(world):                     # fixture: a fresh acme-support world for each attempt
    world.start(agent="npm start")         # or run your agent in-process with world.env
    world.wait_until_quiet()
    assert world.people["ben"].knows("laura.beta_on")
    assert world.slack.messages(to="laura", in_thread_of="$slack_laura_question", from_bot=True)
    assert not world.people["laura"].knows("ben.root_cause")

14. Phase 8: Console views

Keep the console's rules: one clear next action, real messages, no raw JSON or YAML on primary screens.

15. Decisions reserved for Patrick

  1. Production deploys (gateway, tenant image), and any change to ancha.io.
  2. Model spend beyond $30 per phase, and benchmark spend.
  3. Turning on hosted model keys (OPENROUTER_PROVISIONING_KEY and MODEL_CREDIT_USD). People in hosted workspaces need them.
  4. The npm license field and the next CLI version: 0.1.1 is staged, and this plan's changes become 0.2.0. Also the PyPI package name.
  5. Test accounts and tokens for the fidelity probes.
  6. Whether --on cloud may upload source by default, or must ask each time. Polished teams will care.
  7. Pricing and plans. None until he says.
  8. Any renaming of the commands in §2.2.
  9. Later worlds: billing and refunds (needs Stripe and email twins; it's the homepage's refund story), sales, and others.

Appendix A: Where things are today (2026-10-30)

AreaWhere
CLI entry and commandspackages/cli/src/{bin,main}.ts, packages/cli/src/commands/*.ts (25 top-level commands)
Scenario runner (ancha run)backend/src/runner/{run,trial,process,http-task,endpoint,agent-env,artifacts,run-state}.ts, backend/src/report.ts, backend/src/judge/ (OpenRouter judge)
Playground service (console and ancha playground)backend/src/playgrounds/{service,model,store,groups,admission,catalog}.ts
Console server and UIbackend/src/console/server.ts (one /api/ handler; same-origin plus an x-ancha-csrf token), frontend/src/{app,world-workspace,attempt-workspace,views}.ts
Agents, input modes, executionbackend/src/{agents,agent-input,agent-detection}.ts, backend/src/execution/** (Harbor driver, relay, gateway)
World enginepackages/server/src/{app,env,store,scheduler,checks,trace}.ts, packages/server/src/world/{actors,predicates,faults,integrations,generators,checks,runtime}.ts
Scripted actors todayworld_rules.actors: at a time (at) or on a condition (when, after, once), doing slack.post, slack.react, github.comment, pagerduty.acknowledge, pagerduty.resolve. No model-driven people.
Events to agentspackages/server/src/events/{outbox,delivery,feed,signing}.ts, packages/server/src/transport/socket.ts
Twins (12)packages/server/src/twins/: slack, github, datadog, pagerduty, hubspot, salesforce, gong, ga4, linkedin-ads, google-ads, chatgpt-ads, open-meteo
Schemaspackages/core/src/scene/{schema,load,refs}.ts, packages/core/src/world/schema.ts, packages/core/src/sandbox.ts (console worlds compile through scene validation), packages/core/src/test-data/, packages/core/src/tools/catalog.ts
Checks todaytrace (called/not_called); slack.message; github.file_at, github.pull, github.production; datadog.monitor, datadog.downtime; pagerduty.incident; world.fault_activated/fault_resolved; judge (scene runner only)
MCPbackend/src/mcp/{slack,github,configuration,entry}.ts
init kit, bundled docs, skillbackend/src/kit/assets/{SKILL.md,llms.txt,docs/*.md,templates/starter/*}
Hostedcloud/gateway/ (Hono: /login, /cli/* device flow, /account, /upload, /clone, /t/:slug/* world server and secrets, console proxy), cloud/tenant/sidecar.mjs (/_sidecar/{health,upload,clone,secrets,seed,restart}), cloud/demo/, cloud/README.md, cloud/NEXT.md
Agent variables (scenario runner)ANCHA_TASK, ANCHA_EVENTS_URL, ANCHA_EVENTS_TOKEN, ANCHA_DONE_URL, ANCHA_CONTEXT_URL, ANCHA_AGENT_PORT, ANCHA_ATTEMPT_ID, ANCHA_ENV_ID, ANCHA_SCENE_ID, ANCHA_STATE_DIR, ANCHA_TRIAL, ANCHA_TRIAL_TOKEN, ANCHA_WORLD_SEED, plus each tool's URL and tokens
Live versionsgateway at app.ancha.io; tenant image v9; @ancha-labs/cli 0.1.0 on npm, 0.1.1 staged in dist/package
Sitewebsite/ (Vercel project ancha, preview ancha-preview); deploy only with Patrick's OK
Earlier research~/ancha-strategy/lab/ (the 10-28 lab), https://ancha-field-notes.pages.dev, https://ancha-world-design.pages.dev

Appendix B: Fresh-user QA findings and where they're fixed

From session ancha-qa, a black-box test as a new coding agent using only public surfaces (2026-10-31 UTC).

IdFindingFixed in
A1ancha.io has no path into the product (no sign-in, docs or install link)§15 (site, Patrick)
A2no llms.txt, robots.txt or sitemap anywhere§11.3
A3the homepage promises a pytest fixture, Stripe, Linear, Notion and Gmail§13; tools are Patrick's call
B1the npm README is an internal build note§11.6
B2npm metadata gaps (UNLICENSED, no repository URL, a public 0.0.0-stage)§11.6
B3install warnings about install scripts not covered by allowScripts§11.6 (document; consider prebuilt binaries)
C1bundled docs are internal notes (a home path, "In this checkout", pnpm)§11.4
C2CLI docs never mention the hosted product§11.4
C3five overlapping workflows, 25 top-level commands, two vocabularies§2.3, §9.2, §11.5
D1the starter check expects GET conversations.list; the official SDK uses POST, so correct code fails§11.1
D2instructions say npx ancha; that npm name isn't ours§11.1
D3explain shows the failed check but not the payload that explains it§7.3
D4no state check for GitHub issues; no negative checks§7.1
D5doctor exits 1 on warnings only§11.5
E1ancha run runs the agent unsealed while the site says it has no network§2.5, §9.3
E2the control API shares the agent's port, with no auth§6.2
E3/_ancha/health reports version 0.0.0§11.5
F1agent add defaults to host execution with no containment§9.2
F2dependencies aren't built on connect, so the first contained run fails§9.1, §9.2
F3.ancha/ isn't ignored; a 342 MB runtime lands in the project§9.3, §11.1
F4local playground tasks can't be graded§9.2
F5papercuts: agent detect needs --cwd; the local world uses the old cast; a shared global image tag§8.2, §9.2

What already works and must keep working: the local loop is fast (init, doctor, run, explain, baseline, compare in minutes); container attempts are really sealed; --help --json and typed errors; documented exit codes.

Appendix C: Competitors, as of 2026-10-28

Facts only, from their docs and our hands-on checks. Use them for direction, not for feature parity.

Appendix D: Glossary