@@ -53,21 +53,25 @@ Emit a JSON object for numeric scores or multi-aspect results:
5353
5454``` json
5555{
56+ "pass" : true ,
5657 "score" : 1.0 ,
57- "assertions" : [
58- { "text" : " Answer contains correct value (42)" , "passed" : true }
58+ "reason" : " Answer contains the correct value." ,
59+ "checks" : [
60+ { "text" : " Answer contains correct value (42)" , "pass" : true , "reason" : " 42 appears in the output." }
5961 ]
6062}
6163```
6264
6365| Output Field | Type | Description |
6466| -------------| ------| -------------|
67+ | ` pass ` | ` boolean ` | Aggregate pass/fail decision |
6568| ` score ` | ` number ` | 0.0 to 1.0 |
66- | ` assertions ` | ` Array<{ text, passed, evidence? }> ` | Per-aspect results with verdict and optional evidence |
69+ | ` reason ` | ` string ` | Explanation for the aggregate decision |
70+ | ` checks ` | ` Array<{ text, pass, score?, reason, evidence? }> ` | Optional per-aspect results with verdict, optional score, reason, and evidence |
6771
6872### Plain-text output (exit-code convention)
6973
70- For simple pass/fail checks, skip the JSON protocol entirely. The exit code determines the score and stdout becomes the assertion text:
74+ For simple pass/fail checks, skip the JSON protocol entirely. The exit code determines the score and stdout becomes the check text:
7175
7276| Exit code | Score | Verdict |
7377| -----------| -------| ---------|
@@ -112,37 +116,42 @@ import json, sys
112116data = json.load(sys.stdin)
113117output = data.get("output") or ""
114118
115- assertions = []
119+ checks = []
116120
117121if "42" in output :
118- assertions .append({"text" : " Output contains correct value (42)" , "passed ": True})
122+ checks .append({"text" : " Output contains correct value (42)" , "pass ": True, "reason": "42 appears in the output" })
119123else :
120- assertions .append({"text" : " Output does not contain expected value (42)" , "passed ": False})
124+ checks .append({"text" : " Output contains correct value (42)" , "pass ": False, "reason": "42 is missing from the output" })
121125
122- passed = sum(1 for a in assertions if a["passed "])
123- score = passed / len(assertions ) if assertions else 0.0
126+ passed = sum(1 for check in checks if check["pass "])
127+ score = passed / len(checks ) if checks else 0.0
124128
125129print(json.dumps({
130+ " pass " : passed == len(checks),
126131 " score " : score,
127- " assertions " : assertions,
132+ " reason " : f"{passed}/{len(checks)} checks passed",
133+ " checks " : checks,
128134}))
129135```
130136
131137The repo-local helper in ` examples/features/sdk-python/ ` wraps the same contract for that example checkout:
132138
133139``` python
134- from agentv_py.grader import Assertion , ScriptGraderResult, define_script
140+ from agentv_py.grader import Check , ScriptGraderResult, define_script
135141
136142
137143def evaluate (context ):
138144 candidate = context.output or " "
139145 passed = " 42" in candidate
140146 return ScriptGraderResult(
147+ pass_ = passed,
141148 score = 1.0 if passed else 0.0 ,
142- assertions = [
143- Assertion(
149+ reason = " Answer contains the correct value" if passed else " Answer is missing the correct value" ,
150+ checks = [
151+ Check(
144152 text = " Output contains correct value (42)" ,
145- passed = passed,
153+ pass_ = passed,
154+ reason = " 42 appears in the output" if passed else " 42 is missing from the output" ,
146155 )
147156 ],
148157 )
@@ -162,19 +171,21 @@ import { readFileSync } from "fs";
162171const data = JSON .parse (readFileSync (" /dev/stdin" , " utf-8" ));
163172const output: string = data .output ?? " " ;
164173
165- const assertions : Array <{ text: string ; passed : boolean }> = [];
174+ const checks : Array <{ text: string ; pass : boolean ; reason : string }> = [];
166175
167176if (output .includes (" 42" )) {
168- assertions .push ({ text: " Output contains correct value (42)" , passed : true });
177+ checks .push ({ text: " Output contains correct value (42)" , pass : true , reason: " 42 appears in the output " });
169178} else {
170- assertions .push ({ text: " Output does not contain expected value (42)" , passed : false });
179+ checks .push ({ text: " Output contains correct value (42)" , pass : false , reason: " 42 is missing from the output " });
171180}
172181
173- const passed = assertions .filter (a => a . passed ).length ;
182+ const passed = checks .filter (check => check . pass ).length ;
174183
175184console .log (JSON .stringify ({
176- score: passed > 0 ? 1.0 : 0.0 ,
177- assertions ,
185+ pass: passed === checks .length ,
186+ score: checks .length === 0 ? 0.0 : passed / checks .length ,
187+ reason: ` ${passed }/${checks .length } checks passed ` ,
188+ checks ,
178189}));
179190```
180191
@@ -197,18 +208,20 @@ import { defineScriptGrader } from '@agentv/sdk';
197208
198209export default defineScriptGrader(({ output, criteria }) => {
199210 const outputText = output ?? '';
200- const assertions : Array<{ text: string; passed : boolean }> = [];
211+ const checks : Array<{ text: string; pass : boolean; reason: string }> = [];
201212
202213 if (outputText.includes(criteria)) {
203- assertions .push({ text: 'Output matches expected outcome', passed : true });
214+ checks .push({ text: 'Output matches expected outcome', pass : true, reason: 'Criteria text appears in the output.' });
204215 } else {
205- assertions .push({ text: 'Output does not match expected outcome', passed : false });
216+ checks .push({ text: 'Output matches expected outcome', pass : false, reason: 'Criteria text is missing from the output.' });
206217 }
207218
208- const passed = assertions .filter(a => a.passed ).length;
219+ const passed = checks .filter(check => check.pass ).length;
209220 return {
210- score: assertions.length === 0 ? 0 : passed / assertions.length,
211- assertions,
221+ pass: checks.length > 0 && passed === checks.length,
222+ score: checks.length === 0 ? 0 : passed / checks.length,
223+ reason: ` ${passed}/${checks.length} checks passed`,
224+ checks,
212225 };
213226});
214227```
@@ -240,7 +253,7 @@ describe('welcome banner', () => {
240253});
241254```
242255
243- Then use AgentV's built-in Vitest adapter as the `script` command. The adapter copies verifier files into a temporary workspace-local path when needed, runs Vitest in `workspace_path`, reads the JSON reporter output, and maps each test outcome to an AgentV assertion :
256+ Then use AgentV's built-in Vitest adapter as the ` script ` command. The adapter copies verifier files into a temporary workspace-local path when needed, runs Vitest in ` workspace_path ` , reads the JSON reporter output, and maps each test outcome to an AgentV check :
244257
245258``` yaml
246259assert :
@@ -253,7 +266,7 @@ AgentV infers the Vitest adapter for verifier-looking files such as `*.test.ts`,
253266
254267# ## Lower-Level Workspace Helpers
255268
256- For tiny one-off file checks, `defineWorkspaceGrader` can resolve the workspace path, read files relative to the workspace, build assertions , and aggregate the score :
269+ For tiny one-off file checks, `defineWorkspaceGrader` can resolve the workspace path, read files relative to the workspace, build checks , and aggregate the score :
257270
258271` ` ` typescript
259272#!/usr/bin/env bun
@@ -269,7 +282,7 @@ export default defineWorkspaceGrader(async ({ workspace }) => [
269282
270283Prefer Vitest verifiers when the checks naturally fit `expect(...)`. Use `defineWorkspaceGrader` when you need a very small custom script, custom weighting, or details that do not map cleanly to individual test outcomes.
271284
272- **SDK exports:** `defineScriptGrader`, `defineVitestWorkspaceGrader`, `defineWorkspaceGrader`, `Message`, `ToolCall`, `Trace`, `TraceSummary`, `ScriptGraderInput`, `ScriptGraderResult`, `Workspace`, `WorkspaceAssertion `
285+ **SDK exports:** `defineScriptGrader`, `defineVitestWorkspaceGrader`, `defineWorkspaceGrader`, `Message`, `ToolCall`, `Trace`, `TraceSummary`, `ScriptGraderInput`, `ScriptGraderResult`, `ScriptGraderCheck`, ` Workspace`, `WorkspaceCheck `
273286
274287# # Target Access
275288
@@ -303,15 +316,19 @@ export default defineScriptGrader(async ({ input, output }) => {
303316 .join('\n ');
304317 const outputText = output ?? '';
305318 const target = createTargetClient();
306- if (!target) return { score: 0, assertions: [{ text : 'Target not configured', passed: false }] };
319+ if (!target) return { pass: false, score: 0, reason : 'Target not configured' };
307320
308321 const response = await target.invoke({
309322 question: ` Is this relevant to: ${inputText}? Response: ${outputText}`,
310323 systemPrompt : ' Respond with JSON: { "relevant": true/false }'
311324 });
312325
313326 const result = JSON.parse(response.rawText ?? '{}');
314- return { score : result.relevant ? 1.0 : 0.0 };
327+ return {
328+ pass : result.relevant === true,
329+ score : result.relevant === true ? 1.0 : 0.0,
330+ reason : result.relevant === true ? 'Response is relevant' : 'Response is not relevant',
331+ };
315332});
316333```
317334
@@ -392,30 +409,32 @@ import { execFileSync } from "child_process";
392409const input = JSON .parse (readFileSync (" /dev/stdin" , " utf-8" ));
393410const cwd = input .workspace_path ;
394411
395- const assertions : Array <{ text: string ; passed : boolean }> = [];
412+ const checks : Array <{ text: string ; pass : boolean ; reason : string }> = [];
396413
397414// Stage 1: Install dependencies
398415try {
399416 execFileSync (" npm" , [" install" ], { cwd , stdio: " pipe" });
400- assertions .push ({ text: " npm install passed " , passed : true });
401- } catch { assertions .push ({ text: " npm install failed " , passed : false }); }
417+ checks .push ({ text: " npm install" , pass : true , reason: " npm install passed " });
418+ } catch { checks .push ({ text: " npm install" , pass : false , reason: " npm install failed " }); }
402419
403420// Stage 2: Typecheck
404421try {
405422 execFileSync (" npx" , [" tsc" , " --noEmit" ], { cwd , stdio: " pipe" });
406- assertions .push ({ text: " typecheck passed " , passed : true });
407- } catch { assertions .push ({ text: " typecheck failed " , passed : false }); }
423+ checks .push ({ text: " typecheck" , pass : true , reason: " typecheck passed " });
424+ } catch { checks .push ({ text: " typecheck" , pass : false , reason: " typecheck failed " }); }
408425
409426// Stage 3: Run tests
410427try {
411428 execFileSync (" npm" , [" test" ], { cwd , stdio: " pipe" });
412- assertions .push ({ text: " tests passed " , passed : true });
413- } catch { assertions .push ({ text: " tests failed " , passed : false }); }
429+ checks .push ({ text: " tests" , pass : true , reason: " tests passed " });
430+ } catch { checks .push ({ text: " tests" , pass : false , reason: " tests failed " }); }
414431
415- const passed = assertions .filter (a => a . passed ).length ;
432+ const passed = checks .filter (check => check . pass ).length ;
416433console .log (JSON .stringify ({
417- score: assertions .length > 0 ? passed / assertions .length : 0 ,
418- assertions ,
434+ pass: checks .length > 0 && passed === checks .length ,
435+ score: checks .length > 0 ? passed / checks .length : 0 ,
436+ reason: ` ${passed }/${checks .length } checks passed ` ,
437+ checks ,
419438}));
420439```
421440
0 commit comments