experimental-ash 0.41.0 → 0.43.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 (158) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/docs/internals/hooks.md +53 -11
  3. package/dist/docs/public/README.md +8 -8
  4. package/dist/docs/public/advanced/{auth-and-route-protection.md → auth-and-route-protection.mdx} +11 -0
  5. package/dist/docs/public/advanced/context-control.md +4 -4
  6. package/dist/docs/public/advanced/{evals.md → evals.mdx} +11 -1
  7. package/dist/docs/public/advanced/{hooks.md → hooks.mdx} +37 -46
  8. package/dist/docs/public/advanced/instrumentation.md +92 -3
  9. package/dist/docs/public/advanced/project-layout.md +5 -5
  10. package/dist/docs/public/advanced/runs-and-streaming.md +8 -2
  11. package/dist/docs/public/advanced/session-context.md +1 -1
  12. package/dist/docs/public/advanced/typescript-api.md +49 -6
  13. package/dist/docs/public/advanced/vercel-deployment.md +1 -1
  14. package/dist/docs/public/agent-ts.md +5 -5
  15. package/dist/docs/public/channels/{discord.md → discord.mdx} +11 -0
  16. package/dist/docs/public/channels/index.md +10 -10
  17. package/dist/docs/public/channels/{slack.md → slack.mdx} +11 -0
  18. package/dist/docs/public/channels/{teams.md → teams.mdx} +12 -0
  19. package/dist/docs/public/channels/{telegram.md → telegram.mdx} +11 -0
  20. package/dist/docs/public/channels/{twilio.md → twilio.mdx} +11 -0
  21. package/dist/docs/public/{connections.md → connections.mdx} +18 -6
  22. package/dist/docs/public/frontend/README.md +16 -0
  23. package/dist/docs/public/frontend/meta.json +3 -0
  24. package/dist/docs/public/frontend/nextjs.md +192 -0
  25. package/dist/docs/public/frontend/use-ash-agent.md +332 -0
  26. package/dist/docs/public/{getting-started.md → getting-started.mdx} +12 -1
  27. package/dist/docs/public/{human-in-the-loop.md → human-in-the-loop.mdx} +12 -1
  28. package/dist/docs/public/meta.json +1 -0
  29. package/dist/docs/public/sandbox.md +1 -1
  30. package/dist/docs/public/{schedules.md → schedules.mdx} +9 -0
  31. package/dist/docs/public/skills.md +2 -2
  32. package/dist/docs/public/{subagents.md → subagents.mdx} +10 -0
  33. package/dist/docs/public/{tools.md → tools.mdx} +62 -36
  34. package/dist/src/channel/adapter.d.ts +13 -0
  35. package/dist/src/channel/instrumentation.d.ts +10 -0
  36. package/dist/src/channel/instrumentation.js +1 -0
  37. package/dist/src/channel/send.js +1 -1
  38. package/dist/src/channel/types.d.ts +16 -0
  39. package/dist/src/cli/commands/channels.d.ts +2 -1
  40. package/dist/src/cli/commands/channels.js +1 -1
  41. package/dist/src/compiler/manifest.d.ts +13 -1
  42. package/dist/src/compiler/manifest.js +1 -1
  43. package/dist/src/compiler/module-map.js +1 -1
  44. package/dist/src/compiler/normalize-manifest.js +1 -1
  45. package/dist/src/compiler/normalize-skill.d.ts +15 -2
  46. package/dist/src/compiler/normalize-skill.js +1 -1
  47. package/dist/src/compiler/normalize-tool.js +1 -1
  48. package/dist/src/context/dynamic-skill-lifecycle.d.ts +23 -0
  49. package/dist/src/context/dynamic-skill-lifecycle.js +1 -0
  50. package/dist/src/context/dynamic-tool-lifecycle.d.ts +19 -23
  51. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  52. package/dist/src/context/hook-lifecycle.d.ts +4 -6
  53. package/dist/src/context/hook-lifecycle.js +1 -1
  54. package/dist/src/context/keys.d.ts +10 -9
  55. package/dist/src/context/keys.js +1 -1
  56. package/dist/src/context/providers/connection.d.ts +9 -0
  57. package/dist/src/context/providers/connection.js +1 -1
  58. package/dist/src/context/providers/sandbox.js +1 -1
  59. package/dist/src/execution/ash-workflow-attributes.d.ts +118 -0
  60. package/dist/src/execution/ash-workflow-attributes.js +1 -0
  61. package/dist/src/execution/channel-context.d.ts +5 -0
  62. package/dist/src/execution/channel-context.js +1 -0
  63. package/dist/src/execution/create-session-step.d.ts +28 -1
  64. package/dist/src/execution/create-session-step.js +1 -1
  65. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  66. package/dist/src/execution/durable-session-store.d.ts +7 -0
  67. package/dist/src/execution/node-step.d.ts +2 -2
  68. package/dist/src/execution/node-step.js +1 -1
  69. package/dist/src/execution/runtime-context.js +1 -1
  70. package/dist/src/execution/sandbox/prewarm.js +1 -1
  71. package/dist/src/execution/session.d.ts +6 -0
  72. package/dist/src/execution/session.js +2 -2
  73. package/dist/src/execution/skills/instructions.d.ts +3 -2
  74. package/dist/src/execution/subagent-tool.js +1 -1
  75. package/dist/src/execution/workflow-entry.js +1 -1
  76. package/dist/src/execution/workflow-steps.js +1 -1
  77. package/dist/src/harness/attachment-staging.js +1 -1
  78. package/dist/src/harness/code-mode.d.ts +0 -5
  79. package/dist/src/harness/code-mode.js +1 -1
  80. package/dist/src/harness/emission.d.ts +1 -1
  81. package/dist/src/harness/emission.js +1 -1
  82. package/dist/src/harness/instrumentation-config.d.ts +1 -1
  83. package/dist/src/harness/instrumentation-metadata.d.ts +23 -0
  84. package/dist/src/harness/instrumentation-metadata.js +1 -0
  85. package/dist/src/harness/otel-integration.d.ts +2 -2
  86. package/dist/src/harness/otel-integration.js +1 -1
  87. package/dist/src/harness/step-hooks.js +1 -1
  88. package/dist/src/harness/tool-loop.js +1 -1
  89. package/dist/src/harness/turn-tag-state.d.ts +50 -0
  90. package/dist/src/harness/turn-tag-state.js +1 -0
  91. package/dist/src/harness/types.d.ts +20 -2
  92. package/dist/src/internal/application/package.js +1 -1
  93. package/dist/src/internal/authored-definition/schema-backed.d.ts +0 -1
  94. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  95. package/dist/src/internal/instrumentation.d.ts +39 -0
  96. package/dist/src/internal/instrumentation.js +1 -0
  97. package/dist/src/internal/workflow/builtins.d.ts +32 -0
  98. package/dist/src/internal/workflow/builtins.js +1 -1
  99. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +1 -1
  100. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +1 -1
  101. package/dist/src/internal/workflow-bundle/workflow-core-shim.d.ts +34 -0
  102. package/dist/src/internal/workflow-bundle/workflow-core-shim.js +1 -1
  103. package/dist/src/internal/workflow-bundle/workflow-transformer.js +1 -1
  104. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  105. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +2 -2
  106. package/dist/src/public/channels/slack/attachments.js +1 -1
  107. package/dist/src/public/channels/slack/index.d.ts +1 -1
  108. package/dist/src/public/channels/slack/slackChannel.d.ts +6 -0
  109. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  110. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  111. package/dist/src/public/channels/twilio/index.d.ts +1 -1
  112. package/dist/src/public/channels/twilio/twilioChannel.d.ts +6 -0
  113. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  114. package/dist/src/public/definitions/defineChannel.d.ts +12 -2
  115. package/dist/src/public/definitions/defineChannel.js +1 -1
  116. package/dist/src/public/definitions/hook.d.ts +3 -11
  117. package/dist/src/public/definitions/instrumentation.d.ts +88 -2
  118. package/dist/src/public/definitions/skill.d.ts +5 -0
  119. package/dist/src/public/definitions/tool.d.ts +25 -66
  120. package/dist/src/public/definitions/tool.js +1 -1
  121. package/dist/src/public/instrumentation/index.d.ts +1 -4
  122. package/dist/src/public/instrumentation/index.js +1 -1
  123. package/dist/src/public/skills/index.d.ts +2 -0
  124. package/dist/src/public/skills/index.js +1 -1
  125. package/dist/src/public/tools/index.d.ts +2 -2
  126. package/dist/src/public/tools/index.js +1 -1
  127. package/dist/src/runtime/agent/mock-model-adapter.js +4 -7
  128. package/dist/src/runtime/agent/mock-model-skill-selection.d.ts +9 -0
  129. package/dist/src/runtime/agent/mock-model-skill-selection.js +4 -0
  130. package/dist/src/runtime/attributes/emit.d.ts +73 -0
  131. package/dist/src/runtime/attributes/emit.js +1 -0
  132. package/dist/src/runtime/channels/registry.js +1 -1
  133. package/dist/src/runtime/connections/mcp-client.js +1 -1
  134. package/dist/src/runtime/framework-tools/code-mode-connection-auth.d.ts +2 -0
  135. package/dist/src/runtime/framework-tools/connection-search-dynamic.d.ts +34 -0
  136. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -0
  137. package/dist/src/runtime/framework-tools/index.d.ts +7 -5
  138. package/dist/src/runtime/framework-tools/index.js +1 -1
  139. package/dist/src/runtime/prompt/connections.js +1 -1
  140. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  141. package/dist/src/runtime/resolve-agent.js +1 -1
  142. package/dist/src/runtime/resolve-dynamic-skill.d.ts +8 -0
  143. package/dist/src/runtime/resolve-dynamic-skill.js +1 -0
  144. package/dist/src/runtime/resolve-dynamic-tool.js +1 -1
  145. package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
  146. package/dist/src/runtime/sessions/runtime-context-keys.js +1 -1
  147. package/dist/src/runtime/types.d.ts +13 -4
  148. package/dist/src/shared/dynamic-tool-definition.d.ts +57 -80
  149. package/dist/src/shared/dynamic-tool-definition.js +1 -1
  150. package/dist/src/shared/guards.d.ts +14 -0
  151. package/dist/src/shared/guards.js +1 -1
  152. package/dist/src/shared/skill-definition.d.ts +5 -4
  153. package/dist/src/shared/tool-definition.d.ts +12 -0
  154. package/package.json +2 -1
  155. package/dist/src/runtime/framework-tools/connection-search.d.ts +0 -57
  156. package/dist/src/runtime/framework-tools/connection-search.js +0 -1
  157. package/dist/src/runtime/framework-tools/connection-tools.d.ts +0 -55
  158. package/dist/src/runtime/framework-tools/connection-tools.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.43.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 52146a5: Migrate `connection_search` from a framework tool singleton to a dynamic tool resolver. Discovered connection tools are now derived from conversation history via `ctx.messages` — no `DiscoveredConnectionToolsKey` durable state or `onCompact` hook needed. Deletes `connection-tools.ts` and the old `connection-search.ts` implementation.
8
+ - 2469218: Unified dynamic tool API: `defineTools` and `defineTool({ events })` are replaced by `defineDynamic`, and the `tool()` entry wrapper is replaced by `defineTool`. Handlers return either a single `defineTool(...)` (named after the file slug) or a `Record<string, defineTool(...)>` (named `slug__key`). The `identity` field is removed from compiled manifests — single vs map detection is now runtime-based.
9
+ - 192a1eb: Add dynamic instrumentation metadata via `metadata["step.started"]`, with per-attempt session, turn, step, channel, and model-input access. The channel projection exposes `kind` (`channel:<filename>` for authored channels, or `http`/`schedule`/`subagent`), also emitted as the `ash.channel.kind` span attribute. Authored metadata is parsed defensively: resolver failures, thenables, reserved `ash.*` keys, and non-string values are dropped without interrupting the model call.
10
+ - d7de8e0: Clean up `DynamicToolResolveContext`: remove `__cache`, add `messages` for history access, add `toModelOutput` to `DynamicToolEntry`, and run the dynamic tool bundler transform on framework tools during the ash build.
11
+ - dc0db8e: Dynamic skills can now be authored with `defineDynamic` + `defineSkill` in `agent/skills/`, matching the `defineDynamic` + `defineTool` pattern for dynamic tools. Resolvers subscribe to lifecycle events (`session.started`, `turn.started`, `step.started`) and return branded `defineSkill(...)` entries that are materialized to the sandbox. Single returns use the file slug as the skill name; map returns use `slug__key`. Hook-contributed skills via `LifecycleHookResult.skills` continue to work but are deprecated in favor of this approach.
12
+ - 80b4c1d: Remove `LifecycleHookResult.skills` — lifecycle hooks can no longer contribute skills. Use `defineDynamic` + `defineSkill` in `agent/skills/` instead.
13
+
14
+ ### Patch Changes
15
+
16
+ - 3df1814: Emit `$ash.*` workflow run attributes for agent run observability. Every
17
+ session, turn, and subagent run now tags itself with a small set of
18
+ reserved-namespace attributes (`$ash.type`, `$ash.parent`,
19
+ `$ash.root`, `$ash.subagent`, `$ash.trigger`, `$ash.title`,
20
+ `$ash.model`, `$ash.input_tokens`, `$ash.output_tokens`,
21
+ `$ash.cache_read_tokens`, `$ash.tool_count`) so the Vercel dashboard can
22
+ hydrate the Agent Runs view via the workflow runtime's
23
+ `workflow_run_attributes` table without scraping events.
24
+
25
+ Tag writes are best-effort — runtime failures are logged once per process
26
+ and swallowed so a broken attribute write can never break a session. No
27
+ public API changes; authored agent code never sees the `$ash.*` namespace.
28
+ See `research/active/workflow-run-attributes.md` for the full design.
29
+
30
+ - 70a2d17: Authenticate Slack Enterprise Grid file downloads so agents can read private attachments served from workspace `enterprise.slack.com` hosts.
31
+ - 08e2696: Deploy scaffolded Web Chat routes to Vercel when channel setup adds a deployable route.
32
+
33
+ ## 0.42.0
34
+
35
+ ### Minor Changes
36
+
37
+ - edce3c9: Unified event dispatch: single `handleEvent` pipeline for all event-based handlers (channels, hooks, dynamic tools). Dynamic tool entries now require the `tool()` wrapper — the bundler transform only matches `execute` inside `tool()` calls.
38
+
3
39
  ## 0.41.0
4
40
 
5
41
  ### Minor Changes
@@ -124,13 +124,35 @@ itself throws while the dispatcher is emitting the recoverable
124
124
  `session.failed` instead. This is the bounded second-order behavior
125
125
  when both a `lifecycle.turn` and a `turn.failed` event hook fail.
126
126
 
127
- Stream-event dispatch is `dispatchStreamEventHooks`, called from each
128
- runtime's `HarnessEmitFn` composer **after** the channel adapter's
129
- event handler runs. The dispatcher reads the typed bucket
127
+ ### Stream event dispatch (`handleEvent`)
128
+
129
+ Every stream event passes through one `handleEvent` function
130
+ (`execution/workflow-steps.ts`) that coordinates three stages:
131
+
132
+ ```
133
+ handleEvent(event):
134
+ 1. emit(event) — channel adapter handler + durable stream write
135
+ 2. dispatchStreamEventHooks — typed bucket, then wildcard bucket
136
+ 3. dispatchDynamicToolEvent — resolvers subscribed to event.type
137
+ ```
138
+
139
+ `emit` handles the channel adapter's event handler (can mutate
140
+ adapter state, can transform the event) and writes the (possibly
141
+ transformed) event to the durable stream. The durable record is
142
+ consistent with what was written even if a downstream handler throws.
143
+
144
+ `dispatchStreamEventHooks` reads the typed bucket
130
145
  (`registry.streamEventsByType.get(eventType)`) followed by the flat
131
- wildcard bucket (`registry.streamEventsWildcard`); thrown errors
132
- propagate through the emit composer. The existing harness error path
133
- catches them and emits the recoverable `turn.failed` cascade.
146
+ wildcard bucket (`registry.streamEventsWildcard`). Thrown errors
147
+ propagate through the handler; the existing harness error path catches
148
+ them and emits the recoverable `turn.failed` cascade.
149
+
150
+ `dispatchDynamicToolEvent` (`context/dynamic-tool-lifecycle.ts`) runs
151
+ dynamic tool resolvers subscribed to the event type and merges
152
+ resolved tools into `DynamicToolsKey`. The tool-loop reads this key
153
+ right before each model call. See the [event dispatch
154
+ doc](../../research/active/unified-event-dispatch.md) for the full
155
+ design.
134
156
 
135
157
  ## `SessionPreparedKey`
136
158
 
@@ -162,11 +184,31 @@ The `discover/discover-subagent.ts` path walks each local subagent's
162
184
  | `lifecycle.turn` | Caught by dispatcher → recoverable `turn.failed` cascade emitted |
163
185
  | Stream-event hooks | Propagated through the emit composer → existing harness error path emits `turn.failed` |
164
186
 
165
- The contract with `HarnessEmitFn`: the wrapped emit fn calls the
166
- channel adapter's event handler first, writes the (possibly
167
- transformed) event to the durable stream, **then** fans out to authored
168
- stream-event hook subscribers. The durable record is consistent with
169
- what was written even if a downstream hook throws.
187
+ ### Full per-turn execution order
188
+
189
+ ```
190
+ dispatchHookLifecycle():
191
+ lifecycle.session hooks (once per session, produce modelContext)
192
+ lifecycle.turn hooks (once per turn, produce modelContext)
193
+
194
+ emitTurnPreamble() via handleEvent:
195
+ handleEvent(session.started) → emit, hooks, dynamic tool resolvers
196
+ handleEvent(turn.started) → emit, hooks, dynamic tool resolvers
197
+ handleEvent(message.received) → emit, hooks
198
+
199
+ emitStepStarted() via handleEvent:
200
+ handleEvent(step.started) → emit, hooks, dynamic tool resolvers
201
+
202
+ runOneModelCall():
203
+ read DynamicToolsKey (current tools from all prior events)
204
+ build effective toolset (static + dynamic + connections)
205
+ model.stream()
206
+
207
+ emitStepActions() via handleEvent:
208
+ handleEvent(actions.requested) → emit, hooks, dynamic tool resolvers
209
+ handleEvent(action.result) × N → emit, hooks, dynamic tool resolvers
210
+ handleEvent(step.completed) → emit, hooks
211
+ ```
170
212
 
171
213
  ## Testing Strategy
172
214
 
@@ -15,24 +15,24 @@ Important naming note:
15
15
 
16
16
  Read in this order:
17
17
 
18
- 1. [Getting Started](./getting-started.md)
18
+ 1. [Getting Started](./getting-started.mdx)
19
19
  2. [Project Layout](./advanced/project-layout.md)
20
20
  3. [`agent.ts`](./agent-ts.md)
21
21
  4. [TypeScript API](./advanced/typescript-api.md)
22
22
  5. [Context Control](./advanced/context-control.md)
23
23
  6. [Skills](./skills.md)
24
- 7. [Tools](./tools.md)
25
- 8. [Connections](./connections.md)
24
+ 7. [Tools](./tools.mdx)
25
+ 8. [Connections](./connections.mdx)
26
26
  9. [Workspace](./advanced/workspace.md)
27
27
  10. [Sandboxes](./sandbox.md)
28
28
  11. [Channels](./channels/README.md)
29
- 12. [Human In The Loop](./human-in-the-loop.md)
29
+ 12. [Human In The Loop](./human-in-the-loop.mdx)
30
30
  13. [Session Context](./advanced/session-context.md)
31
31
  14. [Sessions And Streaming](./advanced/runs-and-streaming.md)
32
- 15. [Subagents](./subagents.md)
33
- 16. [Schedules](./schedules.md)
34
- 17. [Evals](./advanced/evals.md)
35
- 18. [Auth And Route Protection](./advanced/auth-and-route-protection.md)
32
+ 15. [Subagents](./subagents.mdx)
33
+ 16. [Schedules](./schedules.mdx)
34
+ 17. [Evals](./advanced/evals.mdx)
35
+ 18. [Auth And Route Protection](./advanced/auth-and-route-protection.mdx)
36
36
  19. [Vercel Deployment](./advanced/vercel-deployment.md)
37
37
  20. [CLI, Build, And Debugging](./advanced/cli-build-and-debugging.md)
38
38
 
@@ -16,6 +16,17 @@ These settings apply to:
16
16
  - `POST /ash/v1/session/:sessionId`
17
17
  - `GET /ash/v1/session/:sessionId/stream`
18
18
 
19
+ <CopyPrompt text="Protect the user's Ash HTTP routes through the channel layer. In Ash, route auth and IP policy live on channel factories, usually agent/channels/ash.ts with ashChannel auth config, not in agent.ts. Inspect the existing channel file and app auth code, replace any generated exampleProductionAuth placeholder with the smallest AuthFn or helper composition, use helpers such as localDev, vercelOidc, none, httpBasic, jwtHmac, jwtEcdsa, or oidc as appropriate, keep secrets in environment variables, preserve localDev and vercelOidc behavior where useful, verify unauthenticated production browser requests are rejected with 401, and do not commit unless the user asks.">
20
+ Protect the user's Ash HTTP routes through the channel layer. In Ash, route auth and IP policy
21
+ live on channel factories, usually agent/channels/ash.ts with ashChannel auth config, not in
22
+ agent.ts. Inspect the existing channel file and app auth code, replace any generated
23
+ exampleProductionAuth placeholder with the smallest AuthFn or helper composition, use helpers such
24
+ as localDev, vercelOidc, none, httpBasic, jwtHmac, jwtEcdsa, or oidc as appropriate, keep secrets
25
+ in environment variables, preserve localDev and vercelOidc behavior where useful, verify
26
+ unauthenticated production browser requests are rejected with 401, and do not commit unless the
27
+ user asks.
28
+ </CopyPrompt>
29
+
19
30
  ## Generated Web Chat Auth
20
31
 
21
32
  `pnpm create experimental-ash-agent` scaffolds `agent/channels/ash.ts` from the Web Chat example.
@@ -116,7 +116,7 @@ Subagents are a context-control tool too:
116
116
  - they can have their own tools and their own sandbox
117
117
  - they run inside their own delegated subagent context instead of extending the root agent inline
118
118
 
119
- See [Subagents](./subagents.md).
119
+ See [Subagents](./subagents.mdx).
120
120
 
121
121
  ## Choosing The Right Lever
122
122
 
@@ -140,8 +140,8 @@ For most agents:
140
140
 
141
141
  ## What To Read Next
142
142
 
143
- - [Tools](./tools.md)
144
- - [Hooks](./hooks.md)
143
+ - [Tools](./tools.mdx)
144
+ - [Hooks](./hooks.mdx)
145
145
  - [Skills](./skills.md)
146
146
  - [Workspace](./workspace.md)
147
- - [Subagents](./subagents.md)
147
+ - [Subagents](./subagents.mdx)
@@ -13,6 +13,16 @@ Use evals when you want to:
13
13
  - compare output against expected text, JSON, SQL, or session behavior
14
14
  - export results to Braintrust for review and comparison
15
15
 
16
+ <CopyPrompt text="Add repeatable Ash evals to the user's project. Ash eval suites live under the app root evals directory, use .eval.ts files, and export defineEvalSuite from experimental-ash/evals; suite identity is derived from the file path, not an authored id or name. Inspect existing evals and agent behavior, create a focused smoke suite with model, cases or load, and scores, prefer cheap scorers such as Run.didNotFail, Run.usedTool, Text.includes, Json.deepEqual, or Sql.exactNormalized before LLM judges, add fixture loading only if it helps, run ash eval with the relevant suite flags, and do not commit unless the user asks.">
17
+ Add repeatable Ash evals to the user's project. Ash eval suites live under the app root evals
18
+ directory, use .eval.ts files, and export defineEvalSuite from experimental-ash/evals; suite
19
+ identity is derived from the file path, not an authored id or name. Inspect existing evals and
20
+ agent behavior, create a focused smoke suite with model, cases or load, and scores, prefer cheap
21
+ scorers such as Run.didNotFail, Run.usedTool, Text.includes, Json.deepEqual, or
22
+ Sql.exactNormalized before LLM judges, add fixture loading only if it helps, run ash eval with the
23
+ relevant suite flags, and do not commit unless the user asks.
24
+ </CopyPrompt>
25
+
16
26
  ## Where Evals Live
17
27
 
18
28
  Ash discovers eval suites under the app root `evals/` directory.
@@ -276,5 +286,5 @@ For most apps:
276
286
  ## What To Read Next
277
287
 
278
288
  - [TypeScript API](./typescript-api.md)
279
- - [Tools](./tools.md)
289
+ - [Tools](./tools.mdx)
280
290
  - [Sessions And Streaming](./runs-and-streaming.md)
@@ -7,10 +7,24 @@ url: /hooks
7
7
  Hooks are Ash's authored extension points for the per-turn lifecycle and
8
8
  the runtime event stream.
9
9
 
10
+ This page is about `agent/hooks/*.ts` runtime hooks. For the React client hook,
11
+ see [`useAshAgent`](../frontend/use-ash-agent.md).
12
+
10
13
  Use a hook when you want to run code **around** a turn (before the model
11
14
  runs, around stream events) without writing a tool, a context provider,
12
15
  or a channel adapter handler.
13
16
 
17
+ <CopyPrompt text="Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events. Use lifecycle hooks when the model needs ephemeral modelContext before the next model call; use events handlers for observe-only side effects after events are durably recorded. Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only when durable shared state is needed, wrap best-effort side effects so they do not fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the user asks.">
18
+ Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from
19
+ experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events.
20
+ Use lifecycle hooks when the model needs ephemeral modelContext before the next model call; use
21
+ events handlers for observe-only side effects after events are durably recorded. Inspect existing
22
+ hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only
23
+ when durable shared state is needed, wrap best-effort side effects so they do not fail sessions
24
+ accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the
25
+ user asks.
26
+ </CopyPrompt>
27
+
14
28
  ## The Main API
15
29
 
16
30
  `agent/hooks/audit.ts`
@@ -103,7 +117,7 @@ Lifecycle hooks share one signature:
103
117
  ```ts
104
118
  type LifecycleHook = (
105
119
  ctx: HookContext,
106
- ) => void | { modelContext?: readonly ModelMessage[]; skills?: readonly NamedSkillDefinition[] } | Promise<…>;
120
+ ) => void | { modelContext?: readonly ModelMessage[] } | Promise<…>;
107
121
  ```
108
122
 
109
123
  Session and turn metadata are available on `ctx.session`. For example,
@@ -113,14 +127,16 @@ Session and turn metadata are available on `ctx.session`. For example,
113
127
  model turn. `lifecycle.turn` runs **once per fresh delivery** — tool-loop
114
128
  continuations and HITL resumes do not re-fire it.
115
129
 
116
- Both keys may return `{ modelContext, skills }`. `modelContext`
117
- contributions are concatenated session-then-turn before the harness's
118
- next model call, and are never written to durable history. Dynamic
119
- `skills` are written into `/workspace/skills/<name>/` and announced
120
- through durable system history so the next model call can activate them
121
- with `load_skill`. Channels may also contribute the same `modelContext`
122
- shape through `SendPayload`; channel-provided messages appear before
123
- lifecycle hook messages.
130
+ Both keys may return `{ modelContext }`. `modelContext` contributions
131
+ are concatenated session-then-turn before the harness's next model
132
+ call, and are never written to durable history. Channels may also
133
+ contribute the same `modelContext` shape through `SendPayload`;
134
+ channel-provided messages appear before lifecycle hook messages.
135
+
136
+ Browser UIs can contribute one-turn page state through `useAshAgent()`
137
+ `prepareSend` and `clientContext`. That client context is merged before
138
+ runtime lifecycle hook context, so use it for page state and use runtime hooks
139
+ for server-side policy and durable context.
124
140
 
125
141
  ### Seed durable context
126
142
 
@@ -174,36 +190,6 @@ export default defineHook({
174
190
  `modelContext` messages are appended in registry order and visible to
175
191
  the next model call only — never persisted.
176
192
 
177
- ### Contribute dynamic skills
178
-
179
- ```ts
180
- // agent/hooks/tenant-skills.ts
181
- import { defineHook } from "experimental-ash/hooks";
182
-
183
- export default defineHook({
184
- lifecycle: {
185
- async session(ctx) {
186
- return {
187
- skills: [
188
- {
189
- name: "tenant-policy",
190
- description: "Apply tenant-specific policy before answering.",
191
- markdown: "Read the policy reference before making a recommendation.",
192
- files: {
193
- "references/policy.md": "Policy body\n",
194
- },
195
- },
196
- ],
197
- };
198
- },
199
- },
200
- });
201
- ```
202
-
203
- Dynamic skills may be returned from `lifecycle.session` or
204
- `lifecycle.turn`. They can update earlier dynamic skills with the same
205
- name, but cannot replace authored skills discovered under `agent/skills/`.
206
-
207
193
  ### Once-per-session text
208
194
 
209
195
  ```ts
@@ -229,7 +215,7 @@ export default defineHook({
229
215
  The session contribution lands in `modelContext` for the **first** model
230
216
  call only. Subsequent turns proceed without it.
231
217
 
232
- ## Stream events
218
+ ## Stream Events
233
219
 
234
220
  Side-effect-only handlers for accepted runtime events. Subscribe by
235
221
  event type, or use `*` for every event:
@@ -255,12 +241,15 @@ export default defineHook({
255
241
  });
256
242
  ```
257
243
 
258
- Composition:
244
+ ### Execution order
245
+
246
+ When a stream event fires, three things happen in order:
247
+
248
+ 1. **Emit** — the channel adapter handler runs, then the event is written to the durable stream.
249
+ 2. **Hooks** — stream event hooks fire (typed handlers first, then `*` wildcard). Return values are ignored.
250
+ 3. **Dynamic tool resolvers** — resolvers subscribed to the event type run and update the tool set.
259
251
 
260
- - Stream-event hooks run **after** Ash has accepted the event and
261
- written it to the durable stream.
262
- - Return values are ignored.
263
- - Typed event hooks run **before** `*` for the same event.
252
+ Hooks always run **after** the event is durably recorded — if a hook throws, the stream is consistent.
264
253
 
265
254
  ## Errors
266
255
 
@@ -298,7 +287,9 @@ when both are registered.
298
287
 
299
288
  ## What to read next
300
289
 
301
- - [Tools](./tools.md)
290
+ - [`useAshAgent`](../frontend/use-ash-agent.md)
291
+ - [Next.js](../frontend/nextjs.md)
292
+ - [Tools](./tools.mdx)
302
293
  - [Context Control](./context-control.md)
303
294
  - [Sessions And Streaming](./runs-and-streaming.md)
304
295
  - [Session Context](./session-context.md)
@@ -52,7 +52,95 @@ These fields control what the AI SDK records inside OTel spans:
52
52
  - `recordOutputs` -- record model outputs on spans (defaults to `true`). Set to `false` to disable
53
53
  output recording.
54
54
  - `functionId` -- override the function name on spans (defaults to the agent name)
55
- - `metadata` -- additional key-value pairs added to every span
55
+ - `metadata["step.started"]` -- a synchronous callback that returns key-value pairs for one
56
+ model-call attempt
57
+
58
+ ## Metadata
59
+
60
+ Use `metadata["step.started"]` when the metadata depends on the current session, turn, step,
61
+ channel, or model input:
62
+
63
+ ```ts
64
+ import type { SlackInstrumentationMetadata } from "experimental-ash/channels/slack";
65
+
66
+ // A Slack-backed channel authored at `agent/channels/support.ts` has kind
67
+ // `channel:support` (its filename).
68
+ declare module "experimental-ash/instrumentation" {
69
+ interface ChannelMetadataMap {
70
+ readonly "channel:support": SlackInstrumentationMetadata;
71
+ }
72
+ }
73
+
74
+ export default defineInstrumentation({
75
+ metadata: {
76
+ "step.started"(input) {
77
+ if (input.channel.kind !== "channel:support") {
78
+ return {};
79
+ }
80
+
81
+ return {
82
+ "slack.channel_id": input.channel.metadata.channelId ?? "",
83
+ "slack.user_id": input.channel.metadata.triggeringUserId ?? "",
84
+ };
85
+ },
86
+ },
87
+ });
88
+ ```
89
+
90
+ The callback runs after Ash has assembled the model input for the attempt and before constructing
91
+ the AI SDK call. That timing lets the returned values attach to the AI SDK model-call span and its
92
+ child spans. It runs for each model-call attempt, including a retry of the same logical step when
93
+ Ash changes provider settings or instructions.
94
+
95
+ The callback receives:
96
+
97
+ - `session` -- the session id, current/initiator auth, and parent session lineage when this is a
98
+ child run
99
+ - `turn` -- the stream turn id and sequence, e.g. `turn_0`
100
+ - `step` -- the zero-based step index inside the turn
101
+ - `channel` -- the channel's `kind` and the metadata projected by the active channel
102
+ - `modelInput` -- the final instructions and messages passed to the model call
103
+
104
+ A channel exposes its identity through `kind`: the discriminant you narrow on. For authored
105
+ channels it is `channel:<name>`, where `<name>` is the channel's filename under
106
+ `agent/channels/` — `agent/channels/support.ts` is `channel:support`. Framework channels use
107
+ `http`, `schedule`, or `subagent`. It is also emitted as the `ash.channel.kind` span attribute.
108
+
109
+ Channel metadata is channel-owned. Built-in channels expose only the fields they choose to make
110
+ observable; for example, Slack projects `channelId`, `teamId`, `threadTs`, and `triggeringUserId`
111
+ from its durable channel state. User-authored channels expose their own projection by returning
112
+ `metadata(state)` from `defineChannel`. To make TypeScript narrow `input.channel.metadata`,
113
+ declaration-merge the same `channel:<name>` kind into `ChannelMetadataMap`:
114
+
115
+ `metadata(state)` must return an object composed of JSON primitives, arrays, and plain objects.
116
+ Ash omits `undefined` object properties and drops projections containing values such as `Date` or
117
+ `Map` with a warning.
118
+
119
+ ```ts
120
+ import { defineChannel } from "experimental-ash/channels";
121
+
122
+ declare module "experimental-ash/instrumentation" {
123
+ interface ChannelMetadataMap {
124
+ readonly "channel:support": {
125
+ readonly triggeringUserId: string | null;
126
+ };
127
+ }
128
+ }
129
+
130
+ export default defineChannel({
131
+ state: { triggeringUserId: null as string | null },
132
+ metadata(state) {
133
+ return { triggeringUserId: state.triggeringUserId };
134
+ },
135
+ routes: [],
136
+ });
137
+ ```
138
+
139
+ Metadata failures are non-destructive. If the callback throws, returns a non-record, or returns
140
+ non-string values, Ash logs a warning, drops the invalid metadata, and continues the model call.
141
+ Thenables are rejected; the callback is intentionally synchronous. Keys beginning with `ash.` are
142
+ reserved, so authored metadata cannot override framework metadata. Instrumentation projections
143
+ containing values outside the JSON shape, such as `Date` or `Map`, are dropped with a warning.
56
144
 
57
145
  ## Trace Hierarchy
58
146
 
@@ -70,8 +158,9 @@ ash.turn {ash.session.id, ash.turn.id}
70
158
  ```
71
159
 
72
160
  Ash creates the `ash.turn` parent span per turn and passes enriched telemetry to the AI SDK so model
73
- calls and tool executions are traced automatically. Session context (`ash.version`, `ash.session.id`,
74
- `ash.continuation_token`, `ash.environment`) is injected into span metadata.
161
+ calls and tool executions are traced automatically. Session, turn, step, and channel context
162
+ (`ash.version`, `ash.session.id`, `ash.environment`, `ash.turn.id`, `ash.turn.sequence`,
163
+ `ash.step.index`, `ash.channel.kind`) is injected into span metadata.
75
164
 
76
165
  ## What To Read Next
77
166
 
@@ -39,7 +39,7 @@ my-agent/
39
39
  | `instrumentation.ts` | Telemetry config | OTel exporter setup and AI SDK span settings; auto-discovered and run before agent code |
40
40
  | `channels/` | HTTP or messaging entrypoints | Root-only today |
41
41
  | `connections/` | External service connections (MCP) | Each file defines one connection; name derived from filename |
42
- | `hooks/` | Lifecycle and stream-event subscribers | Module-backed only. Recursive directories supported. See [Hooks](./hooks.md). |
42
+ | `hooks/` | Lifecycle and stream-event subscribers | Module-backed only. Recursive directories supported. See [Hooks](./hooks.mdx). |
43
43
  | `skills/` | On-demand procedures and capability packs | Flat markdown, module-backed skills, or packaged skills |
44
44
  | `lib/` | Shared authored helper code | Import-only source, not mounted into the workspace |
45
45
  | `sandbox/` or `sandbox.ts` | The agent's single sandbox. Use top-level `sandbox.ts` for a definition-only override; use `sandbox/sandbox.ts` + optional `sandbox/workspace/**` when you also want seeded files. Framework default applies when neither is authored. | Supported on the root agent and local subagents |
@@ -156,15 +156,15 @@ Use:
156
156
  - `channels/` for HTTP or messaging ingress and delivery
157
157
  - `lib/` for shared helper modules imported by the slots above
158
158
 
159
- See [Connections](./connections.md) for the connection authorization shape, including the
159
+ See [Connections](./connections.mdx) for the connection authorization shape, including the
160
160
  `@vercel/connect/ash` helper for OAuth-backed connections.
161
161
 
162
162
  ## What To Read Next
163
163
 
164
164
  - [`agent.ts`](./agent-ts.md)
165
165
  - [Context Control](./context-control.md)
166
- - [Connections](./connections.md)
167
- - [Hooks](./hooks.md)
166
+ - [Connections](./connections.mdx)
167
+ - [Hooks](./hooks.mdx)
168
168
  - [Skills](./skills.md)
169
- - [Tools](./tools.md)
169
+ - [Tools](./tools.mdx)
170
170
  - [Channels](./channels/README.md)
@@ -13,6 +13,10 @@ The key client model is:
13
13
  - `POST /ash/v1/session/:sessionId` sends a follow-up message
14
14
  - `GET /ash/v1/session/:sessionId/stream` lets you watch the session in real time
15
15
 
16
+ React apps can use [`useAshAgent()`](../frontend/use-ash-agent.md) instead of calling these
17
+ routes directly. Next.js apps can use [`withAsh()`](../frontend/nextjs.md) to proxy these
18
+ routes to the Ash runtime from the same origin.
19
+
16
20
  Ash keeps those separate on purpose:
17
21
 
18
22
  - channels own `continuationToken` as the resume handle
@@ -109,6 +113,8 @@ Important behavior:
109
113
 
110
114
  ## What To Read Next
111
115
 
116
+ - [`useAshAgent`](../frontend/use-ash-agent.md)
117
+ - [Next.js](../frontend/nextjs.md)
112
118
  - [Session Context](./session-context.md)
113
- - [Subagents](./subagents.md)
114
- - [Schedules](./schedules.md)
119
+ - [Subagents](./subagents.mdx)
120
+ - [Schedules](./schedules.mdx)
@@ -205,5 +205,5 @@ the public accessors.
205
205
  ## What To Read Next
206
206
 
207
207
  - [Sessions And Streaming](./runs-and-streaming.md)
208
- - [Subagents](./subagents.md)
208
+ - [Subagents](./subagents.mdx)
209
209
  - [Skills](./skills.md)
@@ -14,6 +14,9 @@ Source of truth:
14
14
  - tools subpath: [`../../packages/ash/src/public/tools/index.ts`](../../packages/ash/src/public/tools/index.ts)
15
15
  - sandbox subpath: [`../../packages/ash/src/public/sandbox/index.ts`](../../packages/ash/src/public/sandbox/index.ts)
16
16
  - channel subpaths: [`../../packages/ash/src/public/channels/index.ts`](../../packages/ash/src/public/channels/index.ts)
17
+ - Next.js subpath: [`../../packages/ash/src/public/next/index.ts`](../../packages/ash/src/public/next/index.ts)
18
+ - React subpath: [`../../packages/ash/src/react/index.ts`](../../packages/ash/src/react/index.ts)
19
+ - client subpath: [`../../packages/ash/src/client/index.ts`](../../packages/ash/src/client/index.ts)
17
20
  - evals subpath: [`../../packages/ash/src/evals/index.ts`](../../packages/ash/src/evals/index.ts)
18
21
 
19
22
  ## Definition Helpers
@@ -69,6 +72,26 @@ Related exported types by subpath:
69
72
  Ash also exports lower-level runtime primitives such as `createToolLoopHarness(...)`
70
73
  and `createWorkflowRuntime(...)`.
71
74
 
75
+ Next.js helpers exported from `experimental-ash/next`:
76
+
77
+ - `withAsh(config, options?)` - wraps a Next.js config so same-origin Ash protocol routes proxy to an Ash service
78
+ - `ASH_NEXT_SERVICE_PREFIX` - default private Vercel service prefix for the Ash service
79
+ - `WithAshOptions` - options for `ashRoot`, `ashBuildCommand`, `configureVercelJson`, and `servicePrefix`
80
+
81
+ React helpers exported from `experimental-ash/react`:
82
+
83
+ - `useAshAgent(options?)` - React hook for sending turns, streaming events, projecting UI state, and tracking a session cursor
84
+ - `defaultMessageReducer()` - default chat-style event projection used by `useAshAgent()`
85
+ - `UseAshAgentOptions`, `UseAshAgentHelpers`, `UseAshAgentSnapshot`, `UseAshAgentStatus` - hook configuration and state types
86
+ - `AshMessageData`, `AshMessage`, `AshMessagePart`, `AshDynamicToolPart` - default message projection types
87
+
88
+ Client helpers exported from `experimental-ash/client`:
89
+
90
+ - `Client` - HTTP client for an Ash server
91
+ - `ClientSession` - one durable session cursor with `send(...)`, `sendMessage(...)`, and stream helpers
92
+ - `defaultMessageReducer()` - reducer shared with the React package
93
+ - `SessionState`, `SendTurnInput`, `SendMessageOptions`, `ClientOptions` - client request and session types
94
+
72
95
  Channel and Slack types exported from `experimental-ash/channels/slack`:
73
96
 
74
97
  - `slackChannel` - Slack channel factory; zero-config by default, accepts a `SlackChannelConfig`
@@ -231,7 +254,7 @@ import {
231
254
  } from "experimental-ash/tools/defaults";
232
255
  ```
233
256
 
234
- See [Tools](./tools.md) for the full override patterns.
257
+ See [Tools](./tools.mdx) for the full override patterns.
235
258
 
236
259
  ### Runtime Mode
237
260
 
@@ -239,6 +262,24 @@ See [Tools](./tools.md) for the full override patterns.
239
262
  import type { RunMode } from "experimental-ash";
240
263
  ```
241
264
 
265
+ ### Next.js Integration
266
+
267
+ ```ts
268
+ import { withAsh } from "experimental-ash/next";
269
+ ```
270
+
271
+ ### React Client
272
+
273
+ ```ts
274
+ import { useAshAgent } from "experimental-ash/react";
275
+ ```
276
+
277
+ ### HTTP Client
278
+
279
+ ```ts
280
+ import { Client } from "experimental-ash/client";
281
+ ```
282
+
242
283
  ### Eval Suite
243
284
 
244
285
  ```ts
@@ -252,14 +293,16 @@ import { Braintrust } from "experimental-ash/evals/reporters";
252
293
 
253
294
  - `defineAgent` -> [`agent.ts`](./agent-ts.md)
254
295
  - `defineChannel` and channel factories -> [Channels](./channels/README.md)
255
- - `defineTool`, `disableTool`, `defineBashTool`, `defineReadFileTool`, `defineWriteFileTool`, and `experimental-ash/tools/defaults` -> [Tools](./tools.md)
256
- - `defineHook`, `HookContext`, and lifecycle/event types -> [Hooks](./hooks.md)
296
+ - `defineTool`, `disableTool`, `defineBashTool`, `defineReadFileTool`, `defineWriteFileTool`, and `experimental-ash/tools/defaults` -> [Tools](./tools.mdx)
297
+ - `defineHook`, `HookContext`, and lifecycle/event types -> [Hooks](./hooks.mdx)
257
298
  - `defineSandbox` and `ctx.getSandbox()` -> [Sandboxes](./sandbox.md)
258
299
  - `defineSkill` and `ctx.getSkill()` -> [Skills](./skills.md)
259
300
  - `ctx.session` -> [Session Context](./session-context.md)
260
- - subagents (authored with `defineAgent` under `subagents/<id>/agent.ts`) -> [Subagents](./subagents.md)
261
- - `defineSchedule` -> [Schedules](./schedules.md)
262
- - `defineEvalSuite`, loaders, reporters, and scorers -> [Evals](./evals.md)
301
+ - `withAsh` -> [Next.js](../frontend/nextjs.md)
302
+ - `useAshAgent` -> [`useAshAgent`](../frontend/use-ash-agent.md)
303
+ - subagents (authored with `defineAgent` under `subagents/<id>/agent.ts`) -> [Subagents](./subagents.mdx)
304
+ - `defineSchedule` -> [Schedules](./schedules.mdx)
305
+ - `defineEvalSuite`, loaders, reporters, and scorers -> [Evals](./evals.mdx)
263
306
 
264
307
  ## Practical Advice
265
308
 
@@ -106,4 +106,4 @@ This is useful for smoke tests against preview or production environments.
106
106
 
107
107
  - [Workspace](./workspace.md)
108
108
  - [Sandboxes](./sandbox.md)
109
- - [Auth And Route Protection](./auth-and-route-protection.md)
109
+ - [Auth And Route Protection](./auth-and-route-protection.mdx)