eve 0.10.0 → 0.11.0

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 (91) hide show
  1. package/CHANGELOG.md +11 -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/harness/input-requests.d.ts +20 -1
  10. package/dist/src/harness/input-requests.js +1 -1
  11. package/dist/src/harness/tool-loop.js +1 -1
  12. package/dist/src/internal/application/package.js +1 -1
  13. package/dist/src/protocol/message.d.ts +11 -2
  14. package/dist/src/protocol/message.js +2 -2
  15. package/dist/src/public/next/index.d.ts +1 -7
  16. package/dist/src/public/next/index.js +1 -1
  17. package/dist/src/public/next/vercel-output-config.js +1 -1
  18. package/dist/src/setup/scaffold/create/project.js +1 -1
  19. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  20. package/dist/src/setup/scaffold/create/web-template.js +27 -4
  21. package/dist/src/setup/scaffold/update/channels.js +1 -1
  22. package/docs/README.md +19 -5
  23. package/docs/agent-config.md +16 -19
  24. package/docs/channels/custom.mdx +24 -18
  25. package/docs/channels/discord.mdx +25 -13
  26. package/docs/channels/eve.mdx +24 -6
  27. package/docs/channels/github.mdx +41 -10
  28. package/docs/channels/linear.mdx +29 -36
  29. package/docs/channels/overview.mdx +16 -4
  30. package/docs/channels/slack.mdx +59 -32
  31. package/docs/channels/teams.mdx +19 -7
  32. package/docs/channels/telegram.mdx +23 -9
  33. package/docs/channels/twilio.mdx +28 -6
  34. package/docs/concepts/context-control.md +24 -41
  35. package/docs/concepts/default-harness.md +19 -9
  36. package/docs/concepts/execution-model-and-durability.md +14 -14
  37. package/docs/concepts/security-model.md +32 -8
  38. package/docs/concepts/sessions-runs-and-streaming.md +11 -9
  39. package/docs/connections.mdx +49 -29
  40. package/docs/evals/assertions.mdx +30 -29
  41. package/docs/evals/cases.mdx +10 -10
  42. package/docs/evals/judge.mdx +14 -14
  43. package/docs/evals/overview.mdx +15 -17
  44. package/docs/evals/reporters.mdx +19 -7
  45. package/docs/evals/running.mdx +2 -2
  46. package/docs/evals/targets.mdx +15 -4
  47. package/docs/getting-started.mdx +70 -31
  48. package/docs/guides/auth-and-route-protection.md +18 -12
  49. package/docs/guides/client/continuations.mdx +5 -13
  50. package/docs/guides/client/messages.mdx +2 -2
  51. package/docs/guides/client/output-schema.mdx +2 -2
  52. package/docs/guides/client/overview.mdx +4 -4
  53. package/docs/guides/client/streaming.mdx +6 -5
  54. package/docs/guides/deployment.md +22 -20
  55. package/docs/guides/dev-tui.md +85 -13
  56. package/docs/guides/dynamic-capabilities.md +24 -8
  57. package/docs/guides/dynamic-workflows.md +33 -12
  58. package/docs/guides/frontend/nextjs.mdx +28 -14
  59. package/docs/guides/frontend/nuxt.mdx +21 -4
  60. package/docs/guides/frontend/overview.mdx +11 -11
  61. package/docs/guides/frontend/sveltekit.mdx +20 -3
  62. package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
  63. package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
  64. package/docs/guides/hooks.md +14 -60
  65. package/docs/guides/instrumentation.md +24 -18
  66. package/docs/guides/remote-agents.md +19 -15
  67. package/docs/guides/session-context.md +12 -12
  68. package/docs/guides/state.md +25 -9
  69. package/docs/instructions.mdx +18 -10
  70. package/docs/introduction.md +12 -12
  71. package/docs/legal.md +16 -0
  72. package/docs/meta.json +1 -0
  73. package/docs/reference/cli.md +101 -53
  74. package/docs/reference/meta.json +1 -1
  75. package/docs/reference/project-layout.md +39 -32
  76. package/docs/reference/typescript-api.md +1 -1
  77. package/docs/sandbox.mdx +25 -19
  78. package/docs/schedules.mdx +16 -14
  79. package/docs/skills.mdx +8 -6
  80. package/docs/subagents.mdx +39 -13
  81. package/docs/tools.mdx +13 -9
  82. package/docs/tutorial/connect-a-warehouse.mdx +16 -7
  83. package/docs/tutorial/first-agent.mdx +21 -11
  84. package/docs/tutorial/guard-the-spend.mdx +11 -9
  85. package/docs/tutorial/how-it-runs.mdx +10 -8
  86. package/docs/tutorial/query-sample-data.mdx +8 -8
  87. package/docs/tutorial/remember-definitions.mdx +5 -5
  88. package/docs/tutorial/run-analysis.mdx +13 -11
  89. package/docs/tutorial/ship-it.mdx +70 -14
  90. package/docs/tutorial/team-playbooks.mdx +33 -10
  91. package/package.json +1 -1
@@ -3,7 +3,7 @@ title: "useEveAgent (Svelte)"
3
3
  description: "Svelte 5 binding that drives an Eve agent session from the browser."
4
4
  ---
5
5
 
6
- `useEveAgent()` from `eve/svelte` is the browser side of an Eve session in a Svelte 5 app. Call it once and you get a long-lived session you can send turns to, with every stream event projected into rune-friendly reactive data. On SvelteKit, the [Vite plugin](./sveltekit) wires up the routes. The [frontend overview](./overview) covers the model shared across frameworks.
6
+ `useEveAgent()` from `eve/svelte` is the browser side of an Eve session in a Svelte 5 app. Call it once for a long-lived session you can send turns to, with every stream event projected into rune-friendly reactive data. On SvelteKit, the [Vite plugin](./sveltekit) wires up the routes. The [frontend overview](./overview) covers the model shared across frameworks.
7
7
 
8
8
  ## Basic usage
9
9
 
@@ -23,16 +23,16 @@ Import from `eve/svelte` and read the reactive getters directly. No `$` prefix:
23
23
 
24
24
  ## What it returns
25
25
 
26
- | Property | Type | Description |
27
- | --------- | -------------------- | ------------------------------------------------------------------------- |
28
- | `data` | `TData` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
29
- | `status` | `UseEveAgentStatus` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
30
- | `error` | `Error \| undefined` | Last transport-level error. |
31
- | `events` | `readonly ...[]` | Raw server events for this session. |
32
- | `session` | `SessionState` | Snapshot of session state. |
33
- | `send` | `(input) => Promise` | Send text or a full turn (multi-part, attachments, HITL responses). |
34
- | `stop` | `() => void` | Abort the in-flight request. |
35
- | `reset` | `() => void` | Clear state and start a new session. |
26
+ | Property | Type | Description |
27
+ | --------- | ------------------------------------------- | ------------------------------------------------------------------------- |
28
+ | `data` | `TData` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
29
+ | `status` | `UseEveAgentStatus` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
30
+ | `error` | `Error \| undefined` | Last transport-level error. |
31
+ | `events` | `readonly HandleMessageStreamEvent[]` | Raw server events for this session. |
32
+ | `session` | `SessionState` | Snapshot of session state. |
33
+ | `send` | `(input: SendTurnPayload) => Promise<void>` | Send text or a full turn (multi-part, attachments, HITL responses). |
34
+ | `stop` | `() => void` | Abort the in-flight request. |
35
+ | `reset` | `() => void` | Clear state and start a new session. |
36
36
 
37
37
  These state fields are reactive getters, so read them straight from templates, `$derived`, or `$effect`. They are not stores, so don't prefix them with `$`.
38
38
 
@@ -62,20 +62,44 @@ These state fields are reactive getters, so read them straight from templates, `
62
62
  </form>
63
63
  ```
64
64
 
65
- When a turn is more than plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format:
65
+ When a turn is more than plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format. Send file data as a base64 `data:` URL so it survives the JSON transport:
66
66
 
67
67
  ```ts
68
+ const bytes = new Uint8Array(await file.arrayBuffer());
69
+ const base64 = btoa(String.fromCodePoint(...bytes));
70
+
68
71
  await agent.send({
69
72
  message: [
70
73
  { type: "text", text: "Describe this image." },
71
- { type: "file", data: fileBytes, mediaType: "image/png" },
74
+ { type: "file", data: `data:${file.type};base64,${base64}`, mediaType: file.type },
72
75
  ],
73
76
  });
74
77
  ```
75
78
 
76
79
  ## Human-in-the-loop prompts
77
80
 
78
- A tool opts into approval with `needsApproval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part of the latest message. Read it, then answer with `agent.send({ inputResponses })`. This find-and-answer flow is identical across frameworks, so the full example lives in the [Frontend overview](./overview#human-in-the-loop-prompts).
81
+ A tool opts into approval with `needsApproval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part of the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `agent.send({ inputResponses })`:
82
+
83
+ ```ts
84
+ import type { EveDynamicToolPart, EveMessagePart } from "eve/svelte";
85
+
86
+ const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
87
+ part.type === "dynamic-tool";
88
+
89
+ const request = agent.data.messages
90
+ .at(-1)
91
+ ?.parts.filter(isDynamicToolPart)
92
+ .map((part) => part.toolMetadata?.eve?.inputRequest)
93
+ .find((value) => value !== undefined);
94
+
95
+ if (request) {
96
+ await agent.send({
97
+ inputResponses: [{ requestId: request.requestId, optionId: "approve" }],
98
+ });
99
+ }
100
+ ```
101
+
102
+ The find-and-answer flow is identical across frameworks. The [React hook reference](./overview) covers the longer walkthrough.
79
103
 
80
104
  ## Stop, reset, and resume
81
105
 
@@ -104,6 +128,74 @@ const agent = useEveAgent({
104
128
  });
105
129
  ```
106
130
 
131
+ ## Attach page context per turn
132
+
133
+ `clientContext` adds ephemeral context for the next model call and nothing more. Strings (or an array of strings) become user-role context messages; an object is JSON-serialized into one. It rides along with a message or HITL response, never dispatches a turn on its own, and never lands in durable session history. Pass it to `send()`:
134
+
135
+ ```ts
136
+ await agent.send({
137
+ message: "What should I do on this screen?",
138
+ clientContext: { route: "/billing", plan: "pro", seatsUsed: 4 },
139
+ });
140
+ ```
141
+
142
+ To attach the same context to every turn without threading it through each call site, pass `prepareSend`. It runs right before each send and returns the (possibly augmented) turn:
143
+
144
+ ```ts
145
+ const agent = useEveAgent({
146
+ prepareSend: (input) => ({
147
+ ...input,
148
+ clientContext: { route: location.pathname },
149
+ }),
150
+ });
151
+ ```
152
+
153
+ ## Lifecycle callbacks
154
+
155
+ The binding takes a few per-turn callbacks:
156
+
157
+ - `onEvent(event)`: fires for each Eve stream event as it arrives.
158
+ - `onError(error)`: fires with the last `Error` when a turn fails.
159
+ - `onFinish(snapshot)`: fires with the final snapshot once a turn settles.
160
+ - `onSessionChange(session)`: fires when the session cursor advances. Persist it to resume across reloads.
161
+
162
+ ```ts
163
+ const agent = useEveAgent({
164
+ onEvent: (event) => console.debug(event.type),
165
+ onError: (error) => console.error(error.message),
166
+ onFinish: (snapshot) => console.log(snapshot.status),
167
+ });
168
+ ```
169
+
170
+ Two more options tune turn behavior:
171
+
172
+ - `optimistic` (default `true`): projects submitted user messages into `data` before Eve confirms them with a `message.received` event. These are reducer-facing projection events only; `events` stays the authoritative Eve stream.
173
+ - `maxReconnectAttempts` (default `3`): stream reconnection budget per turn.
174
+
175
+ ## Custom reducer
176
+
177
+ The default reducer projects events into `{ messages }` (`EveMessageData`). To shape `data` differently, pass a `reducer` implementing `EveAgentReducer<TData>`:
178
+
179
+ ```ts
180
+ import { useEveAgent } from "eve/svelte";
181
+ import type { EveAgentReducer } from "eve/svelte";
182
+
183
+ interface ToolLog {
184
+ readonly toolCalls: number;
185
+ }
186
+
187
+ const toolCounter: EveAgentReducer<ToolLog> = {
188
+ initial: () => ({ toolCalls: 0 }),
189
+ reduce: (data, event) =>
190
+ event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
191
+ };
192
+
193
+ const agent = useEveAgent({ reducer: toolCounter });
194
+ // agent.data is ToolLog
195
+ ```
196
+
197
+ `reduce(data, event)` receives both authoritative Eve stream events and client projection events (`client.message.submitted`, `client.message.failed`, `client.input.responded`). Handle the client events too if you want optimistic and HITL state in your projection. Otherwise, return `data` unchanged for them.
198
+
107
199
  ## What to read next
108
200
 
109
201
  - [SvelteKit](./sveltekit): Vite plugin setup
@@ -3,7 +3,7 @@ title: "useEveAgent (Vue)"
3
3
  description: "Vue composable that drives an Eve agent session from the browser."
4
4
  ---
5
5
 
6
- `useEveAgent()` from `eve/vue` is how a Vue app talks to an Eve session. It opens a long-lived session and sends turns, and it folds every stream event into reactive data you can bind in a template. Nuxt auto-imports it through the [module](./nuxt), and the [frontend overview](./overview) covers the shared model.
6
+ `useEveAgent()` from `eve/vue` is how a Vue app talks to an Eve session. It opens a long-lived session, sends turns, and folds every stream event into reactive data you can bind in a template. Nuxt auto-imports it through the [module](./nuxt), and the [frontend overview](./overview) covers the shared model.
7
7
 
8
8
  ## Basic usage
9
9
 
@@ -25,18 +25,18 @@ const { data } = useEveAgent();
25
25
 
26
26
  ## What it returns
27
27
 
28
- | Property | Type | Description |
29
- | --------- | --------------------------------- | ------------------------------------------------------------------------- |
30
- | `data` | `ComputedRef<TData>` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
31
- | `status` | `ComputedRef<UseEveAgentStatus>` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
32
- | `error` | `ComputedRef<Error \| undefined>` | Last transport-level error. |
33
- | `events` | `ComputedRef<...>` | Raw server events for this session. |
34
- | `session` | `ComputedRef<SessionState>` | Snapshot of session state. |
35
- | `send` | `(input) => Promise` | Send text or a full turn (multi-part, attachments, HITL responses). |
36
- | `stop` | `() => void` | Abort the in-flight request. |
37
- | `reset` | `() => void` | Clear state and start a new session. |
28
+ | Property | Type | Description |
29
+ | --------- | -------------------------------------------------- | ------------------------------------------------------------------------- |
30
+ | `data` | `ComputedRef<TData>` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
31
+ | `status` | `ComputedRef<UseEveAgentStatus>` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
32
+ | `error` | `ComputedRef<Error \| undefined>` | Last transport-level error. |
33
+ | `events` | `ComputedRef<readonly HandleMessageStreamEvent[]>` | Raw server events for this session. |
34
+ | `session` | `ComputedRef<SessionState>` | Snapshot of session state. |
35
+ | `send` | `(input: SendTurnPayload) => Promise<void>` | Send text or a full turn (multi-part, attachments, HITL responses). |
36
+ | `stop` | `() => void` | Abort the in-flight request. |
37
+ | `reset` | `() => void` | Clear state and start a new session. |
38
38
 
39
- The first five are `ComputedRef`s; the rest are methods. Destructure whatever you need: refs keep their reactivity through destructuring. Read them with `.value` in `<script>`, and unwrapped in `<template>`.
39
+ The first five are `ComputedRef`s; the rest are methods. Destructure whatever you need, since refs keep their reactivity through destructuring. Read them with `.value` in `<script>`, and unwrapped in `<template>`.
40
40
 
41
41
  ## Send a message
42
42
 
@@ -64,7 +64,7 @@ async function handleSubmit() {
64
64
  </template>
65
65
  ```
66
66
 
67
- For anything beyond plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format:
67
+ For anything beyond plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format. Send file data as a base64 `data:` URL so it survives the JSON transport:
68
68
 
69
69
  ```vue
70
70
  <script setup lang="ts">
@@ -75,10 +75,12 @@ const { send } = useEveAgent();
75
75
  async function onFileChange(event: Event) {
76
76
  const file = (event.target as HTMLInputElement).files?.[0];
77
77
  if (!file) return;
78
+ const bytes = new Uint8Array(await file.arrayBuffer());
79
+ const base64 = btoa(String.fromCodePoint(...bytes));
78
80
  await send({
79
81
  message: [
80
82
  { type: "text", text: "Describe this image." },
81
- { type: "file", data: new Uint8Array(await file.arrayBuffer()), mediaType: file.type },
83
+ { type: "file", data: `data:${file.type};base64,${base64}`, mediaType: file.type },
82
84
  ],
83
85
  });
84
86
  }
@@ -87,7 +89,30 @@ async function onFileChange(event: Event) {
87
89
 
88
90
  ## Human-in-the-loop prompts
89
91
 
90
- A tool opts into approval with `needsApproval` ([Tools](../../tools)). When it triggers, the pending request shows up as a `dynamic-tool` part on the latest message. Read it and answer with `send({ inputResponses })`. That find-and-answer flow is the same across every framework, so the full example lives in the [Frontend overview](./overview#human-in-the-loop-prompts).
92
+ A tool opts into approval with `needsApproval` ([Tools](../../tools)). When it triggers, the pending request shows up as a `dynamic-tool` part on the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `send({ inputResponses })`:
93
+
94
+ ```ts
95
+ import type { EveDynamicToolPart, EveMessagePart } from "eve/vue";
96
+
97
+ const { data, send } = useEveAgent();
98
+
99
+ const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
100
+ part.type === "dynamic-tool";
101
+
102
+ const request = data.value.messages
103
+ .at(-1)
104
+ ?.parts.filter(isDynamicToolPart)
105
+ .map((part) => part.toolMetadata?.eve?.inputRequest)
106
+ .find((value) => value !== undefined);
107
+
108
+ if (request) {
109
+ await send({
110
+ inputResponses: [{ requestId: request.requestId, optionId: "approve" }],
111
+ });
112
+ }
113
+ ```
114
+
115
+ The find-and-answer flow is the same across every framework. The [React hook reference](./overview) covers the longer walkthrough.
91
116
 
92
117
  ## Stop, reset, and resume
93
118
 
@@ -116,6 +141,74 @@ const agent = useEveAgent({
116
141
  });
117
142
  ```
118
143
 
144
+ ## Attach page context per turn
145
+
146
+ `clientContext` adds ephemeral context for the next model call and nothing more. Strings (or an array of strings) become user-role context messages; an object is JSON-serialized into one. It rides along with a message or HITL response, never dispatches a turn on its own, and never lands in durable session history. Pass it to `send()`:
147
+
148
+ ```ts
149
+ await send({
150
+ message: "What should I do on this screen?",
151
+ clientContext: { route: "/billing", plan: "pro", seatsUsed: 4 },
152
+ });
153
+ ```
154
+
155
+ To attach the same context to every turn without threading it through each call site, pass `prepareSend`. It runs right before each send and returns the (possibly augmented) turn:
156
+
157
+ ```ts
158
+ const agent = useEveAgent({
159
+ prepareSend: (input) => ({
160
+ ...input,
161
+ clientContext: { route: location.pathname },
162
+ }),
163
+ });
164
+ ```
165
+
166
+ ## Lifecycle callbacks
167
+
168
+ The composable takes a few per-turn callbacks:
169
+
170
+ - `onEvent(event)`: fires for each Eve stream event as it arrives.
171
+ - `onError(error)`: fires with the last `Error` when a turn fails.
172
+ - `onFinish(snapshot)`: fires with the final snapshot once a turn settles.
173
+ - `onSessionChange(session)`: fires when the session cursor advances. Persist it to resume across reloads.
174
+
175
+ ```ts
176
+ const agent = useEveAgent({
177
+ onEvent: (event) => console.debug(event.type),
178
+ onError: (error) => console.error(error.message),
179
+ onFinish: (snapshot) => console.log(snapshot.status),
180
+ });
181
+ ```
182
+
183
+ Two more options tune turn behavior:
184
+
185
+ - `optimistic` (default `true`): projects submitted user messages into `data` before Eve confirms them with a `message.received` event. These are reducer-facing projection events only; `events` stays the authoritative Eve stream.
186
+ - `maxReconnectAttempts` (default `3`): stream reconnection budget per turn.
187
+
188
+ ## Custom reducer
189
+
190
+ The default reducer projects events into `{ messages }` (`EveMessageData`). To shape `data` differently, pass a `reducer` implementing `EveAgentReducer<TData>`:
191
+
192
+ ```ts
193
+ import { useEveAgent } from "eve/vue";
194
+ import type { EveAgentReducer } from "eve/vue";
195
+
196
+ interface ToolLog {
197
+ readonly toolCalls: number;
198
+ }
199
+
200
+ const toolCounter: EveAgentReducer<ToolLog> = {
201
+ initial: () => ({ toolCalls: 0 }),
202
+ reduce: (data, event) =>
203
+ event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
204
+ };
205
+
206
+ const agent = useEveAgent({ reducer: toolCounter });
207
+ // agent.data.value is ToolLog
208
+ ```
209
+
210
+ `reduce(data, event)` receives both authoritative Eve stream events and client projection events (`client.message.submitted`, `client.message.failed`, `client.input.responded`). Handle the client events too if you want optimistic and HITL state in your projection. Otherwise, return `data` unchanged for them.
211
+
119
212
  ## What to read next
120
213
 
121
214
  - [Nuxt](./nuxt): module setup
@@ -3,7 +3,7 @@ title: "Hooks"
3
3
  description: "Subscribe to runtime stream events from agent/hooks/."
4
4
  ---
5
5
 
6
- Hooks are Eve's authored extension points for the runtime event stream. A hook subscribes to stream events and runs side effects after each event is durably recorded: audit logging, metrics and alerting, or persisting every session and message to your own database for analytics. Reach for one when you want to observe what the agent does without writing a tool, a context provider, or a channel adapter handler.
6
+ Hooks are Eve's authored extension points for the runtime event stream. A hook subscribes to stream events and runs side effects after each event is durably recorded, such as audit logging, metrics and alerting, or persisting every session and message to your own database for analytics. Reach for one to observe what the agent does without writing a tool, a context provider (a value made available across a step), or a channel adapter handler (a handler defined on a channel's adapter; see [Channels](../channels)).
7
7
 
8
8
  ## Define a hook
9
9
 
@@ -22,31 +22,13 @@ export default defineHook({
22
22
  });
23
23
  ```
24
24
 
25
- The slug is the path-relative basename: `agent/hooks/audit.ts` becomes `"audit"`, and `agent/hooks/auth/load-profile.ts` becomes `"auth/load-profile"`.
25
+ The slug is the path-relative basename. `agent/hooks/audit.ts` becomes `"audit"`, and `agent/hooks/auth/load-profile.ts` becomes `"auth/load-profile"`.
26
26
 
27
- `defineHook`, `HookDefinition`, and `HookContext` live on `eve/hooks`. The `defineState` helper lives on `eve/context`.
27
+ `defineHook`, `HookDefinition`, and `HookContext` live on `eve/hooks`.
28
28
 
29
- ## Shape
29
+ A hook file declares stream-event subscribers under the `events` map, keyed by event type, with `*` matching every event. Subscribe to any event in the runtime stream vocabulary documented in [Sessions, runs and streaming](../concepts/sessions-runs-and-streaming), including the lifecycle events `session.started`, `turn.completed`, `message.completed`, and `action.result`. Handlers are observe-only. They cannot inject model context. To contribute runtime model messages, use `defineDynamic` and `defineInstructions` in `agent/instructions/`.
30
30
 
31
- A hook file declares stream-event subscribers under the `events` map:
32
-
33
- ```ts
34
- defineHook({
35
- events: {
36
- "session.started"(event, ctx) {
37
- /* ... */
38
- },
39
- "turn.completed"(event, ctx) {
40
- /* ... */
41
- },
42
- "*"(event, ctx) {
43
- /* wildcard: fires for every event */
44
- },
45
- },
46
- });
47
- ```
48
-
49
- Handlers are observe-only: they cannot inject model context. To contribute runtime model messages, use `defineDynamic` + `defineInstructions` in `agent/instructions/`.
31
+ ## Hook structure and context
50
32
 
51
33
  Every handler receives the same `HookContext`:
52
34
 
@@ -89,51 +71,23 @@ export default defineHook({
89
71
 
90
72
  Returns `undefined` when the result doesn't match, or when `isError` is `true`. For authored tools the return includes `{ output, toolName, callId }` with `output` typed as the tool's `TOutput`. For connections it includes `{ output, toolName, connectionToolName, callId }` with `output` as `unknown`.
91
73
 
92
- ## Stream events
93
-
94
- Side-effect-only handlers for accepted runtime events. Subscribe by event type, or use `*` for every event:
95
-
96
- ```ts title="agent/hooks/observability.ts"
97
- import { defineHook } from "eve/hooks";
98
- import { datadogCounter } from "../lib/datadog";
99
- import { postToHoneycomb } from "../lib/honeycomb";
100
-
101
- export default defineHook({
102
- events: {
103
- async "session.started"(_event, ctx) {
104
- await postToHoneycomb({ event: "session.started", sessionId: ctx.session.id });
105
- },
106
- async "message.completed"(event) {
107
- await postToHoneycomb({ event: "message.completed", payload: event.data });
108
- },
109
- async "*"(event) {
110
- datadogCounter(`eve.event.${event.type}`).increment();
111
- },
112
- },
113
- });
114
- ```
115
-
116
- ### Execution order
74
+ ## Execution order
117
75
 
118
76
  When a stream event fires, three things happen in order:
119
77
 
120
- 1. Emit: the channel adapter handler runs, then the event is written to the durable stream.
121
- 2. Hooks: stream-event hooks fire (typed handlers first, then the `*` wildcard). Return values are ignored.
122
- 3. Dynamic tool resolvers: resolvers subscribed to the event type run and update the tool set.
78
+ 1. Emit. The channel adapter handler runs, then the event is written to the durable stream.
79
+ 2. Hooks. Stream-event hooks fire (typed handlers first, then the `*` wildcard). Return values are ignored.
80
+ 3. Dynamic tool resolvers. Resolvers subscribed to the event type run and update the tool set.
123
81
 
124
82
  Hooks always run after the event is durably recorded, so if a hook throws, the stream stays consistent.
125
83
 
126
- ## Errors
127
-
128
- | Stage | On throw |
129
- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
130
- | `events.*` | Propagates through the emit composer and surfaces as `turn.failed`. If a hook subscribed to a failure-cascade event also throws, it escalates to `session.failed`. |
84
+ ## What happens when a hook throws
131
85
 
132
- For belt-and-suspenders semantics inside a hook, wrap the body in `try`/`catch`. Eve treats a thrown hook as a real failure.
86
+ A thrown handler propagates through the emit composer and surfaces as `turn.failed`. If a hook subscribed to a failure-cascade event also throws, it escalates to `session.failed`. For belt-and-suspenders semantics inside a hook, wrap the body in `try`/`catch`. Eve treats a thrown hook as a real failure.
133
87
 
134
88
  ## Subagent isolation
135
89
 
136
- Subagents may carry their own `agent/hooks/` directory. Subagent hooks fire only inside the subagent scope: parent-agent hooks do not fire for subagent turns, and subagent hooks see only the subagent's own context.
90
+ Subagents may carry their own `agent/hooks/` directory. Subagent hooks fire only inside the subagent scope. Parent-agent hooks do not fire for subagent turns, and subagent hooks see only the subagent's own context.
137
91
 
138
92
  ## Hook vs tool vs provider
139
93
 
@@ -150,5 +104,5 @@ Stream-event hooks and channel adapter event handlers are structurally identical
150
104
 
151
105
  - [Tools](../tools)
152
106
  - [Context control](../concepts/context-control)
153
- - [Session context](./session-context)
154
- - [Sessions, runs & streaming](../concepts/sessions-runs-and-streaming)
107
+ - [Session context](../reference/typescript-api)
108
+ - [Sessions, runs and streaming](../concepts/sessions-runs-and-streaming)
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  title: "instrumentation.ts"
3
- description: "Trace an agent with OpenTelemetry, read the workflow run tags Eve emits, and debug discovery with eve info and the error catalog."
3
+ description: "Trace an agent with OpenTelemetry in instrumentation.ts, read the workflow run tags Eve emits, and debug discovery with eve info and the common-failures table."
4
4
  ---
5
5
 
6
6
  `instrumentation.ts` is where you configure how an Eve agent is observed. The framework auto-discovers `agent/instrumentation.ts` and runs it at server startup before any agent code. Its presence implicitly enables telemetry, so there is no separate `isEnabled` toggle.
7
7
 
8
+ If you intend to export telemetry, review the exporter destination, data categories, and required legal approvals before enabling telemetry.
9
+
8
10
  ## Three observability surfaces
9
11
 
10
- Eve observes an agent through three distinct surfaces. They do not all live in this file, and they write to different places, so it helps to keep them apart:
12
+ Eve observes an agent through three distinct surfaces. They do not all live in this file, and they write to different places:
11
13
 
12
14
  | Surface | Configured in `instrumentation.ts`? | What it is |
13
15
  | -------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -15,7 +17,7 @@ Eve observes an agent through three distinct surfaces. They do not all live in t
15
17
  | **OpenTelemetry export** | Yes: `setup`, `recordInputs`, `recordOutputs`, `functionId` | Where AI SDK spans are exported and what they record. |
16
18
  | **Runtime context events** | Yes: `events["step.started"]` | Per-model-call values written into the AI SDK's runtime context, which the AI SDK carries onto its spans. |
17
19
 
18
- The two configurable surfaces send AI SDK spans to your OpenTelemetry backend. Workflow run tags are a separate system: they live on the Vercel Workflow run and are queryable in the Workflow dashboard, not on your OTel spans. The sections below cover what you configure here; [Workflow run tags](#workflow-run-tags) documents what Eve emits on its own.
20
+ The two configurable surfaces send AI SDK spans to your OpenTelemetry backend. Workflow run tags are a separate system, queryable in the Workflow dashboard rather than on your OTel spans. The sections below cover what you configure here; [Workflow run tags](#workflow-run-tags) documents what Eve emits on its own.
19
21
 
20
22
  ## Define instrumentation
21
23
 
@@ -40,7 +42,7 @@ Export the result of `defineInstrumentation` as the default export.
40
42
 
41
43
  ## OpenTelemetry
42
44
 
43
- The `setup` callback is invoked by the framework at server startup with the resolved agent name. Use it to register your OTel provider (for example `registerOTel` from `@vercel/otel`). The `context.agentName` is resolved at compile time from your project (the package's `name`, falling back to the app directory name), so you never need to hard-code a service name.
45
+ Use the `setup` callback to register your OTel provider (for example `registerOTel` from `@vercel/otel`). The framework invokes it at server startup with the resolved agent name. `context.agentName` is resolved at compile time from your project (the package's `name`, falling back to the app directory name), so you never hard-code a service name.
44
46
 
45
47
  Any OTel-compatible backend works (Braintrust, Honeycomb, Datadog, Jaeger). Install the exporter package you need and configure it in the callback.
46
48
 
@@ -50,11 +52,15 @@ Three more fields control what the AI SDK records inside those spans (see the AI
50
52
  - `recordOutputs` records model outputs on spans (defaults to `true`). Set it to `false` to disable output recording.
51
53
  - `functionId` overrides the function name on spans (defaults to the agent name).
52
54
 
55
+ For sensitive, regulated, or production data, set `recordInputs` and `recordOutputs` to `false` unless you have reviewed the exporter and its data-retention path.
56
+
57
+ You are responsible for ensuring any observability or eval provider is approved for the data exported to it.
58
+
53
59
  The third configurable surface, [runtime context events](#runtime-context), attaches per-model-call values to these spans.
54
60
 
55
61
  ## Runtime context
56
62
 
57
- _Runtime context_ is an [AI SDK concept](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text): a user-defined object that flows through a generation lifecycle. Eve exposes it through `events["step.started"]`, a callback that runs once Eve has assembled the model input for an attempt and returns `{ runtimeContext }`. Because Eve registers the AI SDK's OpenTelemetry integration with runtime context enabled, those returned values ride onto the model-call span and its children. That is the reason this surface exists. The returned field is named `runtimeContext`, not `metadata`, because AI SDK v7 carries per-call attributes on runtime context rather than a dedicated metadata field.
63
+ _Runtime context_ is an [AI SDK concept](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text): a user-defined object that flows through a generation lifecycle. Eve exposes it through `events["step.started"]`, a callback that runs once Eve has assembled the model input for an attempt and returns `{ runtimeContext }`. Because Eve registers the AI SDK's OpenTelemetry integration with runtime context enabled, those returned values ride onto the model-call span and its children. The field is named `runtimeContext`, not `metadata`, because AI SDK v7 carries per-call attributes on runtime context rather than a dedicated metadata field.
58
64
 
59
65
  Use it when the values depend on the current session, turn, step, channel, or model input:
60
66
 
@@ -80,8 +86,6 @@ export default defineInstrumentation({
80
86
  });
81
87
  ```
82
88
 
83
- For authored channels, Eve emits compiler-owned typings keyed by the channel filename. A file at `agent/channels/support.ts` narrows as `channel:support`, either by checking `input.channel.kind === "channel:support"` or by using `isChannel(input.channel, supportChannel)`.
84
-
85
89
  The callback receives:
86
90
 
87
91
  - `session`: the session id, current and initiator auth, and parent session lineage when this is a child run
@@ -90,7 +94,7 @@ The callback receives:
90
94
  - `channel`: the channel's `kind` and the metadata projected by the active channel
91
95
  - `modelInput`: the final instructions and messages passed to the model call
92
96
 
93
- A channel exposes its identity through `kind`, the discriminant you narrow on. For authored channels it is `channel:<name>`, where `<name>` is the channel's filename under `agent/channels/`, so `agent/channels/support.ts` is `channel:support`. Framework channels use `http`, `schedule`, or `subagent`; an unrecognized or absent kind normalizes to `unknown`. The kind is also emitted as the `eve.channel.kind` span attribute.
97
+ A channel exposes its identity through `kind`, the discriminant you narrow on. For authored channels it is `channel:<name>`, where `<name>` is the channel's filename under `agent/channels/`, so `agent/channels/support.ts` is `channel:support`. Framework channels use `http`, `schedule`, or `subagent`, and an unrecognized or absent kind normalizes to `unknown`. The kind is also emitted as the `eve.channel.kind` span attribute. Eve emits compiler-owned typings keyed by the channel filename, so you can narrow either by checking `input.channel.kind === "channel:support"` or by using `isChannel(input.channel, supportChannel)`.
94
98
 
95
99
  Channel metadata is channel-owned. Built-in channels expose only the fields they choose to make observable; Slack, for example, projects `channelId`, `teamId`, `threadTs`, and `triggeringUserId` from its durable channel state. User-authored channels expose their own projection by returning `metadata(state)` from `defineChannel`. Runtime instrumentation never falls back to raw channel state.
96
100
 
@@ -115,7 +119,7 @@ Eve creates the `ai.eve.turn` parent span per turn and passes enriched telemetry
115
119
 
116
120
  Separately from OpenTelemetry, Eve tags every workflow run with reserved `$eve.*` attributes. These live on the Vercel Workflow run, queryable in the Workflow dashboard, not on OTel spans, and you do not configure them: they are framework-owned and emitted automatically on every session, turn, and subagent run, whether or not an `instrumentation.ts` file is present. Authored code cannot set or override the `$eve.` namespace.
117
121
 
118
- Their job is to let a dashboard reconstruct the tree of runs behind a single agent invocation and surface model and token usage without reading run bodies.
122
+ They let a dashboard reconstruct the tree of runs behind a single agent invocation and surface model and token usage without reading run bodies.
119
123
 
120
124
  Structural tags describe each run's place in the tree:
121
125
 
@@ -134,7 +138,9 @@ Per-turn usage tags are written on each step of a turn, accumulating cumulative
134
138
 
135
139
  Tag writes are best-effort: a failure is logged once per process and then swallowed, so a broken tag emit never breaks the agent.
136
140
 
137
- These tags power the **Agent Runs** tab in the Vercel dashboard. When you deploy on Vercel, the platform auto-detects `eve` as the framework and surfaces an Agent Runs view under your project's **Observability** tab, where you can browse sessions and drill into each conversation's trace no `instrumentation.ts` required. The tab is currently gated per team; see [Deployment](./deployment#view-runs-in-the-dashboard) for enablement. Agent Runs is separate from the OpenTelemetry export above: use OTel when you want spans in Braintrust, Datadog, or another third-party backend.
141
+ These tags power the **Agent Runs** tab in the Vercel dashboard. When you deploy on Vercel, the platform auto-detects `eve` as the framework and surfaces an Agent Runs view under your project's **Observability** tab, where you can browse sessions and drill into each conversation's trace, with no `instrumentation.ts` required. The tab is currently gated per team. See [Deployment](./deployment#view-runs-in-the-dashboard) for enablement. Agent Runs is separate from the OpenTelemetry export above. Use OTel when you want spans in Braintrust, Datadog, or another third-party backend.
142
+
143
+ Note: By default, telemetry records full message history and model outputs You may need to disclose these data flows in your privacy materials if utilized.
138
144
 
139
145
  ## Debugging
140
146
 
@@ -151,17 +157,17 @@ When `eve build` fails on discovery errors, the CLI prints the full diagnostics
151
157
 
152
158
  ### Common failures
153
159
 
154
- | Symptom | Likely cause and fix |
155
- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
156
- | Tool not discovered (the model never sees it) | Run `eve info`. Confirm the file is in the right slot (`agent/tools/<name>.ts`) and default-exports `defineTool(...)`, and check `.eve/diagnostics.json` for shape errors. `schedules/` are root-only. |
157
- | Model won't call a tool it should | Tighten the tool `description` and `inputSchema`; put procedural guidance in a [skill](../skills), not the description. Confirm it's in the active set with `eve info`. |
158
- | Stuck on `session.waiting` | The turn is parked on an approval, a question, or a connection sign-in. Answer it, or POST a follow-up with the `continuationToken` (a stale token is rejected). |
159
- | 401 on production routes | Expected: auth fails closed. Replace `placeholderAuth()`, and set `VERCEL_PROJECT_ID` and environment so `vercelOidc()` accepts user tokens. See [Auth & route protection](./auth-and-route-protection). |
160
- | Build fails with discovery errors | Read the printed diagnostics and `.eve/diagnostics.json`; confirm the root-vs-subagent boundary is valid and secrets come from env vars. |
160
+ | Symptom | Likely cause and fix |
161
+ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
162
+ | Tool not discovered (the model never sees it) | Run `eve info`. Confirm the file is in the right slot (`agent/tools/<name>.ts`) and default-exports `defineTool(...)`, and check `.eve/diagnostics.json` for shape errors. `schedules/` are root-only. |
163
+ | Model won't call a tool it should | Tighten the tool `description` and `inputSchema`; put procedural guidance in a [skill](../skills), not the description. Confirm it's in the active set with `eve info`. |
164
+ | Stuck on `session.waiting` | The turn is parked on an approval, a question, or a connection sign-in. Answer it, or POST a follow-up with the `continuationToken` (a stale token is rejected). |
165
+ | 401 on production routes | Expected: auth fails closed. Replace `placeholderAuth()`, and set `VERCEL_PROJECT_ID` and environment so `vercelOidc()` accepts user tokens. See [Auth and route protection](./auth-and-route-protection). |
166
+ | Build fails with discovery errors | Read the printed diagnostics and `.eve/diagnostics.json`; confirm the root-vs-subagent boundary is valid and secrets come from env vars. |
161
167
 
162
168
  ## What to read next
163
169
 
164
170
  - [`agent.ts`](../agent-config)
165
171
  - [Hooks](./hooks): observe the runtime event stream
166
- - [Dev TUI](./dev-tui): drive the agent locally
172
+ - [Local Development](./dev-tui): drive the agent locally
167
173
  - [Evals](../evals/overview): repeatable scored checks