The 5-Loop Verification Funnel & Production Feedback Loops
AGBoss (Agent Graph Builder Open Source System) replaces the traditional Unit -> Integration -> E2E testing pyramid with a 5-loop cost and determinism verification funnel closed by two production feedback loops. In classical software engineering, every function is deterministic (f(x) = y); in a multi-agent system, an LLM node costs money, takes seconds, and exhibits variance even at temperature: 0. AGBoss therefore enforces one foundational rule across agboss check, @agboss/core/testing, and @agboss/core/eval: never verify with a paid language model what can be proven by the compiler or checked by deterministic TypeScript; and when you invoke a model, check $0 trajectory invariants and code judges first before calling a calibrated LLM judge.
Complete example: Fail-Fast Cascade from $0 code guards to calibrated @Judge
Section titled “Complete example: Fail-Fast Cascade from $0 code guards to calibrated @Judge”Before looking at each axis of the funnel, here is how Loops 0, 1, and 2 work together in code — combining a deterministic @Tool, a deterministic $0 @Judge (CitationFormatJudge, 0ms), a calibrated DecidedBy.Model @Judge (RefundPolicyGroundedJudge), and a testWith(Application) + testTool + testJudge verification suite:
import { Agent, Application, Budget, BudgetPeriod, BudgetScope, DecidedBy, Decision, EmptyEnvironment, Judge, JudgeModel, JudgeVerdict, Tool, Workflow, WorkflowFinish, WorkflowStart, start, type BudgetHandler, type JudgeHandler, type JudgeInput, type ToolHandler,} from "@agboss/core";import { Integer, Text } from "@agboss/core/dto";import { usd, type Usd } from "@agboss/core/units";
export class RefundTaskDto { @Text({ prompt: "Order ID and customer refund request" }) request!: string;}
export class RefundToolInputDto { @Text({ prompt: "Order ID starting with ORD-" }) orderId!: string;
@Integer({ prompt: "Refund amount in cents" }) amountCents!: number;}
export class RefundReplyDto { @Text({ prompt: "Refund confirmation citing [ORD-id]" }) confirmation!: string;}
@Tool({ name: "issue-refund-tool", description: "Issues a refund for an order", input: RefundToolInputDto, output: RefundReplyDto,})export class IssueRefundTool implements ToolHandler<RefundToolInputDto, RefundReplyDto> { run(input: RefundToolInputDto): Promise<RefundReplyDto> { return Promise.resolve({ confirmation: `Refunded ${String(input.amountCents)} cents for [${input.orderId}]`, }); }}
// Cascade Step 1 ($0, 0ms): Deterministic DecidedBy.Code @Judge runs BEFORE any paid LLM judge@Judge({ name: "citation-format-judge", description: "Verifies deterministically that confirmation contains an [ORD-...] citation", decidedBy: DecidedBy.Code, reply: RefundReplyDto,})export class CitationFormatJudge implements JudgeHandler<RefundReplyDto> { judge(input: JudgeInput<RefundReplyDto>): Promise<JudgeVerdict> { return Promise.resolve( /\[ORD-\d+\]/.test(input.reply.confirmation) ? JudgeVerdict.passed() : JudgeVerdict.failed({ reason: "Confirmation must cite [ORD-<digits>]." }), ); }}
// Cascade Step 2 (Paid Decision Model): Runs only after $0 code invariants pass@Judge({ name: "refund-policy-grounded-judge", description: "Evaluates whether the refund explanation matches tool evidence", model: "typesafe/jev-1.13", reply: RefundReplyDto, deps: [JudgeModel],})export class RefundPolicyGroundedJudge implements JudgeHandler<RefundReplyDto> { constructor(private readonly judgeModel: JudgeModel) {}
async judge(input: JudgeInput<RefundReplyDto>): Promise<JudgeVerdict> { const res = await this.judgeModel.decide({ state: { task: input.task, confirmation: input.reply.confirmation }, questions: { grounded: Decision.noul("Does the confirmation match the issue_refund tool output?"), }, }); return res.grounded.noul >= 0.85 ? JudgeVerdict.passed({ metrics: { grounded: res.grounded.noul } }) : JudgeVerdict.failed({ reason: "Confirmation does not match tool output." }); }}
@WorkflowStart({ name: "refund-workflow-start", description: "Start", input: RefundTaskDto })export class RefundWorkflowStart {}
@WorkflowFinish({ name: "refund-workflow-finish", description: "Finish", output: RefundReplyDto })export class RefundWorkflowFinish {}
@Agent({ name: "refund-specialist-agent", description: "Processes customer refunds using IssueRefundTool", model: "moonshotai/kimi-k2.6", prompt: "Call issue_refund and confirm with [ORD-id].", input: { task: RefundTaskDto }, output: RefundReplyDto, tools: [IssueRefundTool], judges: [CitationFormatJudge, RefundPolicyGroundedJudge], maxRetries: 1,})export class RefundSpecialistAgent {}
@Workflow({ name: "refund-verification-workflow", version: "1.0.0", flow: [ start(RefundWorkflowStart) .returns(RefundTaskDto) .toAgent(RefundSpecialistAgent) .returns(RefundReplyDto) .toFinish(RefundWorkflowFinish), ],})export class RefundVerificationWorkflow {}
@Budget({ name: "funnel-app-budget", scope: BudgetScope.Application, period: BudgetPeriod.Day })export class FunnelAppBudget implements BudgetHandler { maxSpend(): Usd { return usd(50); } }@Budget({ name: "funnel-tenant-budget", scope: BudgetScope.Tenant, period: BudgetPeriod.Day })export class FunnelTenantBudget implements BudgetHandler { maxSpend(): Usd { return usd(5); } }@Budget({ name: "funnel-exec-budget", scope: BudgetScope.Execution })export class FunnelExecBudget implements BudgetHandler { maxSpend(): Usd { return usd(0.5); } }
@Application({ name: "refund-verification-application", version: "1.0.0", environment: EmptyEnvironment, workflows: [RefundVerificationWorkflow], budgets: [FunnelAppBudget, FunnelTenantBudget, FunnelExecBudget],})export class RefundVerificationApplication {}import { expect } from "vitest";import { decideWith, replyWith, testJudge, testTool, testWith } from "@agboss/core/testing";import { CitationFormatJudge, IssueRefundTool, RefundPolicyGroundedJudge, RefundReplyDto, RefundSpecialistAgent, RefundVerificationApplication, RefundWorkflowStart,} from "../src/funnel/refund-verification.components.js";
const test = testWith(RefundVerificationApplication);
test("runs $0 unit runners and cascading judges in Loop 1", async ({ app, mockLlm,}) => { // 1. Isolated unit runners ($0, 1ms): const runTool = testTool(IssueRefundTool); const toolRes = await runTool({ orderId: "ORD-101", amountCents: 2500 }); expect(toolRes.output.confirmation).toContain("[ORD-101]");
const runFormatJudge = testJudge(CitationFormatJudge); const badFormat = await runFormatJudge({ variant: RefundReplyDto, reply: { confirmation: "Refunded without order tag" }, task: "Refund order ORD-101", attempt: 0, }); expect(badFormat.output.passed).toBe(false);
// 2. Full workflow test with scripted LLM & judge cascade ($0, 2ms): mockLlm(RefundSpecialistAgent).thenReturn( replyWith(RefundReplyDto, { confirmation: "Refunded 2500 cents for [ORD-101]" }), ); mockLlm(RefundPolicyGroundedJudge).thenReturn(decideWith({ grounded: 0.96 }));
const result = await app.execute(RefundWorkflowStart, { request: "Refund 2500 cents on ORD-101", }); expect(result.output).toEqual({ confirmation: "Refunded 2500 cents for [ORD-101]", });});Axis 1: What are the 4 objects of verification in AGBoss?
Section titled “Axis 1: What are the 4 objects of verification in AGBoss?”Every failure in a multi-agent system originates in one of 4 distinct architectural layers (7 Architectural Invariants). Mixing them into a single undifferentiated test suite makes failures noisy and expensive to debug:
| # | Object of Verification | What Can Go Wrong | Primary AGBoss Primitives |
|---|---|---|---|
| 1 | Topology & Mesh Contracts (Graph & Mesh Contracts) |
Missing upstream inputs on a branch, reading a stale draft in a review loop ({ about }), forgetting a saga compensator, or breaking a schema consumed by a remote server |
agboss check, $ agboss check --federation, .agboss/types.d.ts, Path Scope, compensateWith / committedBy |
| 2 | Harness Code (Harness Code) |
Bugs in deterministic TypeScript surrounding the model: @Action, DecidedBy.Code @Router & @Arbiter, @Tool, @ToolInterceptor, code guardrails, @PiiPolicy masking, rate-limit queues, retries, and LIFO rollbacks |
10 unit runners, testWith(App), mockLlm, TestConcurrency.inOrder, VirtualClock, instances: 2 |
| 3 | Model & Tool Trajectory (Model & Trajectory) |
The model misunderstands the prompt, skips requiredTools, calls forbiddenTools, passes another tenant’s ID in tool arguments, or hallucinates an ungrounded answer |
toHaveCalledTools, toHaveToolCallMatching, toolInvariant, @Judge, @EvalSuite (pass@k & pass^k) |
| 4 | Memory & Compaction (Memory & Compaction) |
A critical safety constraint disappears after @Compaction, or the agent relies on an outdated fact from an old turn when a @Tool or @Rag returns updated rules |
testCompaction(), multi-turn pastTurn() + turn() in @EvalSuite, { about } staleness invalidation |
Axis 2: How does the 5-Loop Cost & Determinism Funnel work?
Section titled “Axis 2: How does the 5-Loop Cost & Determinism Funnel work?”AGBoss arranges verification into 5 concentric loops ordered strictly by speed (< 1s $\to$ 24/7) and token cost ($0 $\to$ $$):
flowchart TD L0["<b>Loop 0: Static Compiler Proof</b><br/><code>agboss check</code> & <code>--federation</code> · < 1s · $0 · Every save"] L1["<b>Loop 1: Deterministic Harness & Chaos</b><br/><code>npm test</code> (10 Unit Runners + <code>testWith</code> + VCR) · ~3s · $0 · Every commit"] LOCK["<b>Freshness Lock: <code>[vcr.stale-cassette]</code></b><br/>Compares cassette <code>promptHash</code> & <code>schemaHash</code> against current code"] L2["<b>Loop 2: Live-Model PR Smoke Gate</b><br/><code>agboss eval run --runs 5 --baseline</code> · ~1 min · $ · On PR when hash changes"] L3["<b>Loop 3: Nightly Polygon</b><br/><code>agboss eval run --runs 20 --metamorphic --chaos</code> · ~15 min · $$ · Nightly / Pre-release"] L4["<b>Loop 4: Production Telemetry & Mesh Hub</b><br/>PII-scrubbed <code>ExecutionRecord</code> → <code>agboss eval ui</code> · 24/7"]
L0 --> L1 --> LOCK --> L2 --> L3 --> L4 L4 -. "<b>Loop A: Failure Taxonomy & Judge Calibration</b><br/>Tag failures, verify Cohen's κ ≥ 0.80, promote to <code>agboss golden</code>" .-> L2 L2 -. "<b>Loop B: Progressive Crystallization</b><br/>Move settled routes & tool guards from Loop 2 ($ LLM) back to Loop 1 ($0 Code)" .-> L1| Loop | Command / Stage | Latency | LLM Cost | When It Runs | What It Catches |
|---|---|---|---|---|---|
| Loop 0 | agboss check (--federation, --cassettes) |
< 1s |
$0.00 |
Every file save & first CI step | 100% of graph wiring errors ([input.missing-on-path]), stale loop inputs ([input.outdated-on-path]), uncompensated sagas ([flow.uncompensated-action]), and broken cross-repo MCP/A2A schemas |
| Loop 1 | npm test (@agboss/core/testing) |
~3s |
$0.00 |
Every commit (live LLM is never called) | Logic bugs in all 10 component types, workflow branching, parallel quorum races (TestConcurrency.inOrder), ModelFailure retries, 30s lease handoff (instances: 2), and VCR schema integration |
| Loop 2 | $ agboss eval run --runs 5 --baseline |
~1 min |
$ |
Pull Requests that modify promptHash, schemaHash, or tools |
Prompt regressions on the critical smoke suite (K = 5 trials per case) using a Fail-Fast $0 -> $$$ judge cascade and flaky-on-baseline quarantine |
| Loop 3 | $ agboss eval run --runs 20 --metamorphic --chaos |
~15 min |
$$ |
Nightly on main & before releases / model upgrades |
Paraphrase fragility (--metamorphic), multi-turn @Compaction memory loss, live provider fault recovery (--chaos), and silent upstream model quantization drift |
| Loop 4 | ExecutionRecord $\to$ $ agboss eval ui |
24/7 |
— | Continuously on production traffic | Real-world edge cases, new failure modes, @Judge rubric drift (eval calibrate Cohen’s $\kappa$), and candidates for Progressive Crystallization |
Loop 0: Static Compiler Proof (agboss check + --federation) — $0, < 1s
Section titled “Loop 0: Static Compiler Proof (agboss check + --federation) — $0, < 1s”- When: On every file save during development and as the very first gate in CI.
- What it proves: 100% of graph edge types,
Path Scopedominance,{ about }review-loop freshness, saga compensation closure ([flow.uncompensated-action]), prompt cache invariance ([prompt.input-in-instructions]), and cross-service@McpServer/@A2AAgentcontracts ($ agboss check --federation). - Why it exists: Eliminates architectural and contract errors before a single unit test or model token is spent.
Loop 1: Deterministic Harness & Infrastructure Chaos (npm test) — $0, ~3s
Section titled “Loop 1: Deterministic Harness & Infrastructure Chaos (npm test) — $0, ~3s”- When: On every commit. A live LLM is never called in Loop 1.
- What runs inside Loop 1:
- 10 isolated unit runners:
testTool,testToolInterceptor,testAction,testRouter,testArbiter,testRag,testJudge,testCompaction,testPii, andtestGuardrailvalidate individual components in1ms. - Full-graph
testWith(Application)tests: Execute complete workflows against scriptedmockLlmreplies and recorded VCR cassettes (VcrMode.Replay). - Resilience & chaos injection: Deterministically inject
failWith(ModelFailure.RateLimit),ModelFailure.Timeout,ModelFailure.InvalidOutput, andModelFailure.InvalidToolArguments, control parallel race arrival order viaTestConcurrency.inOrder(), and verify multi-pod 30-second lease failover (testWith(App, { instances: 2 })) on aVirtualClock.
- 10 isolated unit runners:
- The Freshness Lock to Loop 2 (
[vcr.stale-cassette]): Every VCR cassette embeds thepromptHashandschemaHashof the steps it recorded. As long as you only refactor TypeScript services,@Actionnodes, or DI wiring, your entire test suite runs for$0on cassettes. The moment you change a single word in a prompt or a field on a@Dto,[vcr.stale-cassette]trips — signaling that the fixture is stale and Loop 2 must run against a live model.
Loop 2: Live-Model PR Smoke Gate (npx agboss eval run --runs 5 --baseline)
Section titled “Loop 2: Live-Model PR Smoke Gate (npx agboss eval run --runs 5 --baseline)”- When: On Pull Requests (triggered automatically when
promptHash,schemaHash, or tool bindings change). - Why
temperature: 0still requires--runs 5(pass@kvspass^k): Modern GPU inference servers use dynamic continuous batching across concurrent requests, andBF16/FP8matrix reductions are non-associative. As a result, the exact same prompt attemperature: 0can take different tool-call trajectories across runs — making single-run (K=1) evaluations statistically unreliable. AGBoss measures bothpass@k(passed at least once in $k$ runs — capability) andpass^k(passed all $k$ of $k$ runs — production reliability). - How Loop 2 maximizes accuracy while minimizing token spend:
- Fail-Fast Cascade inside every trial (
$0 -> $$$,EvalCascade.StopAtFirstFailure):- Step 1 (
$0,0ms): Verify deterministic trajectory invariants (checkTrajectory()) first — wererequiredtools called (toolCall), wereforbiddentools avoided, did tool arguments satisfytoolInvariantpredicates, and did the run stay within its@Budget? - Step 2 (
$0,0ms): Run deterministicDecidedBy.Code@Judgeclasses (CitationFormatJudge,FairDecisionJudge). - Step 3 (
$): Only if Steps 1 and 2 pass does AGBoss invoke the calibrated LLM@Judge, passing it the fulltoolCallstrajectory and finalreply.
- Step 1 (
- Multi-trial reliability (
pass^k) vs cached--baseline(no double spend):- Critical smoke cases (up to 10 cases) run $K = 5$ trials each (
--runs 5), while cases directly touched by the PR run $10\text{–}20$ trials. - Instead of running every eval twice on every PR (once on
main, once on the PR branch),--baselinecompares each case’spass^kagainst the cached baseline JSON artifact from the latest nightlymainrun. - If a case had
5/5(pass^5 = 1.00) onmainand drops in the PR, CI fails with exit code5(--fail-on-regression). If a case already had0 < pass^k < 1onmain, it is automatically quarantined asflaky-on-baseline(CaseComparison.FlakyOnBaseline) so baseline noise never blocks unrelated PRs.
- Critical smoke cases (up to 10 cases) run $K = 5$ trials each (
- Artifact output: Attaches a self-contained single-file
eval-report.html(--html) directly to the Pull Request.
- Fail-Fast Cascade inside every trial (
Loop 3: Nightly Polygon (Regression, --metamorphic, Memory Drift & --chaos)
Section titled “Loop 3: Nightly Polygon (Regression, --metamorphic, Memory Drift & --chaos)”- When: On a nightly schedule on
mainor before a production release / model migration. - What it verifies:
- Full domain benchmark with pinned hosts (
only,quantizations): Runs 30–50+ real domain cases with strict@ModelProviderhost and quantization pinning (quantizations: ["fp8", "bf16"],allowFallbacks: false) and exports a fresh--baselineartifact for tomorrow’s PRs. - Paraphrase invariance (
--metamorphic): Varies the phrasing or language of the user prompt (paraphrasesinevalCase) and verifies that the sequence of@Toolcalls and their semantic arguments remain invariant. - Multi-turn memory &
@Compactionsurvival: UsespastTurn()+turn()to verify that (a) critical negative constraints survive@Compactionsummarization, and (b) fresh@Tool/@Ragevidence overrides outdated facts from earlier turns in the thread. - Live-graph fault injection (
--chaos): Injects transient upstream429s, network timeouts, and malformed tool JSON mid-run on the live graph to prove retry and.onError()resilience under real model nondeterminism. - Silent provider drift detection: Catches unannounced upstream model weight or quantization changes when
maincode has not changed but nightlypass^kdrops.
- Full domain benchmark with pinned hosts (
Axis 3: How do the two production feedback loops improve the graph?
Section titled “Axis 3: How do the two production feedback loops improve the graph?”The verification pipeline does not end at deployment — Loop 4 (ExecutionRecord telemetry) closes two continuous feedback loops back into Loops 2 and 1:
Feedback Loop A: From Production to Golden Sets & @Judge Calibration (Failure Taxonomy Loop)
Section titled “Feedback Loop A: From Production to Golden Sets & @Judge Calibration (Failure Taxonomy Loop)”- PII-scrubbed production traces: In production, workflows persist
ExecutionRecord(Tern) entries intoExecutionRecordRepository, already pseudonymized at the boundary by@PiiPolicy. - Failure taxonomy tagging in Eval UI / Mesh Hub: Engineers open
$ agboss eval uilocally (or the AGBoss Mesh Hub across a fleet), filter failed or high-latency trajectories, and tag root causes with concise taxonomy labels (wrong-tool,forgot-compacted-constraint,policy-hallucination). - Calibrating
@Judgevia Cohen’s kappa ($\kappa \ge 0.80$): On 20–30 real recorded sessions, human domain experts record pass/fail labels (evals/labels/*.jsonl) and run$ agboss eval calibrate --min-kappa 0.80. AGBoss computes pairwise Cohen’s kappa ($\kappa$) and a confusion matrix (TP,FP,TN,FN) for each@Judge(and warns with[judge.same-model-family]if the judge shares the agent’s model family). If $\kappa < 0.80$, you refine the@Judgerubric until automated grading aligns with human judgment. - One-click promotion to Golden Sets (
agboss golden): Real production failures are promoted intogolden/<workflow>/*.yaml(npx agboss golden add) and@EvalSuitecases, becoming permanent regression guards in Loops 2 and 3.
Feedback Loop B: Progressive Crystallization (From Loop 2 back to Loop 1)
Section titled “Feedback Loop B: Progressive Crystallization (From Loop 2 back to Loop 1)”The relationship between pass@k (passed at least once in $k$ trials — capability) and pass^k (passed all $k$ of $k$ trials — production reliability) tells you exactly how to make the graph faster, cheaper, and more deterministic (Progressive Crystallization):
| Observed Eval Signal | What It Means | Architectural Action (Loop 2 $\to$ Loop 1) |
|---|---|---|
High pass@k (100%), Low pass^k (40%) |
The model knows how to solve the task, but slips on 3 of 5 attempts (e.g., calling a @Tool with an invalid filter or missing prefix). |
Do not bloat the system prompt. Add a deterministic @ToolInterceptor (ToolCallDecision.replyToModel) or DecidedBy.Code @InputGuardrail that catches the mistake in 0ms for $0. |
Stable pass^k = 100% across thousands of runs |
agboss describe --json + ExecutionRecord traces show a DecidedBy.Model @Router or @Arbiter making a predictable decision from DTO fields on $>99%$ of runs. |
Crystallize the component from DecidedBy.Model to DecidedBy.Code (or collapse a fixed tool chain into an @Action). Its verification permanently moves from paid Loop 2 (agboss eval) into free, 1ms Loop 1 (npm test). |
Summary in Three Lines
Section titled “Summary in Three Lines”agboss check(--federation) proves statically in< 1s($0) that your graph topology, Path Scope DTOs, saga compensations, and cross-service mesh contracts are sound (see02-path-scope-proofand06-enterprise-agent-mesh).npm test(testWith+ 10 unit runners + hash-locked VCR cassettes) verifies 100% of your TypeScript harness code, chaos recovery, and concurrency deterministically in seconds ($0) on every commit, using a symmetric Spring-styletests/directory mirroringsrc/(see01-hello-agboss).agboss eval(--runs K+--baseline+--ui/ Mesh Hub) measures true model reliability (pass^k), blocks regressions against a compact git-diff-friendly baseline (eval/baseline/main-baseline.json), calibrates judges by Cohen’s $\kappa$, and pinpoints which stochastic nodes are ready to crystallize into deterministic code (see03-support-refund-desk).