Testing
Agents look untestable. The model is nondeterministic, the loop is asynchronous, the side effects land on a real filesystem — so most teams either don't test their agents, or test a pile of mocks that proves nothing about production.
Axon's answer: everything is real except the model. The test harness boots your
full runtime — real kernel, real capsule, real tools, real policy, real session log —
against your actual axon.config.ts. The only substitution is inference. There is
nothing else to mock, because everything else is deterministic.
it("answers a greeting", async () => {
const runtime = await Axon({
blueprint: { config: { engine: Mock({ hello: "hi there" }) } },
})
const result = await runtime.axon.request("hello")
expect(result.text).toBe("hi there")
await runtime.shutdown()
})
That's not a unit test of a function. That's your agent — booted, invoked through its public API, shut down. The same runtime that serves production requests just ran on your machine in milliseconds, for free.
Script the model
Mock() replaces inference with a script you write. Map patterns to replies, and the
loop behaves exactly as if the model had said it:
import { Mock, run } from "@arcforge/engines/mock"
// single reply — matched against the last user message
engine: Mock({ "sprint status": "Two issues remain in review." })
// a sequence — one step per loop tick, in order
engine: Mock({
"review the file": [
run(`fs.read("src/index.ts")`), // tick 1: the "model" acts
"The file looks correct.", // tick 2: it reads the result, then speaks
],
})
run() is the interesting one: the scripted step executes real code in the real
capsule, under your real policy. You're not simulating a tool call — the tool runs, the
result enters the session log, and the next tick sees it, exactly as in production. You
choreograph the model's decisions; the entire machinery underneath them is live.
This means you can deterministically test the parts of agent behaviour that are usually untestable: multi-step flows, tool failure handling, policy rejections, what lands in the trace.
What to test
Tools — plain TypeScript, called directly through runtime.axon.tools.*. Assert
on return shapes and guard conditions. Test the logic you own, not the external
services it wraps.
Prompts — render with runtime.axon.prompt() and assert on the output. Catches
broken interpolation, missing sections, stale variable names — the regressions that
silently degrade agent quality.
Flows — invoke through the public API and assert on what actually happened: the
result text, the entries in result.entries, the session log. Fire hooks with
callHook() and assert a reply came back. This is the primary integration test — the
module emits an event, your plugin handles it, the agent runs, the trace proves it.
Failure behaviour — the runtime fails loudly, and you can assert on that too:
it("rejects when no engine is configured", async () => {
const runtime = await Axon()
await expect(runtime.axon.request("hello")).rejects.toThrow(/No Engine Configured/)
await runtime.shutdown()
})
What you don't test is the model. It isn't yours, it isn't deterministic, and no assertion about its prose survives a model upgrade. Test the machine around it — that's the part with correct answers, and it's the part you built.
Agents and modules
Testing an agent — you own the config and the source. Boot the harness, test your
tools, prompts, and flows. Test files live in tests/ at the agent root.
Testing a module — modules have no standalone runtime; tests run from inside an agent with the module installed. Same harness, different assertions: the tool namespace appeared, the prompts render, the hooks trigger the right behaviour in the host.
Where to go
- Agent tests — the full reference for testing agents
- Module tests — the full reference for testing modules
- Mock() — the complete Mock engine API