eve 0.81.0 → 0.82.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 (327) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/src/channel/channel-address.d.ts +21 -2
  3. package/dist/src/channel/channel-address.js +1 -1
  4. package/dist/src/channel/channel-operations.d.ts +32 -1
  5. package/dist/src/channel/channel-operations.js +1 -1
  6. package/dist/src/channel/delivery-idempotency.d.ts +4 -0
  7. package/dist/src/channel/delivery-idempotency.js +1 -0
  8. package/dist/src/channel/routes.d.ts +6 -0
  9. package/dist/src/channel/session.d.ts +5 -0
  10. package/dist/src/channel/session.js +1 -1
  11. package/dist/src/channel/types.d.ts +14 -11
  12. package/dist/src/chunks/{use-eve-agent-Bp6nbhfV.js → use-eve-agent-DTu78IAF.js} +36 -0
  13. package/dist/src/chunks/{use-eve-agent-DLkjJIm3.js → use-eve-agent-DlznYwQk.js} +36 -0
  14. package/dist/src/cli/dev/command-options.d.ts +1 -0
  15. package/dist/src/cli/dev/diagnostic-dump.js +1 -1
  16. package/dist/src/cli/run.d.ts +1 -0
  17. package/dist/src/cli/run.js +1 -1
  18. package/dist/src/client/index.d.ts +1 -1
  19. package/dist/src/client/message-reducer-types.d.ts +7 -3
  20. package/dist/src/client/message-reducer.js +1 -1
  21. package/dist/src/client/transcript-reducer.d.ts +6 -3
  22. package/dist/src/client/transcript-reducer.js +1 -1
  23. package/dist/src/compiled/.vendor-stamp.json +2 -2
  24. package/dist/src/compiled/@eve/catalog/index.js +1 -1
  25. package/dist/src/compiler/extension-compatibility.d.ts +19 -18
  26. package/dist/src/compiler/extension-compatibility.js +1 -1
  27. package/dist/src/compiler/load-binding-namespace.d.ts +2 -0
  28. package/dist/src/compiler/load-binding-namespace.js +1 -1
  29. package/dist/src/compiler/manifest.js +1 -1
  30. package/dist/src/compiler/module-lifecycle.d.ts +1 -1
  31. package/dist/src/compiler/module-lifecycle.js +1 -1
  32. package/dist/src/compiler/module-map.js +1 -1
  33. package/dist/src/compiler/normalize-agent-config.js +1 -1
  34. package/dist/src/compiler/normalize-manifest.js +1 -1
  35. package/dist/src/compiler/project-sources.d.ts +6 -0
  36. package/dist/src/compiler/project-sources.js +1 -1
  37. package/dist/src/context/keys.d.ts +2 -0
  38. package/dist/src/context/keys.js +1 -1
  39. package/dist/src/evals/cli/eval-state-directory.d.ts +7 -0
  40. package/dist/src/evals/cli/eval-state-directory.js +1 -0
  41. package/dist/src/evals/cli/eval.js +2 -2
  42. package/dist/src/eve-channel/create-request.d.ts +2 -1
  43. package/dist/src/eve-channel/index.js +1 -1
  44. package/dist/src/eve-channel/request.d.ts +2 -1
  45. package/dist/src/eve-channel/request.js +1 -2
  46. package/dist/src/eve-channel/support.d.ts +11 -1
  47. package/dist/src/eve-channel/support.js +2 -1
  48. package/dist/src/eve-channel/types.d.ts +25 -0
  49. package/dist/src/execution/agent-sessions/context.d.ts +2 -13
  50. package/dist/src/execution/agent-sessions/context.js +1 -1
  51. package/dist/src/execution/agent-sessions/runtime-overrides.d.ts +5 -0
  52. package/dist/src/execution/agent-sessions/runtime-overrides.js +1 -0
  53. package/dist/src/execution/agent-sessions/session.d.ts +2 -2
  54. package/dist/src/execution/agent-sessions/session.js +1 -1
  55. package/dist/src/execution/agent-sessions/steps.d.ts +2 -0
  56. package/dist/src/execution/agent-sessions/steps.js +1 -1
  57. package/dist/src/execution/coordination-dispatch-shared.d.ts +5 -5
  58. package/dist/src/execution/coordination-dispatch-step.d.ts +17 -2
  59. package/dist/src/execution/coordination-dispatch-step.js +1 -1
  60. package/dist/src/execution/create-session-step.d.ts +5 -2
  61. package/dist/src/execution/create-session-step.js +1 -1
  62. package/dist/src/execution/deliver-payloads.js +1 -1
  63. package/dist/src/execution/durable-session-store.d.ts +0 -1
  64. package/dist/src/execution/dynamic-connection-recovery.js +1 -1
  65. package/dist/src/execution/dynamic-workflow/tool.js +1 -1
  66. package/dist/src/execution/effective-agent-config.d.ts +8 -2
  67. package/dist/src/execution/effective-agent-config.js +1 -1
  68. package/dist/src/execution/hook-ownership.d.ts +8 -0
  69. package/dist/src/execution/hook-ownership.js +1 -1
  70. package/dist/src/execution/install-workflow-deployment-id.d.ts +3 -0
  71. package/dist/src/execution/install-workflow-deployment-id.js +1 -0
  72. package/dist/src/execution/legacy-session/turn-workflow.js +1 -1
  73. package/dist/src/execution/node-step.d.ts +1 -0
  74. package/dist/src/execution/node-step.js +1 -1
  75. package/dist/src/execution/runtime-context.d.ts +2 -0
  76. package/dist/src/execution/runtime-context.js +1 -1
  77. package/dist/src/execution/session/active-turn.js +1 -1
  78. package/dist/src/execution/session/admission.js +1 -1
  79. package/dist/src/execution/session/checkpoint-migrations.js +1 -1
  80. package/dist/src/execution/session/entry-input.d.ts +20 -8
  81. package/dist/src/execution/session/entry-input.js +1 -1
  82. package/dist/src/execution/session/entry.js +1 -1
  83. package/dist/src/execution/session/finalization.js +1 -1
  84. package/dist/src/execution/session/handoff-steps.d.ts +75 -13
  85. package/dist/src/execution/session/handoff-steps.js +1 -1
  86. package/dist/src/execution/session/handoff.d.ts +73 -34
  87. package/dist/src/execution/session/handoff.js +1 -1
  88. package/dist/src/execution/session/input-queue.d.ts +5 -1
  89. package/dist/src/execution/session/input-queue.js +1 -1
  90. package/dist/src/execution/session/pending-history.d.ts +5 -0
  91. package/dist/src/execution/session/pending-history.js +1 -0
  92. package/dist/src/execution/session/pending-turn-state.js +1 -1
  93. package/dist/src/execution/session/program.d.ts +1 -1
  94. package/dist/src/execution/session/program.js +1 -1
  95. package/dist/src/execution/session/run-watch.d.ts +22 -0
  96. package/dist/src/execution/session/run-watch.js +1 -0
  97. package/dist/src/execution/session/timeout-control.d.ts +18 -4
  98. package/dist/src/execution/session/timeout-control.js +1 -1
  99. package/dist/src/execution/session/timeout.d.ts +1 -2
  100. package/dist/src/execution/session/timeout.js +1 -1
  101. package/dist/src/execution/session/turn-event-handler.js +1 -1
  102. package/dist/src/execution/session/turn-step-result.js +1 -1
  103. package/dist/src/execution/session/turn-step.js +1 -1
  104. package/dist/src/execution/session/turn-waiting-step.js +1 -1
  105. package/dist/src/execution/session/turn.js +1 -1
  106. package/dist/src/execution/session-inbox/address.d.ts +4 -1
  107. package/dist/src/execution/session-inbox/end-stranded-session.js +1 -1
  108. package/dist/src/execution/session-inbox/hook-release.d.ts +9 -2
  109. package/dist/src/execution/session-inbox/hook-release.js +1 -1
  110. package/dist/src/execution/session-inbox/inbox.d.ts +66 -6
  111. package/dist/src/execution/session-inbox/inbox.js +1 -1
  112. package/dist/src/execution/session-inbox/protocol.js +1 -1
  113. package/dist/src/execution/session-inbox/resume.d.ts +4 -3
  114. package/dist/src/execution/session.d.ts +0 -12
  115. package/dist/src/execution/session.js +2 -2
  116. package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
  117. package/dist/src/execution/tasks/model-step.js +1 -1
  118. package/dist/src/execution/tasks/start.d.ts +4 -4
  119. package/dist/src/execution/tasks/start.js +1 -1
  120. package/dist/src/execution/tasks/steps.js +1 -1
  121. package/dist/src/execution/terminate-child-sessions-step.js +1 -1
  122. package/dist/src/execution/tools/agent-router-workflow.js +1 -1
  123. package/dist/src/execution/tools/workflow/body.d.ts +2 -2
  124. package/dist/src/execution/workflow-runtime.d.ts +11 -12
  125. package/dist/src/execution/workflow-runtime.js +1 -1
  126. package/dist/src/harness/hitl/approvals.js +1 -1
  127. package/dist/src/harness/hitl/budget-request.d.ts +12 -6
  128. package/dist/src/harness/hitl/budget-request.js +1 -1
  129. package/dist/src/harness/hitl/budget.d.ts +7 -1
  130. package/dist/src/harness/hitl/budget.js +1 -1
  131. package/dist/src/harness/hitl/coordinator.js +1 -1
  132. package/dist/src/harness/hitl/delivery.d.ts +1 -1
  133. package/dist/src/harness/hitl/delivery.js +1 -1
  134. package/dist/src/harness/hitl/index.js +1 -1
  135. package/dist/src/harness/hitl/intake.d.ts +2 -2
  136. package/dist/src/harness/hitl/intake.js +1 -1
  137. package/dist/src/harness/messages.d.ts +5 -0
  138. package/dist/src/harness/messages.js +1 -1
  139. package/dist/src/harness/model-call/call.d.ts +2 -2
  140. package/dist/src/harness/model-call/call.js +1 -1
  141. package/dist/src/harness/model-call/request.d.ts +2 -2
  142. package/dist/src/harness/session-machine/transitions.d.ts +3 -1
  143. package/dist/src/harness/session-machine/transitions.js +1 -1
  144. package/dist/src/harness/step/context.d.ts +3 -0
  145. package/dist/src/harness/step/context.js +1 -1
  146. package/dist/src/harness/step/intake.d.ts +5 -2
  147. package/dist/src/harness/step/intake.js +1 -1
  148. package/dist/src/harness/tool-loop.js +1 -1
  149. package/dist/src/harness/turn-tag-state.d.ts +7 -26
  150. package/dist/src/harness/turn-tag-state.js +1 -1
  151. package/dist/src/harness/types.d.ts +10 -25
  152. package/dist/src/instrumentation/content.d.ts +1 -1
  153. package/dist/src/instrumentation/content.js +1 -1
  154. package/dist/src/instrumentation/lifecycle.d.ts +4 -2
  155. package/dist/src/internal/application/build-profile.d.ts +3 -0
  156. package/dist/src/internal/application/build-profile.js +1 -1
  157. package/dist/src/internal/application/compiled-artifacts.js +2 -2
  158. package/dist/src/internal/application/dev-environment.d.ts +13 -0
  159. package/dist/src/internal/application/dev-environment.js +1 -1
  160. package/dist/src/internal/application/stamped-package-version.js +1 -1
  161. package/dist/src/internal/authored-definition/connection.js +1 -1
  162. package/dist/src/internal/authored-definition/core.js +1 -1
  163. package/dist/src/internal/authored-module-build-options.d.ts +7 -0
  164. package/dist/src/internal/authored-module-build-options.js +1 -0
  165. package/dist/src/internal/authored-module-graph.d.ts +13 -0
  166. package/dist/src/internal/authored-module-graph.js +1 -0
  167. package/dist/src/internal/authored-module-loader.d.ts +22 -1
  168. package/dist/src/internal/authored-module-loader.js +1 -3
  169. package/dist/src/internal/authored-module.d.ts +7 -0
  170. package/dist/src/internal/authored-module.js +1 -1
  171. package/dist/src/internal/authored-package-boundary.js +1 -1
  172. package/dist/src/internal/bundler/nitro-rolldown.d.ts +5 -1
  173. package/dist/src/internal/bundler/nitro-rolldown.js +1 -1
  174. package/dist/src/internal/development-programmatic-source-loader.d.ts +1 -0
  175. package/dist/src/internal/development-programmatic-source-loader.js +1 -1
  176. package/dist/src/internal/mcp/skills.js +1 -1
  177. package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
  178. package/dist/src/internal/nitro/dev-runtime-generation-metadata.js +1 -1
  179. package/dist/src/internal/nitro/host/build-application.js +1 -1
  180. package/dist/src/internal/nitro/host/build-deployment-id.d.ts +8 -0
  181. package/dist/src/internal/nitro/host/build-deployment-id.js +1 -0
  182. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
  183. package/dist/src/internal/nitro/host/dev-server-state.js +1 -1
  184. package/dist/src/internal/nitro/host/skill-server-assets.js +1 -1
  185. package/dist/src/internal/nitro/host/stage-build-deployment-id.d.ts +2 -0
  186. package/dist/src/internal/nitro/host/stage-build-deployment-id.js +1 -0
  187. package/dist/src/internal/nitro/host/start-production-server.d.ts +1 -0
  188. package/dist/src/internal/nitro/host/start-production-server.js +3 -3
  189. package/dist/src/internal/nitro/routes/schedule-task.d.ts +3 -0
  190. package/dist/src/internal/nitro/routes/schedule-task.js +1 -1
  191. package/dist/src/internal/programmatic-source-loader.d.ts +1 -0
  192. package/dist/src/internal/programmatic-source-loader.js +1 -1
  193. package/dist/src/internal/schedules/disabled.d.ts +2 -0
  194. package/dist/src/internal/schedules/disabled.js +1 -0
  195. package/dist/src/internal/workflow/development-world-client.js +1 -1
  196. package/dist/src/internal/workflow/development-world-server.js +1 -1
  197. package/dist/src/internal/workflow/local-world-data-directory.d.ts +2 -1
  198. package/dist/src/internal/workflow/local-world-data-directory.js +1 -1
  199. package/dist/src/internal/workflow-bundle/builder.js +1 -1
  200. package/dist/src/protocol/message.d.ts +27 -1
  201. package/dist/src/protocol/message.js +2 -2
  202. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +13 -0
  203. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  204. package/dist/src/public/channels/chat-sdk/fetch-message.d.ts +10 -0
  205. package/dist/src/public/channels/chat-sdk/fetch-message.js +1 -0
  206. package/dist/src/public/channels/chat-sdk/index.d.ts +1 -1
  207. package/dist/src/public/channels/chat-sdk/messageToUserContent.d.ts +2 -1
  208. package/dist/src/public/channels/chat-sdk/messageToUserContent.js +1 -1
  209. package/dist/src/public/channels/chat-sdk/reply-context.d.ts +15 -0
  210. package/dist/src/public/channels/chat-sdk/reply-context.js +2 -0
  211. package/dist/src/public/channels/discord/discordChannel.d.ts +3 -0
  212. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  213. package/dist/src/public/channels/github/dispatch.js +1 -1
  214. package/dist/src/public/channels/github/githubChannel.d.ts +3 -0
  215. package/dist/src/public/channels/imessage-tapbacks.d.ts +74 -0
  216. package/dist/src/public/channels/imessage-tapbacks.js +1 -0
  217. package/dist/src/public/channels/index.d.ts +2 -1
  218. package/dist/src/public/channels/index.js +1 -1
  219. package/dist/src/public/channels/linear/linearChannel.d.ts +3 -0
  220. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  221. package/dist/src/public/channels/linq/inboundContent.d.ts +7 -0
  222. package/dist/src/public/channels/linq/inboundContent.js +1 -1
  223. package/dist/src/public/channels/linq/index.d.ts +1 -0
  224. package/dist/src/public/channels/linq/index.js +1 -1
  225. package/dist/src/public/channels/linq/linqChannel.d.ts +17 -1
  226. package/dist/src/public/channels/linq/linqChannel.js +1 -1
  227. package/dist/src/public/channels/photon/attachments.d.ts +5 -2
  228. package/dist/src/public/channels/photon/attachments.js +1 -1
  229. package/dist/src/public/channels/photon/inboundContent.d.ts +7 -0
  230. package/dist/src/public/channels/photon/inboundContent.js +2 -1
  231. package/dist/src/public/channels/photon/index.d.ts +1 -0
  232. package/dist/src/public/channels/photon/index.js +1 -1
  233. package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +17 -1
  234. package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
  235. package/dist/src/public/channels/slack/api.d.ts +14 -1
  236. package/dist/src/public/channels/slack/api.js +1 -1
  237. package/dist/src/public/channels/slack/model-context.d.ts +7 -0
  238. package/dist/src/public/channels/slack/model-context.js +1 -1
  239. package/dist/src/public/channels/slack/reply-delivery.d.ts +5 -0
  240. package/dist/src/public/channels/slack/reply-delivery.js +1 -1
  241. package/dist/src/public/channels/slack/slackChannel.d.ts +17 -3
  242. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  243. package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -0
  244. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  245. package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -0
  246. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  247. package/dist/src/public/definitions/agent.d.ts +1 -1
  248. package/dist/src/public/definitions/channel.d.ts +2 -2
  249. package/dist/src/public/definitions/channel.js +1 -1
  250. package/dist/src/public/definitions/hook.d.ts +1 -0
  251. package/dist/src/public/index.d.ts +1 -1
  252. package/dist/src/public/tools/index.d.ts +1 -1
  253. package/dist/src/runtime/agent/resolve-model.d.ts +4 -0
  254. package/dist/src/runtime/agent/resolve-model.js +1 -1
  255. package/dist/src/runtime/resolve-agent.js +1 -1
  256. package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
  257. package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -1
  258. package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
  259. package/dist/src/runtime/types.d.ts +3 -2
  260. package/dist/src/setup/scaffold/create/project.js +1 -1
  261. package/dist/src/shared/agent-definition.d.ts +31 -42
  262. package/dist/src/shared/gateway-routing.d.ts +20 -0
  263. package/dist/src/shared/gateway-routing.js +1 -0
  264. package/dist/src/shared/session-history.d.ts +22 -0
  265. package/dist/src/shared/session-history.js +1 -0
  266. package/dist/src/shared/skill-package.js +1 -1
  267. package/dist/src/subagents/tool.d.ts +1 -3
  268. package/dist/src/subagents/tool.js +1 -1
  269. package/dist/src/svelte/index.js +1 -1
  270. package/dist/src/svelte/use-eve-agent.js +1 -1
  271. package/dist/src/tools/provided/ask-question-workflow.js +1 -1
  272. package/dist/src/tools/provided/sleep-workflow.js +1 -1
  273. package/dist/src/tools/workflow-definition.d.ts +14 -1
  274. package/dist/src/tracing/eve/agent-otel-provider.js +1 -1
  275. package/dist/src/tracing/eve/conversation-context.d.ts +4 -1
  276. package/dist/src/tracing/eve/conversation-context.js +1 -1
  277. package/dist/src/tracing/eve/gateway.d.ts +5 -0
  278. package/dist/src/tracing/eve/gateway.js +1 -1
  279. package/dist/src/tracing/eve/invoke-tool-span.js +1 -1
  280. package/dist/src/tracing/eve/operation-input.d.ts +0 -12
  281. package/dist/src/tracing/eve/operation-input.js +1 -1
  282. package/dist/src/tracing/eve/otel-registration.js +1 -1
  283. package/dist/src/tracing/eve/policy.d.ts +1 -1
  284. package/dist/src/tracing/eve/policy.js +1 -1
  285. package/dist/src/tracing/eve/session.d.ts +5 -2
  286. package/dist/src/tracing/eve/session.js +1 -1
  287. package/dist/src/vue/index.js +1 -1
  288. package/dist/src/vue/use-eve-agent.js +1 -1
  289. package/docs/README.md +1 -0
  290. package/docs/agent-config.md +108 -65
  291. package/docs/channels/chat-sdk.mdx +10 -3
  292. package/docs/channels/custom.mdx +38 -6
  293. package/docs/channels/eve.mdx +73 -0
  294. package/docs/channels/linq.mdx +51 -0
  295. package/docs/channels/photon.mdx +68 -2
  296. package/docs/channels/slack.mdx +6 -2
  297. package/docs/concepts/built-in-tools.md +1 -1
  298. package/docs/concepts/execution-model-and-durability.mdx +27 -11
  299. package/docs/concepts/sessions-runs-and-streaming.md +9 -8
  300. package/docs/connections/overview.mdx +1 -1
  301. package/docs/experimental/dynamic-schedules.mdx +186 -0
  302. package/docs/experimental/meta.json +4 -0
  303. package/docs/guides/client/streaming.mdx +1 -0
  304. package/docs/guides/deployment/self-hosting.md +3 -0
  305. package/docs/guides/hooks.md +21 -1
  306. package/docs/guides/instrumentation/otel.mdx +20 -0
  307. package/docs/memory/overview.mdx +2 -1
  308. package/docs/patterns/dynamic-scheduling.md +3 -1
  309. package/docs/patterns/meta.json +1 -0
  310. package/docs/patterns/voice-notes.md +67 -0
  311. package/docs/reference/cli.md +14 -7
  312. package/docs/sandbox/index.mdx +2 -0
  313. package/docs/schedules.mdx +2 -0
  314. package/docs/subagents/index.mdx +2 -0
  315. package/docs/tools/overview.mdx +2 -1
  316. package/docs/tools/tasks-upgrade.md +12 -7
  317. package/docs/tools/tasks.md +10 -6
  318. package/docs/tools/workflows.mdx +36 -5
  319. package/docs/tutorial/connect-a-warehouse.mdx +1 -1
  320. package/docs/tutorial/run-analysis.mdx +1 -1
  321. package/package.json +1 -1
  322. package/dist/src/execution/run-session-limits.d.ts +0 -12
  323. package/dist/src/execution/run-session-limits.js +0 -1
  324. package/dist/src/harness/session-usage-limits.d.ts +0 -49
  325. package/dist/src/harness/session-usage-limits.js +0 -1
  326. package/dist/src/subagents/token-budget.d.ts +0 -15
  327. package/dist/src/subagents/token-budget.js +0 -1
@@ -53,6 +53,11 @@ export default channel;
53
53
 
54
54
  `adapters` is a map of adapter name to adapter instance; each entry mounts its own webhook (see below). `state` takes any Chat SDK state adapter: `createMemoryState()` is fine for local development, but use a durable adapter (Redis, Upstash, etc.) in production so thread subscriptions and inbound deduplication survive restarts. `send` accepts a plain string or an AI SDK `UserContent` array, and must be called from inside a Chat SDK handler — it dispatches the turn on the webhook that is currently running.
55
55
 
56
+ Pass `idempotencyKey: message.id` to `send` to prevent duplicate agent input
57
+ within the session's [recent-key window](./custom#retried-deliveries). Chat SDK's
58
+ separate duplicate check can mark a message as seen before eve accepts it, so
59
+ a shared state adapter alone does not guarantee delivery.
60
+
56
61
  Deploy once the channel file is in place:
57
62
 
58
63
  ```bash
@@ -104,7 +109,7 @@ bot.onNewMention(async (thread, message) => {
104
109
  });
105
110
  ```
106
111
 
107
- `options` accepts `thread` and `adapterName`, plus the channel send options `auth`, `context`, `outputSchema`, `title`, `callback`, and `turnPolicy`. `title` sets the eve session's display title without changing the model message; `auth` attaches an authenticated principal to the turn. `auth` defaults to `null`.
112
+ `options` accepts `thread` and `adapterName`, plus the channel send options `auth`, `context`, `outputSchema`, `title`, `callback`, `turnPolicy`, and `idempotencyKey`. `title` sets the eve session's display title without changing the model message; `auth` attaches an authenticated principal to the turn. `auth` defaults to `null`.
108
113
 
109
114
  To answer pending input requests from a handler you render yourself, call `respond(inputResponses, { thread, auth?, context? })`. Like a channel's `respond()`, it never creates a session. This example answers a custom button whose `value` carries the eve request id:
110
115
 
@@ -140,7 +145,7 @@ This policy controls overlapping eve turns. Chat SDK's separate `concurrency` op
140
145
 
141
146
  The default handlers post the agent's reply back to the thread — no `events` override required. Completed assistant messages are posted as markdown (`{ markdown: … }`) so adapters render rich text and email HTML rather than a raw string.
142
147
 
143
- Streaming is on by default: the channel posts an initial message and edits it as tokens arrive (`message.appended`), throttled by `streamingEditIntervalMs` (default `1000`). Set `streaming: false` for surfaces that deliver one message per turn — email, for example — so the reply posts once on completion instead of editing:
148
+ Streaming is on by default: the channel posts an initial message and edits it as tokens arrive (`message.appended`), throttled by `streamingEditIntervalMs` (default `1000`). Set `streaming: false` for surfaces that deliver one message per turn or can't edit sent messages — email and WhatsApp, for example — so the reply posts once on completion instead of editing:
144
149
 
145
150
  ```ts
146
151
  chatSdkChannel({
@@ -245,7 +250,9 @@ bot.onNewMention(async (thread, message) => {
245
250
  });
246
251
  ```
247
252
 
248
- When the adapter can download an attachment (it sets `fetchData`, as the Slack, Google Chat, WhatsApp, and Twilio adapters do), eve downloads it with the adapter's credentials when the turn runs, not in the webhook, and passes the bytes. Platform file URLs usually need those credentials, so the model provider couldn't open them. eve rebuilds the download with the adapter's `rehydrateAttachment`; an adapter without one can't download after the webhook returns, so its files reach the agent as a short note. A failed download, or a file over 25 MB, also becomes a note. An attachment with only a URL is downloaded by eve when the URL is public `https:`; otherwise it becomes a note.
253
+ When the adapter can download an attachment (it sets `fetchData`, as the Slack, Google Chat, WhatsApp, and Twilio adapters do), eve downloads it with the adapter's credentials when the turn runs, not in the webhook, and passes the bytes. Platform file URLs usually need those credentials, so the model provider couldn't open them. eve rebuilds the download with the adapter's `rehydrateAttachment`; an adapter without one can't download after the webhook returns, so its files reach the agent as a short note. A failed download, or a file over 25 MB, also becomes a note. An attachment with only a URL is downloaded by eve when the URL is public `https:`; otherwise it becomes a note. An attachment with neither a download nor a URL also becomes a note, so the agent still knows a file was sent.
254
+
255
+ Audio attachments, such as voice notes, reach the model as a file label, not as sound. To answer them, give the agent a transcription tool; see [Voice notes](../patterns/voice-notes).
249
256
 
250
257
  See [File uploads](./custom#file-uploads) for how eve stages remote file URLs before the model call.
251
258
 
@@ -104,11 +104,11 @@ part of the authored channel API.
104
104
  ## Channel operations and session handles
105
105
 
106
106
  Channel operations are dynamic: every call targets whichever session currently
107
- owns the address. Only `send()` can create a session when the address is unowned.
108
- Cold-start sends return their accepted candidate without waiting for address
109
- ownership. If simultaneous sends race, eve forwards the losing candidate's
110
- message to the winner; resolve the address after startup when you need a fixed
111
- handle to the canonical owner.
107
+ owns the address. `create()` and `send()` can create a session when the address is unowned.
108
+ Without an [idempotency key](#retried-deliveries), a new session's handle can
109
+ return before it claims the address. If another send wins the address, eve
110
+ forwards the message to that session. Call `resolveSession(address)` after
111
+ startup to get the winning session's handle.
112
112
 
113
113
  ```ts
114
114
  const source = from(threadId);
@@ -206,6 +206,38 @@ Attaching does no lookup. The first operation reports whether the ID is active.
206
206
  Call `resolveSession(address)` only when you explicitly need to snapshot an
207
207
  address's current owner as a fixed handle.
208
208
 
209
+ ## Retried deliveries
210
+
211
+ After verifying a webhook, pass its stable event ID as `idempotencyKey`. Reuse the key
212
+ when retrying a failed or timed-out send:
213
+
214
+ ```ts
215
+ const session = await from(conversationId).send(message, {
216
+ auth,
217
+ idempotencyKey: providerEventId,
218
+ });
219
+ ```
220
+
221
+ Channel and fixed-session `send()` and `respond()` calls accept this option,
222
+ as does the Chat SDK bridge. Keys must contain 1–256 characters and identify one
223
+ delivery. Prefix IDs with the provider or event type if they can overlap.
224
+ Reusing a retained key ignores the new input, even if the message changed.
225
+
226
+ Each session remembers its last 1,024 distinct keys. Retries do not extend
227
+ retention and can run again after eviction. Aliases share this window; replay
228
+ and owner handoffs preserve it. Other sessions have separate windows, including
229
+ those created after reset, expiration, or replacement. Replicas must share the
230
+ same durable Workflow World.
231
+
232
+ A known duplicate does not interrupt generation, call `deliver`, or start a
233
+ turn. A keyed send that creates a session waits up to 30 seconds for the winning
234
+ session's handle. Sends to existing sessions return after eve stores the
235
+ delivery, without waiting for a turn or reporting whether it was a duplicate.
236
+
237
+ Await `send()` before acknowledging a webhook when the provider allows it.
238
+ Acknowledging sooner can lose work. This does not make tool calls, outbound
239
+ posts, or work done before `send()` exactly once.
240
+
209
241
  ## Wrap an existing channel's routes
210
242
 
211
243
  A channel value can be spread into a new object with replaced `routes`. eve keeps the channel's adapter, events, and build metadata (such as the Slack app manifest) on the spread copy, so `{ ...channel, routes }` compiles and runs like the original.
@@ -374,7 +406,7 @@ Before sending:
374
406
 
375
407
  - Verify the provider's signature and validate the payload, including size limits. Derive `event.source` from the verified sender, not an untrusted payload field.
376
408
  - Filter out events that should not reach the agent.
377
- - Deduplicate retried events in your application if duplicates would cause unwanted sessions or posts. `send(...)` does not deduplicate them.
409
+ - Deduplicate retries in your application unless the destination's `receive` hook passes a [delivery key](#retried-deliveries) to `from(address).send()`.
378
410
 
379
411
  The supplied `auth` identifies who started the session, available to tools and event handlers as `session.auth.initiator`.
380
412
 
@@ -341,6 +341,79 @@ export default eveChannel({
341
341
 
342
342
  `onMessage` must return an auth result. Return `title` alongside `auth` to set the title when the dispatch creates a session or sends the first message to a prewarmed session. A successful canonical eve HTTP message always dispatches and therefore always produces or continues a session.
343
343
 
344
+ ### Turn context
345
+
346
+ `context` strings become user messages in durable session history. For context that applies to one turn only, return `clientContext` instead. It replaces the request's [`clientContext`](../guides/client/messages) for that turn: it reaches every model call in the turn and is not kept in session history.
347
+
348
+ `ctx.eve.clientContext` holds the request's `clientContext` exactly as the client sent it, or `undefined` when it sent none. Use it to check or correct client-supplied state before the model sees it:
349
+
350
+ ```ts title="agent/channels/eve.ts"
351
+ import { eveChannel, defaultEveAuth } from "eve/channels/eve";
352
+ import { vercelOidc } from "eve/channels/auth";
353
+
354
+ import { getVerifiedPlan } from "../lib/billing";
355
+
356
+ export default eveChannel({
357
+ auth: [vercelOidc()],
358
+ async onMessage(ctx) {
359
+ const auth = defaultEveAuth(ctx);
360
+ const plan = await getVerifiedPlan(auth);
361
+ return { auth, clientContext: [`The user's plan is ${plan}.`] };
362
+ },
363
+ });
364
+ ```
365
+
366
+ - Omit `clientContext` to keep the request's context. Return `[]` to drop it.
367
+ - The returned value takes the same shape as request `clientContext` (`ClientContextValue`, exported from `eve/channels/eve`): a string, an array of strings, or a JSON object. eve turns it into model messages the same way.
368
+ - eve labels client-supplied entries `Client context:` for the model. Returned context is sent without the label.
369
+ - `onMessage` runs only for requests with a message. An input response keeps the context its request sent.
370
+
371
+ ### Start from prior history
372
+
373
+ Pass `history` to bring in conversation eve did not produce, such as a transcript your app stored, a conversation moving from another system, or a branch of an earlier exchange. The model sees each entry as a real user or assistant turn, not as a transcript inside one message.
374
+
375
+ Create a session that starts from stored history and waits for its first message with `create()` on a channel address. No turn runs until a message arrives:
376
+
377
+ ```ts title="agent/channels/app.ts"
378
+ import { defineChannel, POST } from "eve/channels";
379
+
380
+ export default defineChannel({
381
+ routes: [
382
+ POST("/conversations/:id/session", async (req, { from, params }) => {
383
+ const auth = await authenticate(req);
384
+ // Load only conversations this caller may read.
385
+ const conversation = await db.conversations.findForUser(params.id!, auth);
386
+ const { created, session } = await from(`conversation:${conversation.id}`).create({
387
+ auth,
388
+ history: conversation.messages.map((message) => ({
389
+ id: message.id,
390
+ role: message.role, // "user" | "assistant"
391
+ text: message.body,
392
+ })),
393
+ });
394
+ if (!created) {
395
+ return Response.json({ error: "Conversation already has a session." }, { status: 409 });
396
+ }
397
+ return Response.json({ sessionId: session.id });
398
+ }),
399
+ ],
400
+ });
401
+ ```
402
+
403
+ The client then resumes that session by id, for example with `useEveAgent({ initialSession: { sessionId, streamIndex: 0 }, resume: true })`. `created` is `false` when a session already owned the address, or another `create()` or `send()` claimed it first. That session comes back unchanged and your `history` was not added, so check `created` before treating the session as seeded. Use a new address for each new conversation or branch.
404
+
405
+ When the session at the address is [stranded](../concepts/execution-model-and-durability#stranded-sessions), `create()` throws `SessionStrandedError` instead of replacing it, because that session may hold the conversation you meant to import. Call `reset()` on the address, then `create()` again.
406
+
407
+ `create()` returns once the new session claims its address, so a `send()` right after it reaches that session. It throws if no session claims the address within 15 seconds.
408
+
409
+ `send()` accepts the same `history` option. The entries are added right before that message and its `context`, whether the send creates the session or continues it. The eve channel's `onMessage` can return `history` the same way. It applies to every message it is returned for, so return it only when the message should carry those turns.
410
+
411
+ Each entry has a `role` of `"user"` or `"assistant"`, non-empty `text`, and an optional `id` that is unique within the call. Invalid history throws an `InvalidSessionHistoryError`, exported from `eve/channels`, that names the entry and the rule it broke. eve doesn't cap how many entries or how much text one call carries. Your route decides how much stored conversation to load, so trim long conversations there, for example to the newest messages. Tool calls, files, and reasoning are not supported.
412
+
413
+ eve never accepts `history` from a client request body. Resolve it on the server and authorize the caller against the stored conversation.
414
+
415
+ The stream publishes added entries in `history.imported`, right before the `message.received` of the turn they join. Each event message has the same `role`, `text`, and `id` you passed. `transcriptReducer()` from `eve/client` includes them, so its `{ role, text }` messages can be passed straight back as `history` to branch a session. The default client reducer renders them ahead of that turn's message with `metadata.imported` set and your `id` in `metadata.historyId`, so you can rejoin your own annotations. Message ids stay unique even when a later call reuses an `id`. After that, the entries are ordinary history: compaction, `clear()`, and handoffs treat them like any other turn.
416
+
344
417
  ## Clients
345
418
 
346
419
  The browser side of this API lives in the [Frontend](../guides/frontend/overview) docs, where `useEveAgent` drives the eve channel from React UI.
@@ -39,6 +39,57 @@ export default linqChannel({
39
39
 
40
40
  Set `route` to override the webhook path or `webhookVerifier` to use a different trusted-forwarder verifier.
41
41
 
42
+ ## Tapbacks
43
+
44
+ eve ignores tapbacks people send unless you pass `onTapback`. It receives the tapback and returns the same result as `onMessage`, or `null` to ignore it:
45
+
46
+ ```ts title="agent/channels/linq.ts"
47
+ import { connectLinqCredentials } from "@vercel/connect/eve";
48
+ import { defaultLinqAuth, linqChannel } from "eve/channels/linq";
49
+
50
+ export default linqChannel({
51
+ credentials: connectLinqCredentials("linq/my-agent"),
52
+ onTapback(_ctx, tapback) {
53
+ if (!tapback.added) return null;
54
+ return { auth: defaultLinqAuth(tapback) };
55
+ },
56
+ });
57
+ ```
58
+
59
+ The agent reads an accepted tapback as a message in the form iMessage uses when it shows a tapback as text, such as `Tapback: Loved “See you at 7”` or `Tapback: Removed a like tapback from a message`. The quoted text appears when Linq can return the message the tapback is on. `tapback.tapback` is `love`, `like`, `dislike`, `laugh`, `emphasize`, or `question`, or the emoji itself for an emoji tapback. `tapback.messageId` and `tapback.author` identify the message and the person. An accepted tapback starts or steers a turn like any other message, following `turnPolicy`. The agent's own tapbacks never reach `onTapback`. `onTapback` runs instead of `onMessage` for tapbacks, so repeat any sender checks from `onMessage` there. Tapbacks arrive through the `reaction.added` and `reaction.removed` webhook events, which `eve add channel/linq` subscribes to.
60
+
61
+ To let the agent send tapbacks, export the channel's tapback tool from a file in `agent/tools/`. The file name becomes the tool name:
62
+
63
+ ```ts title="agent/tools/tapback.ts"
64
+ import linq from "../channels/linq";
65
+
66
+ export default linq.tapbackTool();
67
+ ```
68
+
69
+ The model calls it with a standard tapback, such as `{ "tapback": "love" }`, or any single emoji, such as `{ "tapback": "🎉" }`, and eve reacts to the latest message someone sent in the conversation. A standard tapback's own emoji, such as `👍`, sends that tapback. The model can send a tapback alongside a reply. To let a tapback be the whole answer, also add [`no_reply`](../concepts/built-in-tools#no_reply). The tool is never added on its own, and subagents don't receive it. In a session that didn't come through this channel, such as a web chat, or before anyone has sent a message, the call fails with an error the model reads.
70
+
71
+ ### Answer approvals with a tapback
72
+
73
+ Return `answer` from `onTapback` to answer the input request eve posted in the message the tapback is on. A [tool approval](/docs/human-in-the-loop#approvals) offers the options `approve` and `cancel`, so a like or dislike on the approval prompt can approve or cancel it:
74
+
75
+ ```ts title="agent/channels/linq.ts"
76
+ import { connectLinqCredentials } from "@vercel/connect/eve";
77
+ import { defaultLinqAuth, linqChannel } from "eve/channels/linq";
78
+
79
+ export default linqChannel({
80
+ credentials: connectLinqCredentials("linq/my-agent"),
81
+ onTapback(_ctx, tapback) {
82
+ if (!tapback.added) return null;
83
+ const auth = defaultLinqAuth(tapback);
84
+ if (tapback.tapback === "like") return { auth, answer: "approve" };
85
+ if (tapback.tapback === "dislike") return { auth, answer: "cancel" };
86
+ return { auth };
87
+ },
88
+ });
89
+ ```
90
+
91
+ `answer` settles only the request posted in that message, and only while it is still waiting and offers that option. A tapback on any other message, or on a prompt that was already answered, reaches the agent as an ordinary tapback, such as `Tapback: Liked “Got it”`. The approval's response policy still decides whether the person who sent the tapback may answer. For a [question](/docs/human-in-the-loop#questions), return one of its option ids. To replace the text the agent receives for a tapback, return `message`. Don't set `message` to an option's id, label, or number: eve reads that text like a typed reply, which answers the first waiting approval no matter which message the tapback is on.
92
+
42
93
  ## Other hosts
43
94
 
44
95
  For a host without Vercel Connect, choose **Use portable credentials** during `eve add channel/linq`. eve writes `LINQ_API_KEY` and `LINQ_WEBHOOK_SECRET` to `.env.local`.
@@ -32,10 +32,16 @@ boundary. See [Overlapping messages](./overview#overlapping-messages).
32
32
 
33
33
  Set `turnPolicy: "queue"` when every response should finish before Photon starts the next message.
34
34
 
35
+ Photon uses inbound message IDs as [delivery idempotency keys](./custom#retried-deliveries).
36
+ Replicas that share a durable Workflow World ignore retries while the ID remains
37
+ in the session's recent-key window.
38
+
35
39
  Images and other files people send reach the agent as file parts. Photon's
36
40
  webhook lists each file without its bytes, so eve downloads it from Photon by
37
- its attachment id with the project's credentials. A failed download, or a file
38
- over 25 MB, reaches the agent as a short note.
41
+ its attachment id with the project's credentials. The webhook omits the id
42
+ for voice notes, so eve refetches those messages from Photon to get it. A
43
+ failed download, a file over 25 MB, or a file eve can't find an id for reaches
44
+ the agent as a short note.
39
45
 
40
46
  Customize inbound dispatch with `onMessage`. Return `null` to ignore a message, or return `title` alongside `auth` to set the title when the dispatch starts a run:
41
47
 
@@ -55,6 +61,66 @@ export default photonIMessageChannel({
55
61
  Set `route` to override the webhook path or `webhookVerifier` to use a different
56
62
  trusted-forwarder verifier.
57
63
 
64
+ ## Tapbacks
65
+
66
+ eve ignores tapbacks people send unless you pass `onTapback`. It receives the
67
+ tapback and returns the same result as `onMessage`, or `null` to ignore it:
68
+
69
+ ```ts title="agent/channels/photon.ts"
70
+ import { connectPhotonCredentials } from "@vercel/connect/eve";
71
+ import { defaultPhotonAuth, photonIMessageChannel } from "eve/channels/photon";
72
+
73
+ export default photonIMessageChannel({
74
+ credentials: connectPhotonCredentials("photon/my-agent"),
75
+ onTapback(_ctx, tapback) {
76
+ if (!tapback.added) return null;
77
+ return { auth: defaultPhotonAuth(tapback) };
78
+ },
79
+ });
80
+ ```
81
+
82
+ The agent reads an accepted tapback as a message in the form iMessage uses when
83
+ it shows a tapback as text, such as `Tapback: Loved “See you at 7”`. Photon
84
+ reports only added tapbacks, so `tapback.added` is always `true` and removing a
85
+ tapback never reaches `onTapback`. The quoted text appears when Photon can return the message the tapback is on. `tapback.tapback` is `love`,
86
+ `like`, `dislike`, `laugh`, `emphasize`, or `question`, or the emoji itself for
87
+ an emoji tapback. `tapback.messageId` and `tapback.author` identify the message
88
+ and the person. An accepted tapback starts or steers a turn like any other
89
+ message, following `turnPolicy`. The agent's own tapbacks never reach `onTapback`. `onTapback` runs instead of `onMessage` for tapbacks, so repeat any sender checks from `onMessage` there.
90
+
91
+ To let the agent send tapbacks, export the channel's tapback tool from a file in
92
+ `agent/tools/`. The file name becomes the tool name:
93
+
94
+ ```ts title="agent/tools/tapback.ts"
95
+ import photon from "../channels/photon";
96
+
97
+ export default photon.tapbackTool();
98
+ ```
99
+
100
+ The model calls it with a standard tapback, such as `{ "tapback": "love" }`, or any single emoji, such as `{ "tapback": "🎉" }`, and eve reacts to the latest message someone sent in the conversation. A standard tapback's own emoji, such as `👍`, sends that tapback. The model can send a tapback alongside a reply. To let a tapback be the whole answer, also add [`no_reply`](../concepts/built-in-tools#no_reply). The tool is never added on its own, and subagents don't receive it. In a session that didn't come through this channel, such as a web chat, or before anyone has sent a message, the call fails with an error the model reads.
101
+
102
+ ### Answer approvals with a tapback
103
+
104
+ Return `answer` from `onTapback` to answer the input request eve posted in the message the tapback is on. A [tool approval](/docs/human-in-the-loop#approvals) offers the options `approve` and `cancel`, so a like or dislike on the approval prompt can approve or cancel it:
105
+
106
+ ```ts title="agent/channels/photon.ts"
107
+ import { connectPhotonCredentials } from "@vercel/connect/eve";
108
+ import { defaultPhotonAuth, photonIMessageChannel } from "eve/channels/photon";
109
+
110
+ export default photonIMessageChannel({
111
+ credentials: connectPhotonCredentials("photon/my-agent"),
112
+ onTapback(_ctx, tapback) {
113
+ if (!tapback.added) return null;
114
+ const auth = defaultPhotonAuth(tapback);
115
+ if (tapback.tapback === "like") return { auth, answer: "approve" };
116
+ if (tapback.tapback === "dislike") return { auth, answer: "cancel" };
117
+ return { auth };
118
+ },
119
+ });
120
+ ```
121
+
122
+ `answer` settles only the request posted in that message, and only while it is still waiting and offers that option. A tapback on any other message, or on a prompt that was already answered, reaches the agent as an ordinary tapback, such as `Tapback: Liked “Got it”`. The approval's response policy still decides whether the person who sent the tapback may answer. For a [question](/docs/human-in-the-loop#questions), return one of its option ids. To replace the text the agent receives for a tapback, return `message`. Don't set `message` to an option's id, label, or number: eve reads that text like a typed reply, which answers the first waiting approval no matter which message the tapback is on.
123
+
58
124
  ## Other hosts
59
125
 
60
126
  For a host without Vercel Connect, choose **Use portable credentials** during
@@ -100,7 +100,7 @@ controls observability content capture, not access to the channel. See
100
100
 
101
101
  ### Message hooks
102
102
 
103
- Message hooks return `{ auth }` to dispatch, `null` to drop, or `{ auth, context }` to inject background into history. Public conversations use the returned `title`, or the triggering message text when `title` is omitted. DMs and private channels always use `Private message` so the run title does not expose message details.
103
+ Message hooks return `{ auth }` to dispatch, `null` to drop, `{ auth, context }` to inject background into history, or `{ auth, history }` to add prior conversation as user and assistant turns (see [Start from prior history](./eve#start-from-prior-history)). Public conversations use the returned `title`, or the triggering message text when `title` is omitted. DMs and private channels always use `Private message` so the run title does not expose message details.
104
104
 
105
105
  - `onMessage(ctx, message)` handles newly posted Slack messages. eve drops messages authored by the installed app before this hook runs, preventing self-reply loops. Messages from other bots remain visible; check `message.author?.isBot` when those should also be ignored. `ctx.isBotMentioned()` and `ctx.isSubscribed()` support mention and active-thread policies.
106
106
  - `onAppMention(ctx, message)` handles only `app_mention` and takes precedence over `onMessage`. Its default derives workspace-scoped auth.
@@ -247,6 +247,8 @@ export default slackChannel({
247
247
 
248
248
  `threadContext` requires the matching Slack history scope. Thread helpers reuse messages already loaded within the same inbound handler, and overlapping refreshes share one `conversations.replies` request. Omit it when the agent should see only direct mentions. `loadThreadContextMessages` remains available when you need custom filtering or non-model processing of the raw thread messages.
249
249
 
250
+ When the triggering message starts the thread's session, eve adds those replies as session history instead of a transcript. Replies from this installed app become assistant turns, and replies from people and other bots become user messages with their Slack user ids attached. If the agent posted a report and people replied to it, the new session sees that report as its own earlier turn. Slack shows rendered output rather than the model's original response, so these assistant turns can differ from what the model wrote. Later messages in the same thread keep the transcript behavior above. When a hook returns its own `history`, eve keeps the transcript and does not add thread replies as history.
251
+
250
252
  #### Control overlapping turns
251
253
 
252
254
  Accepted Slack messages steer by default. Before assistant answer output or local tool execution begins, steering interrupts pending model generation and continues the same turn with the correction. Executing tools finish and checkpoint their results first; after answer output starts, steering waits for the next committed workflow boundary. Reasoning and search progress do not count as answer output, and interrupted provider-managed search may run again.
@@ -302,7 +304,7 @@ Reset returns `{ status: "reset", previousSessionId }` when the thread had a ses
302
304
 
303
305
  ### Other Events API callbacks
304
306
 
305
- Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model. Each call returns the resulting session.
307
+ Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title, history })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model, and `history` adds prior conversation before the message (see [Start from prior history](./eve#start-from-prior-history)). Each call returns the resulting session.
306
308
 
307
309
  ```ts title="agent/channels/slack.ts"
308
310
  import { connectSlackCredentials } from "@vercel/connect/eve";
@@ -750,6 +752,8 @@ The agent receives the files attached to the message that reaches it. When a men
750
752
 
751
753
  Images, PDFs, audio, video, and other files all reach the agent. See [Inbound attachments](../sandbox#inbound-attachments) for which files the model reads directly and which it opens with tools. Files shared through an integration such as Google Drive or Dropbox are links to the other service, not Slack files, so eve skips them; their link stays in the message text. A download that fails or takes longer than 30 seconds reaches the model as a note.
752
754
 
755
+ Slack voice clips reach the model as an audio file label, not as sound. To answer them, give the agent a transcription tool; see [Voice notes](../patterns/voice-notes).
756
+
753
757
  Private file downloads resolve credentials from the session's app installation workspace and require the Slack bot token's `files:read` scope. Add the scope and reinstall the Slack app before sending attachments.
754
758
 
755
759
  Downloads cover Slack's own file hosts plus URLs under a configured [`api.fileBaseUrl`](#point-the-channel-at-a-different-slack-api-host), and travel on `api.fetch` when one is configured.
@@ -352,7 +352,7 @@ import { askQuestion } from "eve/tools/ask_question";
352
352
  export default askQuestion();
353
353
  ```
354
354
 
355
- `ask_question` is a [workflow tool](/docs/tools/workflows) that calls `ctx.ask()`. The model receives `{ status: "answered", answer }`, where `answer` is the chosen option's label or the user's own words. A plain follow-up message answers the question too when it is the only pending question. When other questions are also pending, a message does not answer any of them: `ask_question` withdraws its question, resolves as `{ interrupted: true }`, which the model reads as `Stopped early because a new message arrived.`, and the model reads the message next. In a session that cannot request input, such as a scheduled run, the result is `{ status: "unavailable" }` and the model continues on its own judgment. Remove the file to remove the tool.
355
+ `ask_question` is a [workflow tool](/docs/tools/workflows) that calls `ctx.ask()`. The model receives `{ status: "answered", answer }`, where `answer` is the chosen option's label or the user's own words. A plain follow-up message answers the question too when it is the first open request and the message matches an option or the question allows free text; see [Several requests at once](/docs/human-in-the-loop#several-requests-at-once). A steering message that doesn't answer it withdraws the question: `ask_question` resolves as `{ interrupted: true }`, which the model reads as `Stopped early because a new message arrived.`, and the model reads the message next. In a session that cannot request input, such as a [markdown schedule](/docs/schedules#markdown-form-fire-and-forget), the result is `{ status: "unavailable" }` and the model continues on its own judgment. Remove the file to remove the tool.
356
356
 
357
357
  ### `glob`
358
358
 
@@ -23,11 +23,15 @@ replay unit.
23
23
 
24
24
  The Workflow SDK is not inherently tied to Vercel. In local development and in a self-deployed `eve start` process, eve uses the SDK's local world by default; that world persists workflow runs on disk under `.eve/.workflow-data` and dispatches through the same Nitro-hosted workflow routes. On Vercel, the same workflow code runs against Vercel Workflow instead, which adds deployment-aware routing and dashboard run metadata.
25
25
 
26
- When a Vercel production deployment changes, a new delivery to an idle session hands the session's settled state to the exact deployment that accepted the request. This applies whether the delivery arrives through the session ID or through any channel continuation address the session has claimed. The session keeps its original ID and event stream, while the next model turn uses that deployment's current system-role instructions, model, and tools. Static user-role instructions stay pinned in the durable conversation history and are not seeded again. A successful handoff restarts the session's original configured [`sessionTimeoutMs`](../agent-config#runtime-limits) duration; disabled timeouts stay disabled. Failed or skipped handoffs keep the existing deadline.
26
+ When a Vercel production deployment changes, a new delivery to an idle session hands the session's settled state to the exact deployment that accepted the request. This applies whether the delivery arrives through the session ID or through any channel continuation address the session has claimed. The session keeps its original ID and event stream, while the next model turn uses that deployment's current system-role instructions, model, and tools. Static user-role instructions stay pinned in the durable conversation history and are not seeded again. As with any turn, the triggering delivery restarts the session's [`sessionTimeoutMs`](../agent-config#runtime-limits) idle window, whether or not the handoff succeeds; disabled timeouts stay disabled.
27
27
 
28
28
  Before taking ownership, the target deployment upgrades the checkpoint when an older eve release wrote it, then checks that no pending work remains. A deployment accepts checkpoints from eve 0.66.0 and later, so upgrading eve does not leave long-lived sessions, such as a Slack thread, on their old deployment. An older release cannot read a newer checkpoint: after a rollback, a session that already moved to the newer deployment stays there. Extension state saved before eve 0.69 moves to its mount when exactly one mount of that extension package defines that state. State that the target deployment no longer defines, such as a removed `defineState` key, is dropped, and the target deployment's runtime logs show a `dropping unknown context key during deserialization` warning. When two or more mounts of one extension package define that state, eve cannot choose an owner, and the session stays on its current deployment. Idle subagent sessions that eve 0.66–0.68 kept for later reuse are stopped after the handoff. Authored session state remains opaque to this validation.
29
29
 
30
- If the target deployment rejects the session, the previous owner keeps it and processes the triggering delivery. When the target deployment cannot read the checkpoint, its Vercel runtime logs show a `session handoff refused` warning with the checkpoint version and the reason. Owners on this release or later also write a `session handoff failed` warning to their own runtime logs, with the target deployment and the reason.
30
+ Messages that arrive while a handoff is in progress are accepted right away and are not lost. The new owner processes them after the triggering delivery, in the order they arrived, whichever run accepted them.
31
+
32
+ The previous owner forwards the messages it accepted as soon as the new owner has taken over the session. Until they arrive, the new owner holds back everything it receives itself, including replies to tool runs and subagents that its first turn started, and processes it afterwards. A cancel still stops the new owner's running turn as soon as it arrives. If either run ends before the handoff settles, the other stops waiting for it.
33
+
34
+ If the target deployment rejects the session, the previous owner keeps it and processes the triggering delivery, followed by any messages that arrived during the attempt. When the target deployment cannot read the checkpoint, its Vercel runtime logs show a `session handoff refused` warning with the checkpoint version and the reason. Owners on this release or later also write a `session handoff failed` warning to their own runtime logs, with the target deployment and the reason.
31
35
 
32
36
  eve does not move live work between deployments. A session with a running turn, pending human input or authorization, queued messages, or active tasks and subagents stays on its current deployment. After that work settles, a later delivery can move the idle session; the earlier deployment signal is not remembered.
33
37
 
@@ -35,7 +39,9 @@ eve does not move live work between deployments. A session with a running turn,
35
39
 
36
40
  Every workflow run records each step it executes, so a session that ran on one run for weeks would carry its whole history in that run. To bound this, eve moves a session to a fresh run on its current deployment after [compaction](./default-harness#compaction) rewrites the conversation history. This applies to automatic compaction during a turn and to a client or channel compaction request.
37
41
 
38
- The move happens once the turn settles and nothing else is waiting: no queued messages, running tasks or subagents, or pending human input. The new run receives the compacted history and settled state, then waits for the next message. If a single message reaches an otherwise idle session before the move completes, eve moves the session with that message, and the message's turn runs on the new run. If work is still pending, eve tries again after a later turn settles. The session keeps its ID, event stream, continuation addresses, and deadline; compaction never extends [`sessionTimeoutMs`](../agent-config#runtime-limits). If the new run cannot take ownership, the current run keeps the session, writes a `session handoff failed` warning to its runtime logs, and tries again later.
42
+ The move happens once the turn settles and nothing else is waiting: no queued messages, running tasks or subagents, or pending human input. The new run receives the compacted history and settled state, then waits for the next message. A message that is already waiting when the turn settles runs on the current run first, and the session moves at the next point where nothing is waiting. A message that arrives after the move starts is accepted right away and runs once, on the new run. If work is still pending, eve tries again after a later turn settles. The session keeps its ID, event stream, and continuation addresses, and its [`sessionTimeoutMs`](../agent-config#runtime-limits) idle window still counts from the turn that compacted. If the new run cannot take ownership, the current run keeps the session, writes a `session handoff failed` warning to its runtime logs, and tries again later.
43
+
44
+ Both kinds of handoff need a Workflow world that can move a session's addresses from one run to another without releasing them, and can keep a released address reserved for a while (the `hookForceClaim` and `hookRetention` capabilities). Vercel Workflow, the local world, and `@workflow/world-postgres` 5.0.4 and later provide both. On a world without them, sessions never move between runs: deployment and compaction handoffs are skipped, and a session's run history grows for as long as the session lives. The first skipped attempt writes a `session handoff unavailable` warning to the runtime logs.
39
45
 
40
46
  ### Stranded sessions
41
47
 
@@ -60,6 +66,7 @@ See [How a stranded session ends](#how-a-stranded-session-ends) for the details.
60
66
  For a root session, what happens next depends on how the message is addressed:
61
67
 
62
68
  - **A channel address**, such as a Slack thread or any `from(address).send(...)`, resets the stranded session and starts a fresh session at the same address. The message becomes the fresh session's first turn. Concurrent messages to the address produce one fresh session. The fresh session names the session it replaced, whose recorded conversation your instructions can read; see [What a replacement session receives](#what-a-replacement-session-receives). A delivery that carries input responses, with or without a message, throws `SessionStrandedError` and leaves the stranded session in place, because a fresh session has no request for them to answer. See [custom channels](../channels/custom#channel-operations-and-session-handles).
69
+ - **`create()` on a channel address** throws `SessionStrandedError` and leaves the stranded session in place. Call `reset()` on the address, then `create()` again; see [Start from prior history](../channels/eve#start-from-prior-history).
63
70
  - **A session ID** is never reset implicitly. The eve HTTP channel returns `409` with `code: "session_stranded"` and the `eveVersion` that built the session when it recorded one. `Session.send()` and `Session.respond()` throw `SessionStrandedError` from `eve/channels`, and the TypeScript client's `ClientSession.send()` and `respond()` throw `ClientSessionStrandedError` from `eve/client`. Nothing is delivered, and the stranded run keeps its status and event log. See [Start and continue a session](../channels/eve#start-and-continue-a-session).
64
71
  - **Following a stream** of a stranded session returns the same `409 session_stranded`. The TypeScript client stops reconnecting and reports `ClientSessionStrandedError`; the TUI explains how to recover. An existing connection learns this when it reconnects. Messages accepted before the upgrade are not redelivered.
65
72
  - **Reading recorded history** still works. A bounded read (`stream({ follow: false })`, or `follow=false` on the stream route) returns the events recorded so far without inspecting or changing the session, so you can read a stranded conversation before deciding to reset it.
@@ -115,7 +122,7 @@ export default defineDynamic({
115
122
 
116
123
  The resolver runs before the replacement's first model call, and its result enters the replacement's durable history once, ahead of the new message. In a session that replaced no other, `ctx.session.predecessor` is undefined and the instruction contributes nothing.
117
124
 
118
- `transcriptReducer()` keeps user message text and completed assistant text, oldest first, as `{ role, text }` messages. Reasoning, tool calls and results, attachments, approvals, and output from a turn that never completed are left out. A clear in the stranded session starts the transcript over, so text the user cleared is never returned. `maxMessages` keeps only the newest messages.
125
+ `transcriptReducer()` keeps history added with `history`, user message text, and completed assistant text, oldest first, as `{ role, text }` messages. Reasoning, tool calls and results, attachments, approvals, and output from a turn that never completed are left out. A clear in the stranded session starts the transcript over, so text the user cleared is never returned. `maxMessages` keeps only the newest messages.
119
126
 
120
127
  The read and the prompt are yours to bound:
121
128
 
@@ -131,7 +138,7 @@ The transcript is context only: tools, approvals, files, sandbox contents, autho
131
138
 
132
139
  A stranded root session ends on its next channel message, a reset, or its [session timeout](../agent-config#runtime-limits), whichever comes first. It executes nothing while it waits. When the local world or Postgres recovers active runs at startup, eve acknowledges the root session's delivery without replaying it and logs a warning naming the run and both eve versions. The run keeps its `pending` or `running` status and its addresses. A later restart skips it the same way.
133
140
 
134
- The timeout is the session's own durable timer, armed for the deadline `sessionTimeoutMs` sets from the session's creation or latest deployment handoff (30 days by default; compaction handoffs keep the deadline). The timer is a separate workflow run that keeps working after the upgrade, and when it fires on a stranded session it ends the session the same way a reset does, without starting a fresh one. The next message to the address then starts an ordinary session. A session with `sessionTimeoutMs: false` has no timer, so a stranded one stays until a message or a reset, just as a healthy one would stay open.
141
+ The timeout is the session's own durable timer, which its last owner armed for the end of an idle window (30 days by default). An owner re-arms the timer only when the timer fires before the window ends, so a stranded session ends at most `sessionTimeoutMs` after its last turn settled, and possibly sooner. The timer is a separate workflow run that keeps working after the upgrade, and when it fires on a stranded session it ends the session the same way a reset does, without starting a fresh one. The next message to the address then starts an ordinary session. A session with `sessionTimeoutMs: false` has no timer, so a stranded one stays until a message or a reset, just as a healthy one would stay open.
135
142
 
136
143
  Ending a root session does not find or cancel its subagent sessions and workflow tool runs. Each one is cancelled when a delivery for it reaches the current deployment, with the cancellation reason `Run retired: its eve deployment is no longer available`, and eve logs a `Retired stranded descendant run` warning. A run that never receives a delivery, such as one parked in a dormant `eve dev` generation, can stay active; cancel it in your Workflow world if you need to. Cancellation does not run authored cleanup or lifecycle hooks, undo side effects, or stop independent external jobs, and results and approvals do not carry over to a replacement session.
137
144
 
@@ -157,7 +164,7 @@ fresh session.
157
164
 
158
165
  Keep the original deployment available until these sessions end. Their old
159
166
  drivers remain waiting to keep the original streams open; the current runtime
160
- owns subsequent turns. Import restarts the original configured session timeout;
167
+ owns subsequent turns. Import restarts the session's idle window;
161
168
  disabled timeouts stay disabled. A driver
162
169
  that has not reached its next turn dispatch has not migrated yet.
163
170
 
@@ -253,11 +260,20 @@ the returned handle. That identity lookup does not block workflow resumption.
253
260
  ID-addressed delivery already knows the session ID and needs no hook metadata
254
261
  lookup. An ambiguous delivery failure does not trigger session creation.
255
262
 
256
- While a handoff is in progress, the session's addresses are briefly unowned.
257
- The releasing owner leaves a short-lived marker for each address, so a
258
- delivery that lands in that interval retries until the successor claims the
259
- address instead of being reported as an inactive session. A channel therefore
260
- never starts a replacement session for an address that is mid-handoff.
263
+ Pass a [delivery idempotency key](../channels/custom#retried-deliveries) to prevent
264
+ retried input from interrupting generation or starting another turn. Each session
265
+ remembers its last 1,024 distinct keys across replay and owner handoffs. Keyed
266
+ cold-start sends wait for the winning session's ID; sends without a key can
267
+ return a candidate ID before ownership is settled.
268
+
269
+ A handoff never leaves the session's addresses without an owner. The previous
270
+ owner keeps every address until the new run takes all of them over, so a
271
+ delivery during a handoff is accepted immediately and is never reported as an
272
+ inactive session. An owner still running an earlier eve release gives up its
273
+ addresses briefly during a handoff and leaves a short-lived marker for each
274
+ one; a delivery that lands in that interval retries until the successor
275
+ claims the address. Either way, a channel never starts a replacement session
276
+ for an address that is changing owners.
261
277
 
262
278
  When a session is waiting, a delivery through its ID or any claimed channel address wakes it and starts the next turn. Message sends default to `turnPolicy: "steer"`. Before assistant output begins, the session owner signals pending model generation to stop, then applies the correction within the same turn and with the same turn ID. This includes model requests that perform provider-managed search before answering; interrupted search work may run again. Reasoning and search progress do not count as assistant output.
263
279
 
@@ -21,13 +21,13 @@ whichever session currently owns a platform conversation, such as a Slack thread
21
21
  That identity stays behind the channel boundary and is never accepted or returned
22
22
  by the eve HTTP session API. See [Custom channels](../channels/custom#channel-operations-and-session-handles).
23
23
 
24
- Sessions last 30 days by default; configure `limits.sessionTimeoutMs` in
25
- `agent.ts`, or set it to `false` to disable the deadline. A successful deployment
26
- handoff or legacy-session import restarts the original configured duration.
27
- Ordinary messages, compaction handoffs, and process restarts keep the existing deadline. At expiration, eve
28
- lets an active turn settle, emits `session.completed`, and releases the
29
- session's continuation addresses so the next qualifying channel message starts fresh. Stored
30
- session data is not deleted. See [Agent config](../agent-config#runtime-limits).
24
+ A session completes after 30 idle days by default; configure
25
+ `limits.sessionTimeoutMs` in `agent.ts`, or set it to `false` to disable the
26
+ timeout. The idle window restarts each time a turn settles, and a session never
27
+ expires while a turn is open. At expiration, eve emits `session.completed` and
28
+ releases the session's continuation addresses so the next qualifying channel
29
+ message starts fresh. Stored session data is not deleted. See
30
+ [Agent config](../agent-config#runtime-limits).
31
31
 
32
32
  React, Vue, and Svelte apps reach for [`useEveAgent()`](../guides/frontend/overview) instead of calling these routes by hand. Next.js and Nuxt apps can proxy them to the eve runtime from the same origin.
33
33
 
@@ -70,6 +70,7 @@ The stream is newline-delimited JSON (NDJSON), one event per line:
70
70
  | Event | Meaning |
71
71
  | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
72
  | `session.started` | A durable session was created; carries `trace` when the runtime is traced, and `predecessor` when the session [replaced a stranded one](./execution-model-and-durability#what-a-replacement-session-receives). |
73
+ | `history.imported` | A delivery added prior conversation with `history`; carries each message's `role`, `content`, and optional `id`. Precedes that delivery's `message.received` in the same turn. |
73
74
  | `turn.started` | A new turn began; carries the active `trace` when the runtime is traced. |
74
75
  | `message.received` | Input reached a turn; carries flattened text plus structured text/file parts. |
75
76
  | `step.started` | A model step began. |
@@ -343,7 +344,7 @@ Every stream event runs four steps, in this order:
343
344
  3. **Hooks**: authored [hooks](../guides/hooks) subscribed to the event fire.
344
345
  4. **Dynamic resolvers**: [dynamic](../guides/dynamic-capabilities) tool, skill, and instruction resolvers fire, and `ctx.channel.metadata` already holds the freshly projected metadata from step 2.
345
346
 
346
- The order is structural, not incidental. By the time a resolver or hook reads channel metadata, the channel has already updated its state and the projection is current.
347
+ The order is structural, not incidental. By the time a dynamic resolver reads `ctx.channel.metadata`, the channel has already updated its state and the projection is current.
347
348
 
348
349
  ## What to read next
349
350
 
@@ -8,7 +8,7 @@ A connection wires an agent into an external server you don't author, either an
8
8
 
9
9
  Connections live under `agent/connections/`. A static connection's runtime name comes from the filename, so `agent/connections/linear.ts` registers as `"linear"`. A dynamic connection file can instead resolve a caller-specific connection set at session or turn boundaries. The model never sees a connection's URL or credentials. It finds tools with the built-in [`eve__search`](/docs/concepts/built-in-tools#eve__search-eve__tool-and-eve__skill), where a query such as `linear__` searches one connection's tools, and calls them with `eve__tool`. Stream events, approval policies, and hooks name each call by its qualified name, `<connection>__<tool>` (e.g. `linear__list_issues`).
10
10
 
11
- A connection owns its name and every name that starts with `<name>__`, so `linear__list_issues` always means the `linear` connection's `list_issues` tool. The compiler rejects a tool, subagent, or connection whose name is a static connection's name or starts with its `<name>__` prefix. eve applies the same rule to dynamic connections, tools, and subagents when they resolve. Both errors name the two sides of the conflict; rename either one.
11
+ A connection owns its name and every name that starts with `<name>__`, so `linear__list_issues` always means the `linear` connection's `list_issues` tool. The compiler rejects a tool, subagent, or connection whose name is a static connection's name or starts with its `<name>__` prefix. eve applies the same rule to dynamic connections, tools, and subagents when they resolve, except that a dynamic connection with exactly a static connection's name replaces that static connection; see [Naming and conflicts](/docs/guides/dynamic-capabilities#naming-and-conflicts). Both errors name the two sides of the conflict; rename either one.
12
12
 
13
13
  ## MCP connections
14
14