fake/* models: intercept-only deterministic agent/AI testing
Status summary#
Implemented; PR #2523. Both egress paths consult the Project DO's live AI
interceptor for fake/* models; unit tests, a three-turn sarcastic spec
(video in the PR), the ai-intercept example, and the testing-doc paragraph
are all in. dumbagent (separate repo) got server-free protocol/preset subpath
exports plus a pkg.pr.new workflow. Remaining: CI + review.
Ask#
Deterministic, fully-controlled LLM responses for e2e tests/specs. A spec creates
an agent with model: "fake/main", installs a live handler via
itx.ai.intercept(handler), and drives a real multi-turn conversation through
the whole agent loop — debounce, llm-request events, codemode, chat reply —
with the "model" being an in-memory function in the test process.
Decisions (from the grill session)#
- Hook points: both egress paths, small product seams.
itx.ai.runand agent turns are separate code paths; both consult the interceptor. Built-inaiis unshadowable by design and processor deps are constructor-built in the facet DO, so zero-product-change was ruled out (the only zero-change alternative,appendSyntheticProviderOutput, bypasses the turn loop). - Intercept-only. No static preset models in product code.
fake/*is the interception namespace: anyfake/<name>dials the installed live handler (capnweb-connected function in the test process); the test routes on the name. Post-disconnect answering is an explicit non-goal. - Interception scoped to
fake/*, not all models. A turn whose journal saysopenai/*can never have been served by a handler. One registration on the Project DO, mirroringegress.intercept(live, last-writer-wins, session-bound); two consultation points (isolateAiRpcTarget.run; facet DO hops to project DO only forfake/*models — zero overhead otherwise). - The lane exists everywhere, including prd, documented. No gate, no
config, no credential —
fake/*without a handler is a loud recorded failure, identical in every env ("no test-only seams"). - No product presets; responders live test-side. Handlers implement behavior; dumbagent grows semantic-level exports (pure eliza/sarcastic cores, message helpers) importable test-side.
- Adoption: new coverage only; existing paid tests stand. New full-loop
spec + new
ai-interceptitx example (e2eProven: true— first automateditx.ai.runcoverage).agent-chat.specconversion and.llm.tagging are follow-ups. - Handler contract: one
itx.ai.intercept(handler), semantic-level, source-discriminated. Handler receives{ source: "agent-turn" | "ai-run", model, body }; agent turns return{ text, usage? }(or a plain string),ai-runreturns arbitrary JSON passed through verbatim. Omittedusage→ text-length default;onChunkword-split; missing handler → clear recorded error. - Prefix:
fake/. Honest in the journal, matches the provider-prefix convention (openai/…,@cf/…).
Checklist#
- Project DO: live
aiinterceptor state next to the egress interceptor (same last-writer-wins/session-bound semantics), plus an internal consult method callable from the facet DO —interceptAi/consultAiInterceptorinproject-durable-object.ts, slot mirrors#egressInterceptor -
AiRpcTarget(rpc-targets.ts):intercept(handler)+fake/branch inrun()beforeenv.AI.run; docstrings;pnpm generate:itx-api— projectId threaded through both construction sites; contract regenerated - Agent path (
agent-llm-request.tsattempt()):fake/branch after thecallLlmcheck — consult handler, word-splitonChunk, usage default; no handler → recorded attempt failure — depconsultAiInterceptoronAgentProcessorDeps, wired viaprojectStubin the facet DO - Unit tests for the fake branch —
agent-fake-model.test.ts: 5 tests incl. callLlm precedence and usage estimate/verbatim - e2e spec: multi-turn sarcastic conversation through the browser UI,
VIDEO_MODE=1recording —specs/agent-fake-model-chat.spec.ts, three turns in ~20s, exact-reply assertions; video in PR body - itx example
ai-intercept(e2eProven: true) — first matrix-provenitx.ai.runcoverage - docs/testing.md: paragraph in the LLM-cost gap section
- dumbagent (separate repo): semantic exports — protocol split out of
api.ts;
./protocol+./presets/*subpaths, server-free; pushed - Remove
/script(added mid-review by Misha) — its jobs move tocapabilityHost.runScript(operators) anditx.ai.intercept(tests);/exampleand the shared slash-command plumbing stay; processor tests converted to/example
Requirements from Misha#
- Implement in a worktree (this one), draft PR early.
- PR body must include a video of a spec showing a multi-turn conversation with a sarcastic-responding agent.