[{"data":1,"prerenderedAt":593},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fv2\u002Fagent\u002Ftesting":3},{"id":4,"title":5,"body":6,"description":16,"extension":587,"meta":588,"navigation":139,"path":589,"seo":590,"stem":591,"__hash__":592},"docs\u002Fdocs\u002Fv2\u002Fagent\u002Ftesting.md","Testing",{"type":7,"value":8,"toc":581},"minimark",[9,13,17,30,230,233,238,244,375,381,384,388,398,408,422,428,525,528,532,542,548,552,577],[10,11,5],"h1",{"id":12},"testing",[14,15,16],"p",{},"Agents look untestable. The model is nondeterministic, the loop is asynchronous, the\nside effects land on a real filesystem — so most teams either don't test their agents,\nor test a pile of mocks that proves nothing about production.",[14,18,19,20,24,25,29],{},"Axon's answer: ",[21,22,23],"strong",{},"everything is real except the model."," The test harness boots your\nfull runtime — real kernel, real capsule, real tools, real policy, real session log —\nagainst your actual ",[26,27,28],"code",{},"axon.config.ts",". The only substitution is inference. There is\nnothing else to mock, because everything else is deterministic.",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-ts shiki shiki-themes dark-plus","it(\"answers a greeting\", async () => {\n    const runtime = await Axon({\n        blueprint: { config: { engine: Mock({ hello: \"hi there\" }) } },\n    })\n\n    const result = await runtime.axon.request(\"hello\")\n    expect(result.text).toBe(\"hi there\")\n\n    await runtime.shutdown()\n})\n","ts","",[26,38,39,72,95,128,134,141,174,203,208,224],{"__ignoreMap":36},[40,41,44,48,52,56,59,63,66,69],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sCudf","it",[40,49,51],{"class":50},"sTNBD","(",[40,53,55],{"class":54},"sKc5r","\"answers a greeting\"",[40,57,58],{"class":50},", ",[40,60,62],{"class":61},"scz_3","async",[40,64,65],{"class":50}," () ",[40,67,68],{"class":61},"=>",[40,70,71],{"class":50}," {\n",[40,73,75,78,82,85,89,92],{"class":42,"line":74},2,[40,76,77],{"class":61},"    const",[40,79,81],{"class":80},"s9McN"," runtime",[40,83,84],{"class":50}," = ",[40,86,88],{"class":87},"sYbnZ","await",[40,90,91],{"class":46}," Axon",[40,93,94],{"class":50},"({\n",[40,96,98,102,105,108,110,113,116,119,122,125],{"class":42,"line":97},3,[40,99,101],{"class":100},"s3F5K","        blueprint:",[40,103,104],{"class":50}," { ",[40,106,107],{"class":100},"config:",[40,109,104],{"class":50},[40,111,112],{"class":100},"engine:",[40,114,115],{"class":46}," Mock",[40,117,118],{"class":50},"({ ",[40,120,121],{"class":100},"hello:",[40,123,124],{"class":54}," \"hi there\"",[40,126,127],{"class":50}," }) } },\n",[40,129,131],{"class":42,"line":130},4,[40,132,133],{"class":50},"    })\n",[40,135,137],{"class":42,"line":136},5,[40,138,140],{"emptyLinePlaceholder":139},true,"\n",[40,142,144,146,149,151,153,155,158,161,163,166,168,171],{"class":42,"line":143},6,[40,145,77],{"class":61},[40,147,148],{"class":80}," result",[40,150,84],{"class":50},[40,152,88],{"class":87},[40,154,81],{"class":100},[40,156,157],{"class":50},".",[40,159,160],{"class":100},"axon",[40,162,157],{"class":50},[40,164,165],{"class":46},"request",[40,167,51],{"class":50},[40,169,170],{"class":54},"\"hello\"",[40,172,173],{"class":50},")\n",[40,175,177,180,182,185,187,190,193,196,198,201],{"class":42,"line":176},7,[40,178,179],{"class":46},"    expect",[40,181,51],{"class":50},[40,183,184],{"class":100},"result",[40,186,157],{"class":50},[40,188,189],{"class":100},"text",[40,191,192],{"class":50},").",[40,194,195],{"class":46},"toBe",[40,197,51],{"class":50},[40,199,200],{"class":54},"\"hi there\"",[40,202,173],{"class":50},[40,204,206],{"class":42,"line":205},8,[40,207,140],{"emptyLinePlaceholder":139},[40,209,211,214,216,218,221],{"class":42,"line":210},9,[40,212,213],{"class":87},"    await",[40,215,81],{"class":100},[40,217,157],{"class":50},[40,219,220],{"class":46},"shutdown",[40,222,223],{"class":50},"()\n",[40,225,227],{"class":42,"line":226},10,[40,228,229],{"class":50},"})\n",[14,231,232],{},"That's not a unit test of a function. That's your agent — booted, invoked through its\npublic API, shut down. The same runtime that serves production requests just ran on\nyour machine in milliseconds, for free.",[234,235,237],"h2",{"id":236},"script-the-model","Script the model",[14,239,240,243],{},[26,241,242],{},"Mock()"," replaces inference with a script you write. Map patterns to replies, and the\nloop behaves exactly as if the model had said it:",[31,245,247],{"className":33,"code":246,"language":35,"meta":36,"style":36},"import { Mock, run } from \"@arcforge\u002Fengines\u002Fmock\"\n\n\u002F\u002F single reply — matched against the last user message\nengine: Mock({ \"sprint status\": \"Two issues remain in review.\" })\n\n\u002F\u002F a sequence — one step per loop tick, in order\nengine: Mock({\n    \"review the file\": [\n        run(`fs.read(\"src\u002Findex.ts\")`),   \u002F\u002F tick 1: the \"model\" acts\n        \"The file looks correct.\",         \u002F\u002F tick 2: it reads the result, then speaks\n    ],\n})\n",[26,248,249,273,277,283,308,312,317,327,337,353,364,370],{"__ignoreMap":36},[40,250,251,254,256,259,261,264,267,270],{"class":42,"line":43},[40,252,253],{"class":87},"import",[40,255,104],{"class":50},[40,257,258],{"class":100},"Mock",[40,260,58],{"class":50},[40,262,263],{"class":100},"run",[40,265,266],{"class":50}," } ",[40,268,269],{"class":87},"from",[40,271,272],{"class":54}," \"@arcforge\u002Fengines\u002Fmock\"\n",[40,274,275],{"class":42,"line":74},[40,276,140],{"emptyLinePlaceholder":139},[40,278,279],{"class":42,"line":97},[40,280,282],{"class":281},"sOLPB","\u002F\u002F single reply — matched against the last user message\n",[40,284,285,289,292,294,296,299,302,305],{"class":42,"line":130},[40,286,288],{"class":287},"sqDPy","engine",[40,290,291],{"class":50},": ",[40,293,258],{"class":46},[40,295,118],{"class":50},[40,297,298],{"class":54},"\"sprint status\"",[40,300,301],{"class":100},":",[40,303,304],{"class":54}," \"Two issues remain in review.\"",[40,306,307],{"class":50}," })\n",[40,309,310],{"class":42,"line":136},[40,311,140],{"emptyLinePlaceholder":139},[40,313,314],{"class":42,"line":143},[40,315,316],{"class":281},"\u002F\u002F a sequence — one step per loop tick, in order\n",[40,318,319,321,323,325],{"class":42,"line":176},[40,320,288],{"class":287},[40,322,291],{"class":50},[40,324,258],{"class":46},[40,326,94],{"class":50},[40,328,329,332,334],{"class":42,"line":205},[40,330,331],{"class":54},"    \"review the file\"",[40,333,301],{"class":100},[40,335,336],{"class":50}," [\n",[40,338,339,342,344,347,350],{"class":42,"line":210},[40,340,341],{"class":46},"        run",[40,343,51],{"class":50},[40,345,346],{"class":54},"`fs.read(\"src\u002Findex.ts\")`",[40,348,349],{"class":50},"),   ",[40,351,352],{"class":281},"\u002F\u002F tick 1: the \"model\" acts\n",[40,354,355,358,361],{"class":42,"line":226},[40,356,357],{"class":54},"        \"The file looks correct.\"",[40,359,360],{"class":50},",         ",[40,362,363],{"class":281},"\u002F\u002F tick 2: it reads the result, then speaks\n",[40,365,367],{"class":42,"line":366},11,[40,368,369],{"class":50},"    ],\n",[40,371,373],{"class":42,"line":372},12,[40,374,229],{"class":50},[14,376,377,380],{},[26,378,379],{},"run()"," is the interesting one: the scripted step executes real code in the real\ncapsule, under your real policy. You're not simulating a tool call — the tool runs, the\nresult enters the session log, and the next tick sees it, exactly as in production. You\nchoreograph the model's decisions; the entire machinery underneath them is live.",[14,382,383],{},"This means you can deterministically test the parts of agent behaviour that are usually\nuntestable: multi-step flows, tool failure handling, policy rejections, what lands in\nthe trace.",[234,385,387],{"id":386},"what-to-test","What to test",[14,389,390,393,394,397],{},[21,391,392],{},"Tools"," — plain TypeScript, called directly through ",[26,395,396],{},"runtime.axon.tools.*",". Assert\non return shapes and guard conditions. Test the logic you own, not the external\nservices it wraps.",[14,399,400,403,404,407],{},[21,401,402],{},"Prompts"," — render with ",[26,405,406],{},"runtime.axon.prompt()"," and assert on the output. Catches\nbroken interpolation, missing sections, stale variable names — the regressions that\nsilently degrade agent quality.",[14,409,410,413,414,417,418,421],{},[21,411,412],{},"Flows"," — invoke through the public API and assert on what actually happened: the\nresult text, the entries in ",[26,415,416],{},"result.entries",", the session log. Fire hooks with\n",[26,419,420],{},"callHook()"," and assert a reply came back. This is the primary integration test — the\nmodule emits an event, your plugin handles it, the agent runs, the trace proves it.",[14,423,424,427],{},[21,425,426],{},"Failure behaviour"," — the runtime fails loudly, and you can assert on that too:",[31,429,431],{"className":33,"code":430,"language":35,"meta":36,"style":36},"it(\"rejects when no engine is configured\", async () => {\n    const runtime = await Axon()\n    await expect(runtime.axon.request(\"hello\")).rejects.toThrow(\u002FNo Engine Configured\u002F)\n    await runtime.shutdown()\n})\n",[26,432,433,452,466,509,521],{"__ignoreMap":36},[40,434,435,437,439,442,444,446,448,450],{"class":42,"line":43},[40,436,47],{"class":46},[40,438,51],{"class":50},[40,440,441],{"class":54},"\"rejects when no engine is configured\"",[40,443,58],{"class":50},[40,445,62],{"class":61},[40,447,65],{"class":50},[40,449,68],{"class":61},[40,451,71],{"class":50},[40,453,454,456,458,460,462,464],{"class":42,"line":74},[40,455,77],{"class":61},[40,457,81],{"class":80},[40,459,84],{"class":50},[40,461,88],{"class":87},[40,463,91],{"class":46},[40,465,223],{"class":50},[40,467,468,470,473,475,478,480,482,484,486,488,490,493,496,498,501,503,507],{"class":42,"line":97},[40,469,213],{"class":87},[40,471,472],{"class":46}," expect",[40,474,51],{"class":50},[40,476,477],{"class":100},"runtime",[40,479,157],{"class":50},[40,481,160],{"class":100},[40,483,157],{"class":50},[40,485,165],{"class":46},[40,487,51],{"class":50},[40,489,170],{"class":54},[40,491,492],{"class":50},")).",[40,494,495],{"class":100},"rejects",[40,497,157],{"class":50},[40,499,500],{"class":46},"toThrow",[40,502,51],{"class":50},[40,504,506],{"class":505},"sxaxS","\u002FNo Engine Configured\u002F",[40,508,173],{"class":50},[40,510,511,513,515,517,519],{"class":42,"line":130},[40,512,213],{"class":87},[40,514,81],{"class":100},[40,516,157],{"class":50},[40,518,220],{"class":46},[40,520,223],{"class":50},[40,522,523],{"class":42,"line":136},[40,524,229],{"class":50},[14,526,527],{},"What you don't test is the model. It isn't yours, it isn't deterministic, and no\nassertion about its prose survives a model upgrade. Test the machine around it — that's\nthe part with correct answers, and it's the part you built.",[234,529,531],{"id":530},"agents-and-modules","Agents and modules",[14,533,534,537,538,541],{},[21,535,536],{},"Testing an agent"," — you own the config and the source. Boot the harness, test your\ntools, prompts, and flows. Test files live in ",[26,539,540],{},"tests\u002F"," at the agent root.",[14,543,544,547],{},[21,545,546],{},"Testing a module"," — modules have no standalone runtime; tests run from inside an\nagent with the module installed. Same harness, different assertions: the tool namespace\nappeared, the prompts render, the hooks trigger the right behaviour in the host.",[234,549,551],{"id":550},"where-to-go","Where to go",[553,554,555,564,571],"ul",{},[556,557,558,563],"li",{},[559,560,562],"a",{"href":561},"\u002Fdocs\u002Fv2\u002Fagent\u002Ftests","Agent tests"," — the full reference for testing agents",[556,565,566,570],{},[559,567,569],{"href":568},"\u002Fdocs\u002Fv2\u002Fmodules\u002Ftests","Module tests"," — the full reference for testing modules",[556,572,573,576],{},[559,574,242],{"href":575},"\u002Fdocs\u002Fv2\u002Fagent\u002Fengines\u002Fmock"," — the complete Mock engine API",[578,579,580],"style",{},"html pre.shiki code .sCudf, html code.shiki .sCudf{--shiki-default:#DCDCAA}html pre.shiki code .sTNBD, html code.shiki .sTNBD{--shiki-default:#D4D4D4}html pre.shiki code .sKc5r, html code.shiki .sKc5r{--shiki-default:#CE9178}html pre.shiki code .scz_3, html code.shiki .scz_3{--shiki-default:#569CD6}html pre.shiki code .s9McN, html code.shiki .s9McN{--shiki-default:#4FC1FF}html pre.shiki code .sYbnZ, html code.shiki .sYbnZ{--shiki-default:#C586C0}html pre.shiki code .s3F5K, html code.shiki .s3F5K{--shiki-default:#9CDCFE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sOLPB, html code.shiki .sOLPB{--shiki-default:#6A9955}html pre.shiki code .sqDPy, html code.shiki .sqDPy{--shiki-default:#C8C8C8}html pre.shiki code .sxaxS, html code.shiki .sxaxS{--shiki-default:#D16969}",{"title":36,"searchDepth":74,"depth":74,"links":582},[583,584,585,586],{"id":236,"depth":74,"text":237},{"id":386,"depth":74,"text":387},{"id":530,"depth":74,"text":531},{"id":550,"depth":74,"text":551},"md",{},"\u002Fdocs\u002Fv2\u002Fagent\u002Ftesting",{"title":5,"description":16},"docs\u002Fv2\u002Fagent\u002Ftesting","pGj7iNCjlRKPohsLyS1X2eJ6KcJWYSatVVnYoELMjro",1785671805718]