eve 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +14 -0
  3. package/dist/src/cli/banner.d.ts +6 -3
  4. package/dist/src/cli/banner.js +1 -1
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/dev/tui/agent-header.js +1 -1
  7. package/dist/src/cli/run.js +1 -1
  8. package/dist/src/evals/runner/run-evals.d.ts +1 -1
  9. package/dist/src/execution/sandbox/bindings/docker.js +1 -1
  10. package/dist/src/execution/sandbox/bindings/microsandbox-lifecycle.js +1 -1
  11. package/dist/src/execution/sandbox/bindings/microsandbox-runtime.d.ts +2 -1
  12. package/dist/src/execution/sandbox/bindings/microsandbox-runtime.js +1 -1
  13. package/dist/src/execution/sandbox/bindings/vercel-errors.d.ts +1 -0
  14. package/dist/src/execution/sandbox/bindings/vercel-errors.js +1 -2
  15. package/dist/src/execution/sandbox/bindings/vercel.d.ts +0 -5
  16. package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
  17. package/dist/src/harness/input-requests.d.ts +20 -1
  18. package/dist/src/harness/input-requests.js +1 -1
  19. package/dist/src/harness/tool-loop.js +1 -1
  20. package/dist/src/internal/application/package.js +1 -1
  21. package/dist/src/protocol/message.d.ts +11 -2
  22. package/dist/src/protocol/message.js +2 -2
  23. package/dist/src/public/next/index.d.ts +1 -7
  24. package/dist/src/public/next/index.js +1 -1
  25. package/dist/src/public/next/vercel-output-config.js +1 -1
  26. package/dist/src/setup/scaffold/create/project.js +1 -1
  27. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  28. package/dist/src/setup/scaffold/create/web-template.js +27 -4
  29. package/dist/src/setup/scaffold/update/channels.js +1 -1
  30. package/docs/README.md +19 -5
  31. package/docs/agent-config.md +16 -19
  32. package/docs/channels/custom.mdx +24 -18
  33. package/docs/channels/discord.mdx +25 -13
  34. package/docs/channels/eve.mdx +24 -6
  35. package/docs/channels/github.mdx +41 -10
  36. package/docs/channels/linear.mdx +29 -36
  37. package/docs/channels/overview.mdx +16 -4
  38. package/docs/channels/slack.mdx +59 -32
  39. package/docs/channels/teams.mdx +19 -7
  40. package/docs/channels/telegram.mdx +23 -9
  41. package/docs/channels/twilio.mdx +28 -6
  42. package/docs/concepts/context-control.md +24 -41
  43. package/docs/concepts/default-harness.md +19 -9
  44. package/docs/concepts/execution-model-and-durability.md +14 -14
  45. package/docs/concepts/security-model.md +32 -8
  46. package/docs/concepts/sessions-runs-and-streaming.md +11 -9
  47. package/docs/connections.mdx +49 -29
  48. package/docs/evals/assertions.mdx +30 -29
  49. package/docs/evals/cases.mdx +10 -10
  50. package/docs/evals/judge.mdx +14 -14
  51. package/docs/evals/overview.mdx +15 -17
  52. package/docs/evals/reporters.mdx +19 -7
  53. package/docs/evals/running.mdx +2 -2
  54. package/docs/evals/targets.mdx +15 -4
  55. package/docs/getting-started.mdx +72 -31
  56. package/docs/guides/auth-and-route-protection.md +18 -12
  57. package/docs/guides/client/continuations.mdx +5 -13
  58. package/docs/guides/client/messages.mdx +2 -2
  59. package/docs/guides/client/output-schema.mdx +2 -2
  60. package/docs/guides/client/overview.mdx +4 -4
  61. package/docs/guides/client/streaming.mdx +6 -5
  62. package/docs/guides/deployment.md +22 -20
  63. package/docs/guides/dev-tui.md +85 -13
  64. package/docs/guides/dynamic-capabilities.md +24 -8
  65. package/docs/guides/dynamic-workflows.md +33 -12
  66. package/docs/guides/frontend/nextjs.mdx +28 -14
  67. package/docs/guides/frontend/nuxt.mdx +21 -4
  68. package/docs/guides/frontend/overview.mdx +11 -11
  69. package/docs/guides/frontend/sveltekit.mdx +20 -3
  70. package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
  71. package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
  72. package/docs/guides/hooks.md +14 -60
  73. package/docs/guides/instrumentation.md +24 -18
  74. package/docs/guides/remote-agents.md +19 -15
  75. package/docs/guides/session-context.md +12 -12
  76. package/docs/guides/state.md +25 -9
  77. package/docs/instructions.mdx +18 -10
  78. package/docs/{introduction.md → introduction.mdx} +14 -12
  79. package/docs/reference/cli.md +99 -53
  80. package/docs/reference/meta.json +1 -1
  81. package/docs/reference/project-layout.md +39 -32
  82. package/docs/reference/typescript-api.md +1 -1
  83. package/docs/responsible-use.md +14 -0
  84. package/docs/sandbox.mdx +25 -19
  85. package/docs/schedules.mdx +16 -14
  86. package/docs/skills.mdx +8 -6
  87. package/docs/subagents.mdx +39 -13
  88. package/docs/tools.mdx +13 -9
  89. package/docs/tutorial/connect-a-warehouse.mdx +16 -7
  90. package/docs/tutorial/first-agent.mdx +21 -11
  91. package/docs/tutorial/guard-the-spend.mdx +11 -9
  92. package/docs/tutorial/how-it-runs.mdx +10 -8
  93. package/docs/tutorial/query-sample-data.mdx +8 -8
  94. package/docs/tutorial/remember-definitions.mdx +5 -5
  95. package/docs/tutorial/run-analysis.mdx +13 -11
  96. package/docs/tutorial/ship-it.mdx +70 -14
  97. package/docs/tutorial/team-playbooks.mdx +33 -10
  98. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  title: "Connections"
3
- description: "Expose external MCP and OpenAPI servers to the model: with connection tokens the model never sees."
3
+ description: "Expose external MCP and OpenAPI servers to the model, with connection tokens the model never sees."
4
4
  ---
5
5
 
6
- A connection wires an agent into an external server you don't author: an MCP server (Linear, GitHub, a warehouse) or any HTTP API with an OpenAPI document. Eve handles the parts you'd otherwise hand-roll: it discovers the remote tools, surfaces them to the model, and brokers auth.
6
+ A connection wires an agent into an external server you don't author, either an MCP server (Linear, GitHub, a warehouse) or any HTTP API with an OpenAPI document. Eve handles the parts you'd otherwise hand-roll, discovering the remote tools, surfacing them to the model, and brokering auth.
7
7
 
8
8
  Connections live under `agent/connections/`. The runtime name comes from the filename, so `agent/connections/linear.ts` registers as `"linear"`. The model never sees a connection's URL or credentials. It discovers tools through the built-in `connection__search` and calls them by their qualified name, `connection__<connection>__<tool>` (e.g. `connection__linear__list_issues`).
9
9
 
10
10
  ## MCP connections
11
11
 
12
- `defineMcpClientConnection` points at an MCP server. All you have to supply is a `url` and a `description`:
12
+ `defineMcpClientConnection` points at an MCP server. Supply a `url` and a `description`:
13
13
 
14
14
  ```ts title="agent/connections/linear.ts"
15
15
  import { defineMcpClientConnection } from "eve/connections";
@@ -23,11 +23,11 @@ export default defineMcpClientConnection({
23
23
  });
24
24
  ```
25
25
 
26
- The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model leans on it to decide which connection to query.
26
+ The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model uses it to decide which connection to query.
27
27
 
28
28
  ### Static-token auth
29
29
 
30
- `getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets: an env var, a secrets manager, an internal vault, or your own OAuth exchange. If the token has a known TTL, set `expiresAt` (milliseconds since epoch) and Eve refreshes ahead of time rather than waiting for a `401`.
30
+ `getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets, including an env var, a secrets manager, an internal vault, or your own OAuth exchange. If the token has a known TTL, set `expiresAt` (milliseconds since epoch) and Eve refreshes ahead of time rather than waiting for a `401`.
31
31
 
32
32
  When `getToken` is the only auth, `principalType` defaults to `"app"`: one shared credential keyed across all sessions. Switch to `principalType: "user"` when each end-user carries their own token.
33
33
 
@@ -35,7 +35,7 @@ Eve resolves and caches connection tokens per step; they never land in conversat
35
35
 
36
36
  ### No auth
37
37
 
38
- Drop `auth` entirely for servers that need no token, like a localhost server during development or a public one:
38
+ Drop `auth` entirely for servers that need no token, such as a localhost server during development or a public one:
39
39
 
40
40
  ```ts
41
41
  export default defineMcpClientConnection({
@@ -44,9 +44,11 @@ export default defineMcpClientConnection({
44
44
  });
45
45
  ```
46
46
 
47
+ We recommend using no-auth connections only for services that are intentionally public, local-only, or otherwise protected outside Eve. Do not use no-auth connections for sensitive third-party services.
48
+
47
49
  ### Headers
48
50
 
49
- Reach for `headers` when the server wants a non-Bearer scheme (an API-key header) or some extra configuration. Headers stack on top of `auth`:
51
+ Use `headers` when the server wants a non-Bearer scheme (an API-key header) or extra configuration. Headers stack on top of `auth`:
50
52
 
51
53
  ```ts
52
54
  export default defineMcpClientConnection({
@@ -58,7 +60,7 @@ export default defineMcpClientConnection({
58
60
 
59
61
  ### Tool filters
60
62
 
61
- To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools just don't appear in `connection__search`:
63
+ To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools do not appear in `connection__search`:
62
64
 
63
65
  ```ts
64
66
  export default defineMcpClientConnection({
@@ -84,11 +86,13 @@ export default defineMcpClientConnection({
84
86
  });
85
87
  ```
86
88
 
87
- `never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume here is the same human-in-the-loop flow covered in [Tools](./tools).
89
+ `never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume is the same human-in-the-loop flow covered in [Tools](./tools).
90
+
91
+ For connection tools that can create, modify, delete, transmit, purchase, message, or access sensitive data, use approval, tool allow-lists, or other safeguards appropriate to the action.
88
92
 
89
93
  ## OpenAPI connections
90
94
 
91
- `defineOpenAPIConnection` turns any OpenAPI 3.x or Swagger 2.0 document into connection tools, one per operation. Pass an HTTPS URL Eve fetches at runtime, or an inline parsed object:
95
+ `defineOpenAPIConnection` turns any OpenAPI 3.x document into connection tools, one per operation. Pass an HTTPS URL Eve fetches at runtime, or an inline parsed object:
92
96
 
93
97
  ```ts title="agent/connections/petstore.ts"
94
98
  import { defineOpenAPIConnection } from "eve/connections";
@@ -104,10 +108,10 @@ Each operation becomes `connection__<connection>__<operationId>` (e.g. `connecti
104
108
 
105
109
  `auth`, `headers`, and `approval` work exactly as they do for MCP. There are two fields specific to OpenAPI:
106
110
 
107
- | Field | Purpose |
108
- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
109
- | `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry or Swagger `schemes`/`host`/`basePath`. |
110
- | `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools. |
111
+ | Field | Purpose |
112
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------- |
113
+ | `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry. |
114
+ | `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools. |
111
115
 
112
116
  ## Interactive OAuth via Vercel Connect
113
117
 
@@ -128,7 +132,7 @@ export default defineMcpClientConnection({
128
132
 
129
133
  ## Self-hosted interactive OAuth
130
134
 
131
- Running your own OAuth? Use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form. Eve mints a callback URL, parks the turn on a framework-owned webhook, and resumes once the token comes back. No Vercel Connect required. Interactive auth is always `principalType: "user"`, and the factory pins that for you.
135
+ To run your own OAuth, use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form and needs no Vercel Connect. Eve mints a callback URL, parks (durably suspends) the turn on a framework-owned webhook, and resumes once the token comes back. Interactive auth is always `principalType: "user"`, and the factory pins that for you.
132
136
 
133
137
  ```ts title="agent/connections/linear.ts"
134
138
  import {
@@ -168,9 +172,18 @@ export default defineMcpClientConnection({
168
172
  });
169
173
  ```
170
174
 
171
- `getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event. Set `url` for redirect/device flows, `userCode` for a device code, `instructions` as the call to action when there's no URL, and `displayName` for the human-readable provider name channels show on the sign-in affordance (e.g. "Salesforce"). Drop `resume` when the provider keeps flow state server-side, so nothing has to cross the step boundary.
175
+ `getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event. Its fields:
176
+
177
+ | Field | Purpose |
178
+ | -------------- | ----------------------------------------------------------------------------------------- |
179
+ | `url` | The authorize URL for redirect or device flows. |
180
+ | `userCode` | The device code, for device flows. |
181
+ | `instructions` | The call to action when there's no URL. |
182
+ | `displayName` | Human-readable provider name channels show on the sign-in affordance (e.g. "Salesforce"). |
172
183
 
173
- `displayName` is presentation-only — the connection's path-derived name still keys the authorization scope, token cache, and callback URL. You can also set `displayName` on the `auth` definition itself (e.g. `auth: { ...connect("sfdc"), displayName: "Salesforce" }`); that definition-level value wins over one the strategy stamps on the challenge, and channels fall back to title-casing the connection name when neither is set.
184
+ Drop `resume` when the provider keeps flow state server-side, so nothing has to cross the step boundary.
185
+
186
+ `displayName` is presentation-only. The connection's path-derived name still keys the authorization scope, token cache, and callback URL. You can also set `displayName` on the `auth` definition itself (e.g. `auth: { ...connect("sfdc"), displayName: "Salesforce" }`); that definition-level value wins over one the strategy stamps on the challenge, and channels fall back to title-casing the connection name when neither is set.
174
187
 
175
188
  ### Signaling authorization state
176
189
 
@@ -192,24 +205,31 @@ To narrow a caught error, use `isConnectionAuthorizationRequiredError(err)` and
192
205
 
193
206
  ### Handling a revoked token mid-call
194
207
 
195
- `getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is just a tool error the model sees a failure and the cached bearer sticks around. Instead, map a provider `401` to `ctx.requireAuth()` (or rethrow `ConnectionAuthorizationRequiredError`). Eve then evicts the rejected token from its per-step cache and re-runs the consent flow with a fresh one, exactly as it does for a connection whose server rejects the bearer.
208
+ `getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is only a tool error, so the model sees a failure and the cached bearer sticks around. Instead, map a provider `401` to `ctx.requireAuth()` (or rethrow `ConnectionAuthorizationRequiredError`). Eve then evicts the rejected token from its per-step cache and re-runs the consent flow with a fresh one, exactly as it does for a connection whose server rejects the bearer.
196
209
 
197
210
  ```ts title="agent/tools/list_issues.ts"
198
- async execute(_input, ctx) {
199
- const { token } = await ctx.getToken();
200
- const res = await fetch("https://api.linear.app/graphql", {
201
- headers: { authorization: `Bearer ${token}` },
202
- });
203
- // The grant was revoked since getToken ran: re-challenge instead of
204
- // returning a dead-token error to the model.
205
- if (res.status === 401) ctx.requireAuth();
206
- return await res.json();
207
- }
211
+ import { defineTool } from "eve/tools";
212
+ import { z } from "zod";
213
+
214
+ export default defineTool({
215
+ description: "List open Linear issues.",
216
+ inputSchema: z.object({}),
217
+ async execute(_input, ctx) {
218
+ const { token } = await ctx.getToken();
219
+ const res = await fetch("https://api.linear.app/graphql", {
220
+ headers: { authorization: `Bearer ${token}` },
221
+ });
222
+ // The grant was revoked since getToken ran: re-challenge instead of
223
+ // returning a dead-token error to the model.
224
+ if (res.status === 401) ctx.requireAuth();
225
+ return await res.json();
226
+ },
227
+ });
208
228
  ```
209
229
 
210
230
  ### Authorization and approval together
211
231
 
212
- A tool can require both sign-in (`auth`) and a human approval. Today the model's approval gate runs before the tool's `execute`, so the order the user sees is **approve, then sign in**. Eve records the approval on session state the moment it's granted, and that record survives the sign-in park so when the turn resumes after authorization, the tool is not put through approval a second time. You get one approval and one sign-in, never a double prompt.
232
+ A tool can require both sign-in (`auth`) and a human approval. The model's approval gate runs before the tool's `execute`, so the order the user sees is **approve, then sign in**. Eve records the approval on session state the moment it's granted, and that record survives the sign-in park, so when the turn resumes after authorization the tool is not put through approval again. You get one approval and one sign-in, never a double prompt.
213
233
 
214
234
  ## What to read next
215
235
 
@@ -3,37 +3,38 @@ title: "Assertions"
3
3
  description: "Run-level methods, t.check value assertions, the matcher mini-language, and gate vs soft severity."
4
4
  ---
5
5
 
6
- Assertions are how an eval grades what its `test(t)` function produced. Each one **records** a result onto `t` and returns a chainable handle the runner reads the recorded results to compute the verdict, so a single run reports every failing assertion rather than dying on the first. There are two deterministic surfaces: run-level methods on `t`, and `t.check` for grading a specific value. For model-graded assertions, see [Judge](./judge).
6
+ Assertions are how an eval grades what its `test(t)` function produced. Each one **records** a result onto `t` and returns a chainable handle. The runner reads the recorded results to compute the verdict, so a single run reports every failing assertion rather than dying on the first. There are two deterministic surfaces: run-level methods on `t`, and `t.check` for grading a specific value. For model-graded assertions, see [Judge](./judge).
7
7
 
8
8
  ## Run-level assertions
9
9
 
10
- Run-level assertions read the whole run, so they take no value. They are methods on `t` and gate by default.
11
-
12
- | Assertion | Asserts |
13
- | --------------------------------------------------- | --------------------------------------------------------------------------------- |
14
- | `t.completed()` | The run did not fail and did not park on unanswered HITL input |
15
- | `t.didNotFail()` | No terminal failure and no `turn.failed`/`step.failed` events (parked runs pass) |
16
- | `t.waiting()` | The run parked on HITL input (for approval-shaped evals) |
17
- | `t.messageIncludes(token)` | Joined assistant text contains `token` (string or RegExp) |
18
- | `t.outputEquals(value)` / `t.outputMatches(schema)` | Deep equality / Standard Schema (e.g. Zod) validation of the parsed output |
19
- | `t.calledTool(name, opts?)` | A matching tool call happened (`input`, `output`, `isError`, `times` constraints) |
20
- | `t.notCalledTool(name)` | No call to `name` |
21
- | `t.toolOrder([...names])` | Tool names appear in order (other calls may interleave) |
22
- | `t.usedNoTools()` | No tool calls at all |
23
- | `t.maxToolCalls(n)` | At most `n` tool calls |
24
- | `t.noFailedActions()` | No tool, subagent, or skill action reported a failure |
25
- | `t.calledSubagent(name, opts?)` | A subagent delegation happened (`remoteUrl`, `output` constraints) |
26
- | `t.event(predicate, label)` | Escape hatch: any predicate over the typed event stream |
27
-
28
- `t.completed()` subsumes `t.didNotFail()` reach for `completed` unless you specifically want to allow a parked run.
10
+ Run-level assertions read the whole run, so they take no value. They are methods on `t` and gate by default. Several key off whether a run **parked**: paused on an unanswered human-in-the-loop (HITL) input request, waiting for an approval or answer before it can continue.
11
+
12
+ | Assertion | Asserts |
13
+ | --------------------------------------------------- | --------------------------------------------------------------------------------------- |
14
+ | `t.completed()` | The run did not fail and did not park on unanswered HITL input |
15
+ | `t.didNotFail()` | No terminal failure and no `turn.failed`/`step.failed` events (parked runs pass) |
16
+ | `t.waiting()` | The run parked on HITL input (for approval-shaped evals) |
17
+ | `t.messageIncludes(token)` | Joined assistant text contains `token` (string or RegExp) |
18
+ | `t.outputEquals(value)` / `t.outputMatches(schema)` | Deep equality or Standard Schema (e.g. Zod) validation of the agent's structured output |
19
+ | `t.calledTool(name, opts?)` | A matching tool call happened (`input`, `output`, `isError`, `times` constraints) |
20
+ | `t.notCalledTool(name)` | No call to `name` |
21
+ | `t.toolOrder([...names])` | Tool names appear in order (other calls may interleave) |
22
+ | `t.usedNoTools()` | No tool calls at all |
23
+ | `t.maxToolCalls(n)` | At most `n` tool calls |
24
+ | `t.noFailedActions()` | No tool, subagent, or skill action reported a failure |
25
+ | `t.calledSubagent(name, opts?)` | A subagent delegation happened (`remoteUrl`, `output` constraints) |
26
+ | `t.event(predicate, label)` | Escape hatch: any predicate over the typed event stream |
27
+
28
+ `t.completed()` subsumes `t.didNotFail()`, so reach for `completed` unless you specifically want to allow a parked run. The structured output that `t.outputEquals` and `t.outputMatches` read is the agent's structured output (see the [output schema guide](../guides/client/output-schema)).
29
29
 
30
30
  ```ts
31
31
  await t.send("What is the weather in Brooklyn?");
32
32
  t.completed();
33
33
  t.calledTool("get_weather");
34
- t.usedNoTools(); // mutually exclusive with the line above — pick the one you mean
35
34
  ```
36
35
 
36
+ `t.calledTool` and `t.usedNoTools` are mutually exclusive; assert one or the other, never both in the same run.
37
+
37
38
  ## Value assertions with `t.check`
38
39
 
39
40
  `t.check(value, assertion)` grades an explicit value against a builder from `eve/evals/expect`. The value can be `t.reply`, a turn's `.message`, parsed JSON, or any local you computed:
@@ -54,11 +55,11 @@ t.check(t.reply, similarity("Sunny, 72F")); // fuzzy 0–1 Levenshtein (soft)
54
55
  | `matches(schema)` | validates against a Standard Schema | gate |
55
56
  | `similarity(expected)` | normalized Levenshtein similarity, 1 = identical | soft |
56
57
 
57
- Pick the cheapest builder that captures what "correct" means. When exact match is too strict but a judge model is overkill, `similarity` is the middle ground; for nuanced grading, reach for the [judge](./judge).
58
+ Pick the cheapest builder that captures what "correct" means. When exact match is too strict but a judge model is overkill, `similarity` is the middle ground. For nuanced grading, reach for the [judge](./judge).
58
59
 
59
60
  ## The matcher mini-language
60
61
 
61
- `t.calledTool` and `t.calledSubagent` take a matcher object `{ input, output, isError, times }` for tools, `{ remoteUrl, output }` for subagents. Each field accepts a literal (objects partial-deep-match), a RegExp, or a function. A matcher function receives the value and returns either a boolean (acts as a predicate) or an expected value to compare against (handy for runner-assigned values like environment-provided URLs):
62
+ `t.calledTool` and `t.calledSubagent` take a matcher object: `{ input, output, isError, times }` for tools, `{ remoteUrl, output }` for subagents. Each field accepts a literal (objects partial-deep-match), a RegExp, or a function. A matcher function receives the value and returns either a boolean (acts as a predicate) or an expected value to compare against (handy for runner-assigned values like environment-provided URLs):
62
63
 
63
64
  ```ts
64
65
  t.calledTool("bash", { input: { command: /^pwd/ }, isError: false, times: 1 });
@@ -73,9 +74,9 @@ t.calledSubagent("weather", {
73
74
 
74
75
  ## Run state and derived facts
75
76
 
76
- A turn that leaves the session open for a next message is the normal end state of a successful turn. Parking on unanswered HITL input is tracked separately that is what `t.completed()` and `t.waiting()` key off.
77
+ Beyond the raw `t.events` stream, the runner derives typed facts the assertions read: tool calls (name, input, output, error state), subagent calls, and HITL input requests. A turn that leaves the session open for a next message is the normal end state of a successful turn; parking on unanswered HITL input is tracked separately, and that is what `t.completed()` and `t.waiting()` key off.
77
78
 
78
- Beyond the raw `t.events` stream, the runner derives typed facts the assertions read: tool calls (name, input, output, error state), subagent calls, and HITL input requests. The built-in assertions cover almost everything; when you need to read the stream directly, `t.event(predicate, label)` is the escape hatch:
79
+ The built-in assertions cover almost everything. When you need to read the stream directly, `t.event(predicate, label)` is the escape hatch:
79
80
 
80
81
  ```ts
81
82
  t.event(
@@ -87,11 +88,11 @@ t.event(
87
88
 
88
89
  ## Severity
89
90
 
90
- Every assertion returns a chainable handle. Severity rides on the assertion there is no separate thresholds map to keep in sync.
91
+ Every assertion returns a chainable handle. Severity rides on the assertion, so there is no separate thresholds map to keep in sync.
91
92
 
92
- - `.gate(threshold?)` hard. A miss marks the eval `failed` and `eve eval` exits non-zero.
93
- - `.soft(threshold?)` tracked data. A below-threshold miss marks the eval `scored`, fatal only under `--strict`. With no threshold, it is tracked-only and never fails.
94
- - `.atLeast(threshold)` soft with a bar (equivalent to `.soft(threshold)`).
93
+ - `.gate(threshold?)` is hard. A miss marks the eval `failed` and `eve eval` exits non-zero.
94
+ - `.soft(threshold?)` is tracked data. A below-threshold miss marks the eval `scored`, fatal only under `--strict`. With no threshold, it is tracked-only and never fails.
95
+ - `.atLeast(threshold)` is soft with a bar (equivalent to `.soft(threshold)`).
95
96
 
96
97
  The defaults are chosen so you rarely set severity. Run-level methods and `includes`/`equals`/`matches` are gates; `similarity` and every `t.judge.*` assertion are soft. Annotate only when you deviate:
97
98
 
@@ -3,11 +3,11 @@ title: "Cases"
3
3
  description: "Author single-turn and multi-turn evals with test(t), and fan one file out over a dataset."
4
4
  ---
5
5
 
6
- Each eval file is one graded case. The runner executes its `test(t)` function against the target, captures every event, and computes a verdict from the [assertions](./assertions) you recorded. Every eval single-turn, multi-turn, HITL, or dataset-driven — is the same shape: one `async test(t)` function that drives the agent and asserts inline.
6
+ Each eval file is one graded case by default, and a single file can fan out over a dataset by default-exporting an array (covered below). The runner executes each `test(t)` function against the target, captures every event, and computes a verdict from the [assertions](./assertions) you recorded. Every eval shares one shape, whether single-turn, multi-turn, human-in-the-loop (HITL), or dataset-driven: one `async test(t)` function that drives the agent and asserts inline.
7
7
 
8
8
  ## Single-turn evals
9
9
 
10
- The common case sends one turn and asserts on the reply. `t.send(input)` resolves once the turn settles; `t.reply` is the last assistant message:
10
+ The common case sends one turn and asserts on the reply. `t.send(input)` resolves once the turn settles, and `t.reply` is the last assistant message:
11
11
 
12
12
  ```ts title="evals/weather/brooklyn-forecast.eval.ts"
13
13
  import { defineEval } from "eve/evals";
@@ -22,7 +22,7 @@ export default defineEval({
22
22
  });
23
23
  ```
24
24
 
25
- Some evals only care about behavior, not text assert on the run and skip the content check entirely:
25
+ Some evals only care about behavior, not text. Assert on the run and skip the content check entirely:
26
26
 
27
27
  ```ts title="evals/weather/no-tools-for-greetings.eval.ts"
28
28
  import { defineEval } from "eve/evals";
@@ -43,7 +43,7 @@ Identity is the file path, so directories are the grouping mechanism. `evals/wea
43
43
  ```text
44
44
  evals/
45
45
  ├── weather/
46
- │ ├── shared.ts # helpers not an eval
46
+ │ ├── shared.ts # helpers, not an eval
47
47
  │ ├── brooklyn-forecast.eval.ts
48
48
  │ └── no-tools-for-greetings.eval.ts
49
49
  └── smoke.eval.ts
@@ -51,7 +51,7 @@ evals/
51
51
 
52
52
  ## Multi-turn evals
53
53
 
54
- Drive several turns in sequence branching, HITL approvals, structured output, attachments, multiple sessions. Because assertions live in the function, an intermediate value is just a local variable: judge a draft before the next turn overwrites it, then keep going.
54
+ Drive several turns in sequence for branching, HITL approvals, structured output, attachments, or multiple sessions. Because assertions live in the function, an intermediate value is a local variable. Judge a draft before the next turn overwrites it, then keep going.
55
55
 
56
56
  ```ts title="evals/draft-then-send.eval.ts"
57
57
  import { defineEval } from "eve/evals";
@@ -69,7 +69,7 @@ export default defineEval({
69
69
  });
70
70
  ```
71
71
 
72
- Bespoke preconditions that no built-in assertion expresses are plain `throw`s a thrown error marks the eval `failed` with the message in the result:
72
+ For a precondition no built-in assertion expresses, `throw`. A thrown error marks the eval `failed` with the message in the result:
73
73
 
74
74
  ```ts title="evals/session-continuity.eval.ts"
75
75
  import { defineEval } from "eve/evals";
@@ -96,22 +96,22 @@ export default defineEval({
96
96
 
97
97
  `t` drives the primary session; `t.newSession()` returns an independent `EveEvalSession` against the same target, whose events feed the same run-level assertions.
98
98
 
99
- - `t.send(input)` sends a turn and waits for it to settle. It accepts the same input as `ClientSession.send()` a string or a structured message and resolves to a turn carrying `.message` and `.expectOk()`.
99
+ - `t.send(input)` sends a turn and waits for it to settle. It accepts the same input as `ClientSession.send()` (a string or a structured message) and resolves to a turn carrying `.message` and `.expectOk()`.
100
100
  - `t.sendFile(text, path, mediaType?)` attaches a local file as a data URL.
101
101
  - `t.expectInputRequests(filter?)` asserts the previous turn parked on HITL input and returns the pending requests.
102
102
  - `t.respond(...responses)` answers specific pending input requests and sends them as the next turn.
103
103
  - `t.respondAll(optionId)` answers every pending input request with the same option and sends the responses as the next turn.
104
104
  - `t.reply` is the last assistant message (or `null`); `t.sessionId` is the current session id; `t.events` is the full typed event stream captured so far.
105
105
 
106
- Each `send` (and `respond`/`respondAll`) resolves to a turn whose `expectOk()` throws only when the turn ended failed a session left open for a next message is the normal end state of a successful turn.
106
+ Each `send` (and `respond`/`respondAll`) resolves to a turn whose `expectOk()` throws only when the turn ended failed. A session left open for a next message is the normal end state of a successful turn.
107
107
 
108
108
  Events from every session are captured in the result and artifacts. `t.log(message)` records debug lines into the eval artifact; `--verbose` also streams them to stdout as evals run. `t.signal` is an `AbortSignal` that fires on timeout.
109
109
 
110
- For driving sessions created outside the eval by a channel webhook or a schedule see [Targets](./targets).
110
+ For driving sessions created outside the eval, by a channel webhook or a schedule, see [Targets](./targets).
111
111
 
112
112
  ## Datasets: exporting an array
113
113
 
114
- To fan one file out over a dataset, default-export an array of `defineEval(...)` values. Eval modules are ESM, so top-level `await` can load anything. Ids derive from the file name plus a zero-padded index (`sql/0000`, `sql/0001`, …, in array order). The loaders (`loadJson`, `loadYaml` from `eve/evals/loaders`) parse fixture files relative to the app root:
114
+ To fan one file out over a dataset, default-export an array of `defineEval(...)` values. Eval modules are ESM, so top-level `await` can load anything. Ids derive from the file name plus a zero-padded index in array order (`sql/0000`, `sql/0001`, and so on). The loaders (`loadJson`, `loadYaml` from `eve/evals/loaders`) parse fixture files relative to the app root:
115
115
 
116
116
  ```ts title="evals/sql.eval.ts"
117
117
  import { defineEval } from "eve/evals";
@@ -3,7 +3,7 @@ title: "Judge"
3
3
  description: "Grade evals with an LLM judge via t.judge.autoevals, set thresholds on the assertion, and configure the judge model."
4
4
  ---
5
5
 
6
- When no deterministic [assertion](./assertions) captures what "good" means factual correctness, summary quality, free-form criteria grade the run with an LLM judge. The `t.judge.*` assertions are the only model-backed ones, and they use a judge model that is resolved separately from the agent under test: Eve only uses it for scoring, never to swap out the agent.
6
+ When no deterministic [assertion](./assertions) captures what "good" means (factual correctness, summary quality, free-form criteria), grade the run with an LLM judge. The `t.judge.*` assertions are the only model-backed ones, and they use a judge model that is resolved separately from the agent under test. Eve only uses it for scoring, never to swap out the agent.
7
7
 
8
8
  ```ts
9
9
  import { defineEval } from "eve/evals";
@@ -19,7 +19,7 @@ export default defineEval({
19
19
 
20
20
  ## The graders
21
21
 
22
- The judges live under `t.judge.autoevals` the namespace names the [Braintrust autoevals](https://github.com/braintrustdata/autoevals) grader family, so the factuality and closedQA semantics are autoevals', not Eve-invented. Each grades `t.reply` by default and is soft by default (tracked, no gate):
22
+ The judges live under `t.judge.autoevals`. The namespace names the [Braintrust autoevals](https://github.com/braintrustdata/autoevals) grader family, so the factuality and closedQA semantics are autoevals', not Eve-invented. Each grader scores `t.reply` by default and is soft by default (tracked, no gate):
23
23
 
24
24
  | Grader | Grades |
25
25
  | ---------------------------------------- | -------------------------------------------------------------------------------------- |
@@ -30,8 +30,8 @@ The judges live under `t.judge.autoevals` — the namespace names the [Braintrus
30
30
 
31
31
  The reference or criteria is the positional argument. An options object follows:
32
32
 
33
- - `on` the value to grade, defaulting to `t.reply`. Pass an intermediate draft or parsed value to grade it instead.
34
- - `model` / `modelOptions` a per-call judge override (see below).
33
+ - `on` is the value to grade, defaulting to `t.reply`. Pass an intermediate draft or parsed value to grade it instead.
34
+ - `model` and `modelOptions` are a per-call judge override (see below).
35
35
 
36
36
  ```ts
37
37
  const draft = await t.send("Draft the welcome email.");
@@ -40,11 +40,11 @@ t.judge.autoevals.closedQA("professional tone", { on: draft.message }).atLeast(0
40
40
 
41
41
  ## Soft scoring and thresholds
42
42
 
43
- Judge assertions are soft, so the threshold rides on the assertion handle there is no separate thresholds map:
43
+ Judge assertions are soft, so the threshold rides on the assertion handle. There is no separate thresholds map:
44
44
 
45
- - **No threshold** tracked-only. The score lands in reports and artifacts and never fails the eval. Use it to watch a metric without gating on it.
46
- - `.atLeast(threshold)` a soft bar. A below-threshold score marks the eval `scored`, fatal only under `eve eval --strict`.
47
- - `.gate(threshold)` promote a judge to a hard gate that fails the eval outright.
45
+ - **No threshold** is tracked-only. The score lands in reports and artifacts and never fails the eval. Use it to watch a metric without gating on it.
46
+ - `.atLeast(threshold)` is a soft bar. A below-threshold score marks the eval `scored`, fatal only under `eve eval --strict`.
47
+ - `.gate(threshold)` promotes a judge to a hard gate that fails the eval outright.
48
48
 
49
49
  ```ts
50
50
  t.judge.autoevals.closedQA("cites a source"); // tracked, never fails
@@ -58,9 +58,9 @@ A judge runs once per assertion and burns tokens, so reach for one only when not
58
58
 
59
59
  The judge model is resolved once when the runner builds `t`. It is **never** the model under test. Three levels resolve innermost-wins:
60
60
 
61
- 1. **Per-call** `t.judge.autoevals.closedQA("…", { model, modelOptions })`.
62
- 2. **Per-eval** `defineEval({ judge: { model, modelOptions }, test })`.
63
- 3. **Project default** `defineEvalConfig({ judge: { model, modelOptions } })` in `evals.config.ts`.
61
+ 1. **Per-call**: `t.judge.autoevals.closedQA("…", { model, modelOptions })`.
62
+ 2. **Per-eval**: `defineEval({ judge: { model, modelOptions }, test })`.
63
+ 3. **Project default**: `defineEvalConfig({ judge: { model, modelOptions } })` in `evals.config.ts`.
64
64
 
65
65
  ```ts title="evals/evals.config.ts"
66
66
  import { defineEvalConfig } from "eve/evals";
@@ -78,14 +78,14 @@ export default defineEval({
78
78
  async test(t) {
79
79
  await t.send("Explain quantum tunneling to a 10-year-old.");
80
80
  t.judge.autoevals.factuality(reference).atLeast(0.7);
81
- t.judge.autoevals.closedQA("is concise", { model: "anthropic/claude-haiku-4-5" }); // cheaper, per-call
81
+ t.judge.autoevals.closedQA("is concise", { model: "anthropic/claude-haiku-4.5" }); // cheaper, per-call
82
82
  },
83
83
  });
84
84
  ```
85
85
 
86
- `judge` in `evals.config.ts` is optional a tree of fully deterministic evals can omit it. But calling `t.judge.*` with no judge model resolved is a fail-fast error at eval definition time.
86
+ `judge` in `evals.config.ts` is optional, and a tree of fully deterministic evals can omit it. Calling `t.judge.*` with no judge model resolved records a failed gate: the runner scores the assertion after the `test` function runs, the missing model throws, and the eval fails with that message.
87
87
 
88
- A **string model id** (e.g. `"anthropic/claude-opus-4.8"`) routes through the Vercel AI Gateway and needs `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` in the environment; an **AI SDK `LanguageModel` instance** is used directly. With a model configured but no credentials, a judge-backed eval **skips visibly** like other real-model legs, so mock-model fixture runs stay green. For provider-specific judge settings, use `modelOptions.providerOptions`.
88
+ A **string model id** (e.g. `"anthropic/claude-opus-4.8"`) routes through the Vercel AI Gateway and needs `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` in the environment. An **AI SDK `LanguageModel` instance** is used directly. With a model configured but no credentials, a judge-backed eval **skips visibly** rather than failing, so the run reports the skip instead of a spurious error. For provider-specific judge settings, use `modelOptions.providerOptions`.
89
89
 
90
90
  ## What to read next
91
91
 
@@ -3,13 +3,13 @@ title: "Overview"
3
3
  description: "Define repeatable scored checks for an Eve agent with defineEval and run them with eve eval."
4
4
  ---
5
5
 
6
- An eval is a scored check that runs your agent against real sessions and grades the result. Use it to catch regressions when you change a prompt or a tool: drive the agent through one or more turns, then assert on what it did the run completed, the right tool ran, the reply contains the right text and optionally ship the results to Braintrust.
6
+ An eval is a scored check that runs your agent against real sessions and grades the result, catching regressions when you change a prompt or a tool. Drive the agent through one or more turns, assert on what it did (the run completed, the right tool ran, the reply contains the right text), and optionally ship the results to Braintrust.
7
7
 
8
- Evals exercise the same HTTP surface your users hit. The runner boots (or targets) a real agent server, drives sessions through the [TypeScript client](../guides/client/overview) protocol, and grades what comes back so a passing eval means the agent actually booted, accepted a request, and produced the result you asserted.
8
+ Evals exercise the same HTTP surface your users hit. The runner boots (or targets) a real agent server, drives sessions through the [TypeScript client](../guides/client/overview) protocol, and grades what comes back, so a passing eval means the agent booted, accepted a request, and produced the result you asserted.
9
9
 
10
10
  ## `defineEval`
11
11
 
12
- Eve discovers evals under the app-root `evals/` directory, in `.eval.ts` files. Each file is exactly one eval one graded case. The file path is the eval's identity, so you don't author an `id` or `name`; directories group related evals (`evals/weather/brooklyn-forecast.eval.ts` id `weather/brooklyn-forecast`).
12
+ Eve discovers evals under the app-root `evals/` directory, in `.eval.ts` files. Each file is one eval by default. A file can also default-export an array to fan out over a dataset (see [Cases](./cases)). The file path is the eval's identity, so you don't author an `id` or `name`. Directories group related evals (`evals/weather/brooklyn-forecast.eval.ts` becomes id `weather/brooklyn-forecast`).
13
13
 
14
14
  ```text
15
15
  my-agent/
@@ -40,7 +40,7 @@ export default defineEval({
40
40
  });
41
41
  ```
42
42
 
43
- `test` is the only required field. The rest are optional: `description`, `judge`, `tags`, `metadata`, `timeoutMs`, `reporters`. The init template adds `evals/**/*.ts` to `tsconfig.json`, so your eval code type-checks alongside the app.
43
+ `test` is the only required field. The rest are optional: `description`, `judge`, `tags`, `metadata`, `timeoutMs`, and `reporters`. The init template adds `evals/**/*.ts` to `tsconfig.json`, so your eval code type-checks alongside the app.
44
44
 
45
45
  ## `evals.config.ts`
46
46
 
@@ -56,11 +56,11 @@ export default defineEvalConfig({
56
56
  });
57
57
  ```
58
58
 
59
- Everything is optional. `judge` sets the default model for [LLM-as-judge](./judge) assertions (`t.judge.*`) — only evals that use them need it, so a tree of fully deterministic evals can omit it entirely. `reporters`, `maxConcurrency`, and `timeoutMs` round out the defaults. Config `reporters` observe every eval in the run set one `Braintrust()` here instead of adding it to each eval. CLI flags (`--max-concurrency`, `--timeout`) and per-eval values take precedence over the config defaults.
59
+ Everything is optional. `judge` sets the default model for [LLM-as-judge](./judge) assertions (`t.judge.*`); a tree of fully deterministic evals can omit it. `reporters`, `maxConcurrency`, and `timeoutMs` round out the defaults. Config `reporters` observe every eval in the run, so set one `Braintrust()` here instead of adding it to each eval. CLI flags (`--max-concurrency`, `--timeout`) and per-eval values take precedence over the config defaults.
60
60
 
61
61
  ## The `t` context
62
62
 
63
- `t` is both the driver and the assertion surface. There are no separate `input`, `run`, `checks`, or `scores` fields you write ordinary control flow, sending turns and asserting inline.
63
+ `t` is both the driver and the assertion surface. There are no separate `input`, `run`, `checks`, or `scores` fields. You write ordinary control flow, sending turns and asserting inline.
64
64
 
65
65
  - **Drive** the agent: `t.send(...)`, `t.respond(...)`, `t.respondAll(...)`, `t.sendFile(...)`, `t.expectInputRequests(...)`, `t.newSession()`. Read what came back with `t.reply` (the last assistant message), `t.sessionId`, and `t.events`. See [Cases](./cases).
66
66
  - **Assert** with three surfaces, covered next.
@@ -69,16 +69,16 @@ Everything is optional. `judge` sets the default model for [LLM-as-judge](./judg
69
69
 
70
70
  Each surface matches a genuinely different kind of judgment:
71
71
 
72
- - **Run-level methods** read the whole run `t.completed()`, `t.calledTool("get_weather")`, `t.usedNoTools()`, `t.toolOrder([...])`. They take no value because they observe the run itself. See [Assertions](./assertions).
73
- - **`t.check(value, assertion)`** grades an explicit value with a deterministic builder from `eve/evals/expect` `t.check(t.reply, includes("sunny"))`. Grade `t.reply`, an intermediate draft, parsed JSON anything. See [Assertions](./assertions).
74
- - **`t.judge.autoevals.*`** is the LLM-as-judge surface `t.judge.autoevals.closedQA("cites a source")`. It grades `t.reply` by default and uses the configured judge model, never the agent under test. See [Judge](./judge).
72
+ - **Run-level methods** read the whole run, like `t.completed()`, `t.calledTool("get_weather")`, `t.usedNoTools()`, and `t.toolOrder([...])`. They take no value because they observe the run itself. See [Assertions](./assertions).
73
+ - **`t.check(value, assertion)`** grades an explicit value with a deterministic builder from `eve/evals/expect`, such as `t.check(t.reply, includes("sunny"))`. Grade `t.reply`, an intermediate draft, parsed JSON, or anything else. See [Assertions](./assertions).
74
+ - **`t.judge.autoevals.*`** is the LLM-as-judge surface, like `t.judge.autoevals.closedQA("cites a source")`. It grades `t.reply` by default and uses the configured judge model, never the agent under test. See [Judge](./judge).
75
75
 
76
76
  ## Gate vs soft
77
77
 
78
- Every assertion returns a chainable handle, so severity rides on the assertion itself there is no separate thresholds map:
78
+ Every assertion returns a chainable handle, so severity rides on the assertion itself. There is no separate thresholds map.
79
79
 
80
80
  - **Gates** are hard. A failed gate marks the eval `failed` and `eve eval` exits non-zero. Run-level methods, `includes`, `equals`, and `matches` are gates by default.
81
- - **Soft** assertions are tracked data. They land in reports and artifacts, and a below-threshold soft assertion marks the eval `scored` visible but not fatal, unless you pass `--strict`. `similarity` and every `t.judge.*` assertion are soft by default. A soft assertion with no threshold is tracked-only and never fails.
81
+ - **Soft** assertions are tracked data. They land in reports and artifacts, and a below-threshold soft assertion marks the eval `scored` (visible but not fatal, unless you pass `--strict`). `similarity` and every `t.judge.*` assertion are soft by default. A soft assertion with no threshold is tracked-only and never fails.
82
82
 
83
83
  Override per assertion: `.gate(threshold?)` promotes to a hard gate, `.soft(threshold?)` demotes to tracked, and `.atLeast(threshold)` is a soft assertion with a bar.
84
84
 
@@ -89,7 +89,7 @@ t.judge.autoevals.closedQA("cites a source"); // soft, tracked (no threshold)
89
89
  t.judge.autoevals.factuality(reference).atLeast(0.7); // soft, gated under --strict at 0.7
90
90
  ```
91
91
 
92
- ## Run it
92
+ ## Run evals with eve eval
93
93
 
94
94
  ```bash
95
95
  eve eval # run all discovered evals against a local dev server
@@ -101,7 +101,9 @@ Exit code `0` means every eval passed its gates. See [Running evals](./running)
101
101
 
102
102
  ## A good baseline
103
103
 
104
- Most apps do fine with a few small smoke evals. Assert behavior with `t.completed()` plus one or two content checks, keep dataset fixtures in `evals/data/`, and only reach for a judge or Braintrust once you actually need fuzzy grading or shared result review. In CI, run `eve eval --strict` so soft threshold misses fail the build too.
104
+ Most apps do fine with a few small smoke evals. Assert behavior with `t.completed()` plus one or two content checks, keep dataset fixtures in `evals/data/`, and reach for a judge or Braintrust only when you need fuzzy grading or shared result review. In CI, run `eve eval --strict` so soft threshold misses fail the build too.
105
+
106
+ ## What to read next
105
107
 
106
108
  The rest of this section covers each piece:
107
109
 
@@ -111,8 +113,4 @@ The rest of this section covers each piece:
111
113
  - [Targets](./targets): local vs remote targets for the same eval files
112
114
  - [Reporters](./reporters): Braintrust experiments and JUnit XML
113
115
  - [Running evals](./running): the `eve eval` CLI, exit codes, and artifacts
114
-
115
- ## What to read next
116
-
117
- - [Cases](./cases): author your first evals
118
116
  - [Tools](../tools): the surface most evals assert on
@@ -1,11 +1,13 @@
1
1
  ---
2
2
  title: "Reporters"
3
- description: "Ship eval results to Braintrust experiments or JUnit XML Eve runs and scores everything itself."
3
+ description: "Ship eval results to Braintrust experiments or JUnit XML. Eve runs and scores everything itself."
4
4
  ---
5
5
 
6
- Eve runs and grades everything itself; reporters just ship the results out. The CLI prints a console summary by default one line per eval, failed assertions with their messages and reporters from `eve/evals/reporters` add destinations on top.
6
+ Eve runs and grades everything itself; reporters ship the results out. The CLI prints a console summary by default (one line per eval, with failed assertions and their messages), and reporters from `eve/evals/reporters` add destinations on top.
7
7
 
8
- Reporters attach in two places. Declare them in `evals.config.ts` to observe **every** eval in the run — the usual choice for a shared destination like one Braintrust experiment, so you don't repeat the reporter in each file. Or list them on an individual eval's `reporters` to scope a destination to that eval (or to a group of evals that share one instance).
8
+ You are responsible for ensuring any observability or eval provider is approved for the data exported to it.
9
+
10
+ Reporters attach in two places. Declare them in `evals.config.ts` to observe **every** eval in the run, the usual choice for a shared destination like one Braintrust experiment, so you don't repeat the reporter in each file. Or list them on an individual eval's `reporters` to scope a destination to that eval (or to a group of evals that share one instance).
9
11
 
10
12
  ## Braintrust
11
13
 
@@ -38,13 +40,13 @@ export default defineEval({
38
40
 
39
41
  The reporter config takes an optional `projectName` and `experimentName`, plus a base experiment (by name or id) to diff against. Gate assertions log as binary scores under a `gate:` prefix so experiments diff gate regressions the same way they diff soft-score regressions. Eval `metadata` rides along to reporters.
40
42
 
41
- A reporter instance observes the evals that reference it: share one instance across several evals the config, a `shared.ts` export, or every entry of a dataset array and their results land in a single experiment. Listing the same config reporter on an eval too does not double-report it.
43
+ A reporter instance observes the evals that reference it. Share one instance across several evals (the config, a `shared.ts` export, or every entry of a dataset array) and their results land in a single experiment. Listing the same config reporter on an eval too does not double-report it.
42
44
 
43
- Braintrust needs its SDK installed in the app and credentials in the environment. Pass `--skip-report` to run the eval without shipping results (this also suppresses config reporters) useful locally when iterating.
45
+ Braintrust needs its SDK installed in the app and credentials in the environment: install the `braintrust` package (`npm install braintrust`) and set `BRAINTRUST_API_KEY`. Pass `--skip-report` to run the eval without shipping results, which also suppresses config reporters and is useful locally when iterating.
44
46
 
45
47
  ## JUnit
46
48
 
47
- `JUnit({ filePath })` writes JUnit XML for CI annotations. The `--junit <path>` CLI flag does the same thing without touching the eval file, which is usually the better fit CI owns the output path, not the eval:
49
+ `JUnit({ filePath })` writes JUnit XML for CI annotations. The `--junit <path>` CLI flag does the same thing without touching the eval file, usually the better fit because CI owns the output path, not the eval:
48
50
 
49
51
  ```bash
50
52
  eve eval --strict --junit .eve/junit.xml
@@ -54,7 +56,17 @@ Each eval becomes one `<testcase>` named by its path-derived id; failed gates an
54
56
 
55
57
  ## Custom reporters
56
58
 
57
- A reporter implements the `EvalReporter` interface from `eve/evals/reporters` and receives the same structured results the built-ins do. Reach for one only when a destination isn't covered the per-run artifacts under `.eve/evals/` already capture everything for ad-hoc inspection.
59
+ A reporter implements the `EvalReporter` interface from `eve/evals/reporters` and receives the same structured results the built-ins do. The runner calls three lifecycle methods, each of which may return a promise for async work like a remote upload:
60
+
61
+ ```ts
62
+ interface EvalReporter {
63
+ onRunStart(evaluations: readonly EveEval[], target: EveEvalTarget): void | Promise<void>;
64
+ onEvalComplete(result: EveEvalResult): void | Promise<void>;
65
+ onRunComplete(summary: EveEvalRunSummary): void | Promise<void>;
66
+ }
67
+ ```
68
+
69
+ `onRunStart` fires once before any eval runs, `onEvalComplete` fires after each observed eval with its checks, scores, and verdict, and `onRunComplete` fires once with the aggregated summary. Reach for a custom reporter only when a destination isn't covered. The per-run artifacts under `.eve/evals/` already capture everything for ad-hoc inspection.
58
70
 
59
71
  ## What to read next
60
72
 
@@ -15,7 +15,7 @@ eve eval --timeout 60000 # per-eval timeout in milliseconds
15
15
  eve eval --max-concurrency 4 # cap concurrent eval executions (default 8)
16
16
  eve eval --junit .eve/junit.xml # write JUnit XML
17
17
  eve eval --list # print discovered evals without running
18
- eve eval --verbose # stream per-eval ctx.log lines to stdout
18
+ eve eval --verbose # stream per-eval t.log lines to stdout
19
19
  eve eval --json # machine-readable output
20
20
  eve eval --skip-report # skip config and eval-defined reporters (e.g. Braintrust)
21
21
  ```
@@ -27,7 +27,7 @@ Positional ids match exactly or by directory prefix: `eve eval weather` runs `ev
27
27
  | Code | Means |
28
28
  | ---- | ------------------------------------------------------------------------------- |
29
29
  | `0` | Every eval passed its gates (and soft thresholds, under `--strict`) |
30
- | `1` | Any eval failed a failed gate, an execution error, or a strict threshold miss |
30
+ | `1` | Any eval failed (a failed gate, an execution error, or a strict threshold miss) |
31
31
  | `2` | Configuration error |
32
32
 
33
33
  ## Artifacts