Testing
Agency comes with a built-in testing framework that makes it easy to test your agents.
Generating fixtures
Generate fixtures for your agent by running
agency test fixtures foo.agencyAgency will execute your node. If there are multiple nodes, it will ask you which node to execute. The return value of the node will be saved as the fixture value.
Test types
There are two test types:
- exact match
- LLM as a judge.
After you have generated the fixture, you can choose what test type you want. If you choose LLM as a judge, you'll need to provide a judge prompt that the LLM can use to judge the result.
Test file
The fixtures command will create a test file with the same name as your agency file, but with the .test.json extension instead
foo.agency → foo.test.jsonRunning tests
Use the agency test command to run the test. You can give it either the path to the agency file or the path to the test.json file.
Test file options
File-level options
A few options live at the top of the file, and apply to the whole file:
skip— skip every test in the file.skipOnCI— skip every test in the file when running in CI.skipReason— a note printed when the file is skipped.defaultTimeoutMs— default timeout for every test, unless a test sets its owntimeoutMs.
{
"skipOnCI": true,
"skipReason": "Needs a local Ollama server",
"defaultTimeoutMs": 30000,
"tests": [ ... ]
}Test-level options
A .test.json file has a top-level tests array, where each entry is one test case. The fixtures command will generate these entries for you.
{
"tests": [
{
"nodeName": "main",
"input": "",
"expectedOutput": "\"hello world\"",
"evaluationCriteria": [{ "type": "exact" }]
}
]
}Required:
nodeName— the node to run.input— the input passed to the node.expectedOutput— the expected return value, JSON-encoded as a string.evaluationCriteria— how to compare the result toexpectedOutput(see below).
Optional:
description— a human-readable description.skip— set totrueto skip this test.skipOnCI— skip only when running in CI (i.e. when theCIenv var is set). Handy for tests that depend on a developer machine, like macOS-only builtins or interactive prompts.retry— number of times to retry before failing. Useful for flaky tests.timeoutMs— per-test timeout in milliseconds. Defaults to 2 minutes and is capped at 5.argv— extra command-line arguments to hand the node. These show up asprocess.argv.slice(2), so you can teststd::argsand other argv-reading code. A JSON array of strings.
Evaluation criteria
There are two ways to judge a result:
Exact match
"evaluationCriteria": [{ "type": "exact" }]Actual output matches the expectedOutput exactly.
LLM Judge
"evaluationCriteria": [
{
"type": "llmJudge",
"judgePrompt": "The greeting can be any hello to Alice; exact wording doesn't matter.",
"desiredAccuracy": 75
}
]Ask an LLM to judge the result. judgePrompt is a string that describes what a correct result looks like. An LLM judge will see how close the actual output is to the expected output and give a score between 1 and 100. The desired accuracy is the minimum score required.
Interrupt handlers
If your node raises interrupts, list the responses in order under interruptHandlers:
"interruptHandlers": [
{ "action": "approve", "expectedMessage": "confirm" }
]action— one of"approve","reject","modify", or"resolve".resolvedValue— the value to return when the action is"resolve".expectedMessage— asserts the interrupt's message matches before responding.
Mocking LLM calls
To keep a test deterministic, you can mock out llm() calls. Set useTestLLMProvider: true and give an ordered list of mocks, one per llm() call:
"useTestLLMProvider": true,
"llmMocks": [
{ "return": "hello there" },
{ "toolCall": { "name": "search", "args": { "q": "cats" } } }
]Each mock is
- a
return(the value the call produces), or - a
toolCall(a tool the model "decides" to call).
If your test runs several agents, you can make llmMocks an object keyed by agent name (the agent's file basename), giving each agent its own queue of mocks. You can also use a "*" key to catch calls from any agent not listed.
"llmMocks": {
"main": [{ "return": "main-summary" }],
"mutatePrompt": [{ "return": { "operations": [] } }]
}Coverage
Want to know which lines of your .agency code your tests actually exercise? Add --coverage:
agency test --coverage tests/Agency tracks every step the runtime executes, and prints a summary when the run finishes:
Agency Coverage Report
======================
stdlib/array.agency 100.0% (76/76 steps)
stdlib/math.agency 33.3% (2/6 steps)
────────────────────────────────────────────────────────────
Total 53.8% (162/301 steps)Other things you can do:
- generate a detailed report with
agency coverage report - generate an HTML report with
agency coverage report --html - enforce minimum coverage in CI with
--threshold/--per-file-threshold
See the coverage CLI reference for the full set of options.