eve 0.81.1 → 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 (285) hide show
  1. package/CHANGELOG.md +24 -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 +1 -1
  24. package/dist/src/compiler/extension-compatibility.d.ts +19 -18
  25. package/dist/src/compiler/extension-compatibility.js +1 -1
  26. package/dist/src/compiler/load-binding-namespace.d.ts +2 -0
  27. package/dist/src/compiler/load-binding-namespace.js +1 -1
  28. package/dist/src/compiler/manifest.js +1 -1
  29. package/dist/src/compiler/module-lifecycle.d.ts +1 -1
  30. package/dist/src/compiler/module-lifecycle.js +1 -1
  31. package/dist/src/compiler/module-map.js +1 -1
  32. package/dist/src/compiler/normalize-agent-config.js +1 -1
  33. package/dist/src/compiler/normalize-manifest.js +1 -1
  34. package/dist/src/compiler/project-sources.d.ts +6 -0
  35. package/dist/src/compiler/project-sources.js +1 -1
  36. package/dist/src/context/keys.d.ts +2 -0
  37. package/dist/src/context/keys.js +1 -1
  38. package/dist/src/evals/cli/eval-state-directory.d.ts +7 -0
  39. package/dist/src/evals/cli/eval-state-directory.js +1 -0
  40. package/dist/src/evals/cli/eval.js +2 -2
  41. package/dist/src/eve-channel/index.js +1 -1
  42. package/dist/src/eve-channel/support.d.ts +2 -0
  43. package/dist/src/eve-channel/support.js +1 -1
  44. package/dist/src/eve-channel/types.d.ts +7 -0
  45. package/dist/src/execution/agent-sessions/context.d.ts +2 -13
  46. package/dist/src/execution/agent-sessions/context.js +1 -1
  47. package/dist/src/execution/agent-sessions/runtime-overrides.d.ts +5 -0
  48. package/dist/src/execution/agent-sessions/runtime-overrides.js +1 -0
  49. package/dist/src/execution/agent-sessions/session.d.ts +2 -2
  50. package/dist/src/execution/agent-sessions/session.js +1 -1
  51. package/dist/src/execution/agent-sessions/steps.d.ts +2 -0
  52. package/dist/src/execution/agent-sessions/steps.js +1 -1
  53. package/dist/src/execution/coordination-dispatch-shared.d.ts +5 -5
  54. package/dist/src/execution/coordination-dispatch-step.d.ts +17 -2
  55. package/dist/src/execution/coordination-dispatch-step.js +1 -1
  56. package/dist/src/execution/create-session-step.d.ts +5 -2
  57. package/dist/src/execution/create-session-step.js +1 -1
  58. package/dist/src/execution/deliver-payloads.js +1 -1
  59. package/dist/src/execution/durable-session-store.d.ts +0 -1
  60. package/dist/src/execution/dynamic-connection-recovery.js +1 -1
  61. package/dist/src/execution/dynamic-workflow/tool.js +1 -1
  62. package/dist/src/execution/effective-agent-config.d.ts +8 -2
  63. package/dist/src/execution/effective-agent-config.js +1 -1
  64. package/dist/src/execution/install-workflow-deployment-id.d.ts +3 -0
  65. package/dist/src/execution/install-workflow-deployment-id.js +1 -0
  66. package/dist/src/execution/legacy-session/turn-workflow.js +1 -1
  67. package/dist/src/execution/node-step.d.ts +1 -0
  68. package/dist/src/execution/node-step.js +1 -1
  69. package/dist/src/execution/runtime-context.d.ts +2 -0
  70. package/dist/src/execution/runtime-context.js +1 -1
  71. package/dist/src/execution/session/active-turn.js +1 -1
  72. package/dist/src/execution/session/admission.js +1 -1
  73. package/dist/src/execution/session/checkpoint-migrations.js +1 -1
  74. package/dist/src/execution/session/entry-input.d.ts +1 -3
  75. package/dist/src/execution/session/entry.js +1 -1
  76. package/dist/src/execution/session/finalization.js +1 -1
  77. package/dist/src/execution/session/handoff.d.ts +6 -9
  78. package/dist/src/execution/session/handoff.js +1 -1
  79. package/dist/src/execution/session/input-queue.d.ts +5 -1
  80. package/dist/src/execution/session/input-queue.js +1 -1
  81. package/dist/src/execution/session/pending-history.d.ts +5 -0
  82. package/dist/src/execution/session/pending-history.js +1 -0
  83. package/dist/src/execution/session/pending-turn-state.js +1 -1
  84. package/dist/src/execution/session/program.d.ts +1 -1
  85. package/dist/src/execution/session/program.js +1 -1
  86. package/dist/src/execution/session/timeout-control.d.ts +18 -4
  87. package/dist/src/execution/session/timeout-control.js +1 -1
  88. package/dist/src/execution/session/timeout.d.ts +1 -2
  89. package/dist/src/execution/session/timeout.js +1 -1
  90. package/dist/src/execution/session/turn-event-handler.js +1 -1
  91. package/dist/src/execution/session/turn-step-result.js +1 -1
  92. package/dist/src/execution/session/turn-step.js +1 -1
  93. package/dist/src/execution/session/turn-waiting-step.js +1 -1
  94. package/dist/src/execution/session/turn.js +1 -1
  95. package/dist/src/execution/session-inbox/protocol.js +1 -1
  96. package/dist/src/execution/session.d.ts +0 -12
  97. package/dist/src/execution/session.js +2 -2
  98. package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
  99. package/dist/src/execution/tasks/model-step.js +1 -1
  100. package/dist/src/execution/tasks/start.d.ts +4 -4
  101. package/dist/src/execution/tasks/start.js +1 -1
  102. package/dist/src/execution/tasks/steps.js +1 -1
  103. package/dist/src/execution/terminate-child-sessions-step.js +1 -1
  104. package/dist/src/execution/tools/agent-router-workflow.js +1 -1
  105. package/dist/src/execution/tools/workflow/body.d.ts +2 -2
  106. package/dist/src/execution/workflow-runtime.d.ts +5 -1
  107. package/dist/src/execution/workflow-runtime.js +1 -1
  108. package/dist/src/harness/hitl/approvals.js +1 -1
  109. package/dist/src/harness/hitl/budget-request.d.ts +12 -6
  110. package/dist/src/harness/hitl/budget-request.js +1 -1
  111. package/dist/src/harness/hitl/budget.d.ts +7 -1
  112. package/dist/src/harness/hitl/budget.js +1 -1
  113. package/dist/src/harness/hitl/coordinator.js +1 -1
  114. package/dist/src/harness/hitl/delivery.d.ts +1 -1
  115. package/dist/src/harness/hitl/delivery.js +1 -1
  116. package/dist/src/harness/hitl/index.js +1 -1
  117. package/dist/src/harness/hitl/intake.d.ts +2 -2
  118. package/dist/src/harness/hitl/intake.js +1 -1
  119. package/dist/src/harness/messages.d.ts +5 -0
  120. package/dist/src/harness/messages.js +1 -1
  121. package/dist/src/harness/model-call/call.d.ts +2 -2
  122. package/dist/src/harness/model-call/request.d.ts +2 -2
  123. package/dist/src/harness/session-machine/transitions.d.ts +3 -1
  124. package/dist/src/harness/session-machine/transitions.js +1 -1
  125. package/dist/src/harness/step/context.d.ts +3 -0
  126. package/dist/src/harness/step/context.js +1 -1
  127. package/dist/src/harness/step/intake.d.ts +5 -2
  128. package/dist/src/harness/step/intake.js +1 -1
  129. package/dist/src/harness/tool-loop.js +1 -1
  130. package/dist/src/harness/turn-tag-state.d.ts +7 -26
  131. package/dist/src/harness/turn-tag-state.js +1 -1
  132. package/dist/src/harness/types.d.ts +10 -25
  133. package/dist/src/internal/application/build-profile.d.ts +3 -0
  134. package/dist/src/internal/application/build-profile.js +1 -1
  135. package/dist/src/internal/application/compiled-artifacts.js +2 -2
  136. package/dist/src/internal/application/dev-environment.d.ts +13 -0
  137. package/dist/src/internal/application/dev-environment.js +1 -1
  138. package/dist/src/internal/application/stamped-package-version.js +1 -1
  139. package/dist/src/internal/authored-definition/core.js +1 -1
  140. package/dist/src/internal/authored-module-build-options.d.ts +7 -0
  141. package/dist/src/internal/authored-module-build-options.js +1 -0
  142. package/dist/src/internal/authored-module-graph.d.ts +13 -0
  143. package/dist/src/internal/authored-module-graph.js +1 -0
  144. package/dist/src/internal/authored-module-loader.d.ts +22 -1
  145. package/dist/src/internal/authored-module-loader.js +1 -3
  146. package/dist/src/internal/authored-module.d.ts +7 -0
  147. package/dist/src/internal/authored-module.js +1 -1
  148. package/dist/src/internal/authored-package-boundary.js +1 -1
  149. package/dist/src/internal/bundler/nitro-rolldown.d.ts +5 -1
  150. package/dist/src/internal/bundler/nitro-rolldown.js +1 -1
  151. package/dist/src/internal/development-programmatic-source-loader.d.ts +1 -0
  152. package/dist/src/internal/development-programmatic-source-loader.js +1 -1
  153. package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
  154. package/dist/src/internal/nitro/dev-runtime-generation-metadata.js +1 -1
  155. package/dist/src/internal/nitro/host/build-application.js +1 -1
  156. package/dist/src/internal/nitro/host/build-deployment-id.d.ts +8 -0
  157. package/dist/src/internal/nitro/host/build-deployment-id.js +1 -0
  158. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
  159. package/dist/src/internal/nitro/host/dev-server-state.js +1 -1
  160. package/dist/src/internal/nitro/host/skill-server-assets.js +1 -1
  161. package/dist/src/internal/nitro/host/stage-build-deployment-id.d.ts +2 -0
  162. package/dist/src/internal/nitro/host/stage-build-deployment-id.js +1 -0
  163. package/dist/src/internal/nitro/host/start-production-server.d.ts +1 -0
  164. package/dist/src/internal/nitro/host/start-production-server.js +3 -3
  165. package/dist/src/internal/nitro/routes/schedule-task.d.ts +3 -0
  166. package/dist/src/internal/nitro/routes/schedule-task.js +1 -1
  167. package/dist/src/internal/programmatic-source-loader.d.ts +1 -0
  168. package/dist/src/internal/programmatic-source-loader.js +1 -1
  169. package/dist/src/internal/schedules/disabled.d.ts +2 -0
  170. package/dist/src/internal/schedules/disabled.js +1 -0
  171. package/dist/src/internal/workflow/development-world-server.js +1 -1
  172. package/dist/src/internal/workflow/local-world-data-directory.d.ts +2 -1
  173. package/dist/src/internal/workflow/local-world-data-directory.js +1 -1
  174. package/dist/src/internal/workflow-bundle/builder.js +1 -1
  175. package/dist/src/protocol/message.d.ts +27 -1
  176. package/dist/src/protocol/message.js +2 -2
  177. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +13 -0
  178. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  179. package/dist/src/public/channels/chat-sdk/fetch-message.d.ts +10 -0
  180. package/dist/src/public/channels/chat-sdk/fetch-message.js +1 -0
  181. package/dist/src/public/channels/chat-sdk/index.d.ts +1 -1
  182. package/dist/src/public/channels/chat-sdk/reply-context.d.ts +15 -0
  183. package/dist/src/public/channels/chat-sdk/reply-context.js +2 -0
  184. package/dist/src/public/channels/discord/discordChannel.d.ts +3 -0
  185. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  186. package/dist/src/public/channels/github/dispatch.js +1 -1
  187. package/dist/src/public/channels/github/githubChannel.d.ts +3 -0
  188. package/dist/src/public/channels/imessage-tapbacks.d.ts +74 -0
  189. package/dist/src/public/channels/imessage-tapbacks.js +1 -0
  190. package/dist/src/public/channels/index.d.ts +2 -1
  191. package/dist/src/public/channels/index.js +1 -1
  192. package/dist/src/public/channels/linear/linearChannel.d.ts +3 -0
  193. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  194. package/dist/src/public/channels/linq/inboundContent.d.ts +7 -0
  195. package/dist/src/public/channels/linq/inboundContent.js +1 -1
  196. package/dist/src/public/channels/linq/index.d.ts +1 -0
  197. package/dist/src/public/channels/linq/index.js +1 -1
  198. package/dist/src/public/channels/linq/linqChannel.d.ts +17 -1
  199. package/dist/src/public/channels/linq/linqChannel.js +1 -1
  200. package/dist/src/public/channels/photon/inboundContent.d.ts +7 -0
  201. package/dist/src/public/channels/photon/inboundContent.js +2 -1
  202. package/dist/src/public/channels/photon/index.d.ts +1 -0
  203. package/dist/src/public/channels/photon/index.js +1 -1
  204. package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +17 -1
  205. package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
  206. package/dist/src/public/channels/slack/api.d.ts +6 -0
  207. package/dist/src/public/channels/slack/api.js +1 -1
  208. package/dist/src/public/channels/slack/model-context.d.ts +7 -0
  209. package/dist/src/public/channels/slack/model-context.js +1 -1
  210. package/dist/src/public/channels/slack/reply-delivery.d.ts +5 -0
  211. package/dist/src/public/channels/slack/reply-delivery.js +1 -1
  212. package/dist/src/public/channels/slack/slackChannel.d.ts +17 -3
  213. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  214. package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -0
  215. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  216. package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -0
  217. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  218. package/dist/src/public/definitions/agent.d.ts +1 -1
  219. package/dist/src/public/definitions/channel.d.ts +2 -2
  220. package/dist/src/public/definitions/channel.js +1 -1
  221. package/dist/src/public/definitions/hook.d.ts +1 -0
  222. package/dist/src/public/index.d.ts +1 -1
  223. package/dist/src/public/tools/index.d.ts +1 -1
  224. package/dist/src/runtime/agent/resolve-model.d.ts +4 -0
  225. package/dist/src/runtime/agent/resolve-model.js +1 -1
  226. package/dist/src/runtime/resolve-agent.js +1 -1
  227. package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
  228. package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -1
  229. package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
  230. package/dist/src/runtime/types.d.ts +3 -2
  231. package/dist/src/setup/scaffold/create/project.js +1 -1
  232. package/dist/src/shared/agent-definition.d.ts +31 -42
  233. package/dist/src/shared/session-history.d.ts +22 -0
  234. package/dist/src/shared/session-history.js +1 -0
  235. package/dist/src/subagents/tool.d.ts +1 -3
  236. package/dist/src/subagents/tool.js +1 -1
  237. package/dist/src/svelte/index.js +1 -1
  238. package/dist/src/svelte/use-eve-agent.js +1 -1
  239. package/dist/src/tools/provided/ask-question-workflow.js +1 -1
  240. package/dist/src/tools/provided/sleep-workflow.js +1 -1
  241. package/dist/src/tools/workflow-definition.d.ts +14 -1
  242. package/dist/src/tracing/eve/conversation-context.d.ts +4 -1
  243. package/dist/src/tracing/eve/conversation-context.js +1 -1
  244. package/dist/src/tracing/eve/invoke-tool-span.js +1 -1
  245. package/dist/src/tracing/eve/operation-input.d.ts +0 -5
  246. package/dist/src/tracing/eve/operation-input.js +1 -1
  247. package/dist/src/tracing/eve/otel-registration.js +1 -1
  248. package/dist/src/tracing/eve/session.d.ts +3 -2
  249. package/dist/src/tracing/eve/session.js +1 -1
  250. package/dist/src/vue/index.js +1 -1
  251. package/dist/src/vue/use-eve-agent.js +1 -1
  252. package/docs/agent-config.md +86 -65
  253. package/docs/channels/chat-sdk.mdx +6 -1
  254. package/docs/channels/custom.mdx +38 -6
  255. package/docs/channels/eve.mdx +46 -0
  256. package/docs/channels/linq.mdx +51 -0
  257. package/docs/channels/photon.mdx +64 -0
  258. package/docs/channels/slack.mdx +4 -2
  259. package/docs/concepts/built-in-tools.md +1 -1
  260. package/docs/concepts/execution-model-and-durability.mdx +12 -5
  261. package/docs/concepts/sessions-runs-and-streaming.md +9 -8
  262. package/docs/connections/overview.mdx +1 -1
  263. package/docs/experimental/dynamic-schedules.mdx +186 -0
  264. package/docs/experimental/meta.json +4 -0
  265. package/docs/guides/client/streaming.mdx +1 -0
  266. package/docs/guides/deployment/self-hosting.md +3 -0
  267. package/docs/guides/hooks.md +21 -1
  268. package/docs/memory/overview.mdx +2 -1
  269. package/docs/patterns/dynamic-scheduling.md +3 -1
  270. package/docs/reference/cli.md +14 -7
  271. package/docs/schedules.mdx +2 -0
  272. package/docs/subagents/index.mdx +2 -0
  273. package/docs/tools/overview.mdx +2 -1
  274. package/docs/tools/tasks-upgrade.md +12 -7
  275. package/docs/tools/tasks.md +10 -6
  276. package/docs/tools/workflows.mdx +36 -5
  277. package/docs/tutorial/connect-a-warehouse.mdx +1 -1
  278. package/docs/tutorial/run-analysis.mdx +1 -1
  279. package/package.json +1 -1
  280. package/dist/src/execution/run-session-limits.d.ts +0 -12
  281. package/dist/src/execution/run-session-limits.js +0 -1
  282. package/dist/src/harness/session-usage-limits.d.ts +0 -49
  283. package/dist/src/harness/session-usage-limits.js +0 -1
  284. package/dist/src/subagents/token-budget.d.ts +0 -15
  285. package/dist/src/subagents/token-budget.js +0 -1
@@ -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,6 +32,10 @@ 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
41
  its attachment id with the project's credentials. The webhook omits the id
@@ -57,6 +61,66 @@ export default photonIMessageChannel({
57
61
  Set `route` to override the webhook path or `webhookVerifier` to use a different
58
62
  trusted-forwarder verifier.
59
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
+
60
124
  ## Other hosts
61
125
 
62
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";
@@ -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,7 +23,7 @@ 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
 
@@ -39,7 +39,7 @@ eve does not move live work between deployments. A session with a running turn,
39
39
 
40
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.
41
41
 
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, 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
43
 
44
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.
45
45
 
@@ -66,6 +66,7 @@ See [How a stranded session ends](#how-a-stranded-session-ends) for the details.
66
66
  For a root session, what happens next depends on how the message is addressed:
67
67
 
68
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).
69
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).
70
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.
71
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.
@@ -121,7 +122,7 @@ export default defineDynamic({
121
122
 
122
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.
123
124
 
124
- `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.
125
126
 
126
127
  The read and the prompt are yours to bound:
127
128
 
@@ -137,7 +138,7 @@ The transcript is context only: tools, approvals, files, sandbox contents, autho
137
138
 
138
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.
139
140
 
140
- 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.
141
142
 
142
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.
143
144
 
@@ -163,7 +164,7 @@ fresh session.
163
164
 
164
165
  Keep the original deployment available until these sessions end. Their old
165
166
  drivers remain waiting to keep the original streams open; the current runtime
166
- owns subsequent turns. Import restarts the original configured session timeout;
167
+ owns subsequent turns. Import restarts the session's idle window;
167
168
  disabled timeouts stay disabled. A driver
168
169
  that has not reached its next turn dispatch has not migrated yet.
169
170
 
@@ -259,6 +260,12 @@ the returned handle. That identity lookup does not block workflow resumption.
259
260
  ID-addressed delivery already knows the session ID and needs no hook metadata
260
261
  lookup. An ambiguous delivery failure does not trigger session creation.
261
262
 
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
+
262
269
  A handoff never leaves the session's addresses without an owner. The previous
263
270
  owner keeps every address until the new run takes all of them over, so a
264
271
  delivery during a handoff is accepted immediately and is never reported as an
@@ -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
 
@@ -0,0 +1,186 @@
1
+ ---
2
+ title: "Dynamic Schedules"
3
+ description: "Create and manage creator-owned recurring or one-time schedules at runtime."
4
+ ---
5
+
6
+ <Callout type="info" title="Feature flag required">
7
+ Dynamic schedules on Vercel are gated by an organization-level feature flag. Contact your Vercel
8
+ representative to have the feature enabled for your organization.
9
+ </Callout>
10
+
11
+ Use `defineDynamicSchedules` when users or agents need to create recurring or one-time schedules at runtime. Export the definition from `agent/schedules/`; eve derives its identity from the file path. The definition configures management tools and an occurrence callback. It does not create a schedule or set its timing.
12
+
13
+ This API is experimental and may change. Import it from `eve/experimental/schedules`. For fixed, code-authored cron jobs, use [`defineSchedule`](../schedules) instead. For a self-hosted alternative with application-owned storage, see [Dynamic scheduling](../patterns/dynamic-scheduling).
14
+
15
+ ## Define scheduled work
16
+
17
+ The model supplies schema-defined payload. eve captures the authenticated creator separately and resolves current execution auth whenever an occurrence fires.
18
+
19
+ ```ts title="agent/schedules/requests.ts"
20
+ import { byPrincipal, defineDynamicSchedules } from "eve/experimental/schedules";
21
+ import { z } from "zod";
22
+ import slack from "../channels/slack";
23
+ import { resolveCurrentUser } from "../lib/identity";
24
+ import { resolveCreatorDm, resolveAuthorizedTeamChannel } from "../lib/schedule-policy";
25
+
26
+ export default defineDynamicSchedules({
27
+ description: "Schedule Slack tasks for the creator's DM or authorized team channel.",
28
+ inputSchema: z
29
+ .object({
30
+ task: z.string().min(1).max(2000),
31
+ destination: z.enum(["my-dm", "team-channel"]),
32
+ })
33
+ .strict(),
34
+ scope: byPrincipal,
35
+ auth: ({ principal }) => resolveCurrentUser(principal),
36
+ async run({ payload, occurrence, to, auth }) {
37
+ const target =
38
+ payload.destination === "my-dm"
39
+ ? await resolveCreatorDm(auth)
40
+ : await resolveAuthorizedTeamChannel(auth);
41
+ await to(slack, target).send(
42
+ `Perform this task now.\nSchedule: ${occurrence.displayName}\nScheduled for: ${occurrence.scheduledAt}\nTask: ${payload.task}`,
43
+ );
44
+ },
45
+ });
46
+ ```
47
+
48
+ The identity and destination helpers are application code. `inputSchema`, `auth`, and `run` are required. `provider` is optional and defaults to `vercelScheduleProvider()`. Pass a provider explicitly to use another backend or configure the Vercel endpoint.
49
+
50
+ `scope` defaults to `byPrincipal`, isolating management by authenticated principal type, authenticator, issuer, and ID, plus application and path-derived definition identity. A custom scope resolver must authorize each operation before returning a namespace; return `null` to deny.
51
+
52
+ ## Revalidate creator identity
53
+
54
+ `auth({ principal, payload, occurrence })` resolves the stored creator reference to current execution auth. Look up current account status and permissions rather than replaying credentials or roles captured at creation. Return `null` to deny; thrown errors remain eligible for provider retries.
55
+
56
+ The returned principal type, authenticator, issuer, ID, and subject must match the creator reference. eve does not fall back to app identity. Manual invocation does not replace the creator with the invoker. Tool and connection authorization still applies during execution.
57
+
58
+ ## Prepare payloads and destinations
59
+
60
+ The generated creation and update tools validate their `payload` field with your `inputSchema`. Without `preparePayload`, eve validates payloads before persistence and again before dispatch. The complete provider envelope is limited to 64 KB; store JSON data and stable references, not credentials or sensitive recipient details.
61
+
62
+ Optional `preparePayload(input, context)` validates application policy, resolves caller context, and returns the stored shape. It runs on creation and payload replacement, after input validation and scope authorization, before persistence. For model calls, eve prepares before asking for approval and provides the prepared payload to `approval.create` or `approval.update`. It prepares again before writing and rejects the call if the prepared result differs from the approved snapshot. Preparation errors reach the model before a person is asked. Authenticated code calling the client prepares once before writing, without model approval.
63
+
64
+ `auth`, `run`, and occurrence events infer the prepared return type. Declare `preparePayload` before these callbacks or give it an explicit return type for TypeScript contextual inference. Without preparation, payload type comes from the schema.
65
+
66
+ The input schema does not describe prepared output. eve bounds and JSON-validates the persisted envelope but does not structurally revalidate prepared application data against the input schema at execution. Authored code owns that persisted contract; validate it explicitly if needed. Preparation may repeat on write retries, so avoid irreversible effects. Reads, timing-only updates, state changes, and occurrences never rerun it.
67
+
68
+ ```ts
69
+ preparePayload(input, context) {
70
+ const target = resolveDestination(input.destination, context);
71
+ return { task: input.task, target };
72
+ },
73
+ ```
74
+
75
+ `resolveDestination` is application code. For channel delivery, it can validate trusted caller context and capture channel/workspace references without exposing those fields in the model-facing schema. Reject callers that lack the required channel context, including delegated work when appropriate. Payload replacement recaptures the destination and creator from the updating caller.
76
+
77
+ Destination IDs are optional. Conditional logic in `run` can derive a DM from creator identity, select an authorized configured channel, or look up a destination using another payload value. A schema may expose explicit IDs when the model can obtain them, but valid IDs do not prove access. Management scope does not authorize sending to every destination. Validate model-selected destinations against the creator's current permissions.
78
+
79
+ The original creation conversation is not captured automatically. To support “reply in the exact thread where I created this,” persist an authorized thread reference through `preparePayload` or application code. Creator identity alone cannot recover that thread. Runtime lookups can follow changed configuration; persist a destination reference when the destination must stay fixed.
80
+
81
+ ## Expose management tools
82
+
83
+ Each definition contributes tools for `create`, `get`, `list`, `update`, `enable`, `disable`, `invoke`, and `delete`. Each has its own object-rooted schema, approval policy, and readable action label. For example, `agent/schedules/billing/requests.ts` generates `schedule__billing-requests__create`, `schedule__billing-requests__update`, and the other operation names. There is no combined manage tool or operation field.
84
+
85
+ Creation takes `{ name, expression, payload }`. Updates take `{ name, expression?, payload? }` and require at least an expression or a complete replacement payload. Listing takes optional `{ cursor, limit }`, with limits from 1 through 100. The remaining tools take `{ name }` using the unique identifier from receipts or reads.
86
+
87
+ Set `tool: true` (the default) for direct tools, `tool: "deferred"` for catalog-discovered tools, or `tool: false` for code-only scheduling. Deferred operations keep the same names, schemas, approval policies, and authorization checks; the model finds them with `eve__search` and calls them with `eve__tool`. See [Dynamic capabilities](../guides/dynamic-capabilities#dynamic-tools).
88
+
89
+ Reads need no approval by default; mutations require user approval. Configure top-level `approval`, keyed by operation, with ordinary request/response policies. Separate tool identities keep `once()` grants independent. Create and payload-replacement update policies receive a typed `payload` containing prepared data; `toolInput.payload` also contains prepared rather than raw data. Timing-only update policies receive `payload: undefined`.
90
+
91
+ ```ts
92
+ approval: {
93
+ create: ({ payload }) => payload.target.audience === "private" ? "not-applicable" : "user-approval",
94
+ update: ({ payload }) => payload?.target.audience === "private" ? "not-applicable" : "user-approval",
95
+ delete: always(),
96
+ },
97
+ ```
98
+
99
+ Import `always` from `eve/tools/approval`; `target.audience` above is an application-owned prepared field. Combining `tool: false` with `approval` is rejected by TypeScript and runtime validation: these policies apply only to model calls, not code using `schedules(definition)`. Approvals do not bypass scope authorization or configure tools a future agent session uses.
100
+
101
+ ## Use the authenticated client
102
+
103
+ ```ts title="agent/tools/list-requests.ts"
104
+ import { schedules } from "eve/experimental/schedules";
105
+ import { defineTool } from "eve/tools";
106
+ import { z } from "zod";
107
+ import requests from "../schedules/requests";
108
+
109
+ export default defineTool({
110
+ description: "List the caller's scheduled requests.",
111
+ inputSchema: z.object({}),
112
+ async execute() {
113
+ return await (await schedules(requests)).list({ limit: 20 });
114
+ },
115
+ });
116
+ ```
117
+
118
+ Call the client inside an active eve context, not at module scope. Client creation accepts `{ name, expression, payload }`. The supplied `name` is a display label and may repeat; it must start with an ASCII letter or digit and contain only letters, digits, `.`, `_`, or `-`, up to 256 characters. eve truncates labels longer than 218 characters and appends `--` plus a UUID to allocate a unique management name.
119
+
120
+ Receipts and reads include `name` (use this exact value for management) and `displayName`. Callbacks and events keep the unique identifier in `occurrence.name` and expose the readable label as `occurrence.displayName`. Approval applies to the supplied label, timing, and payload before the unique identifier is allocated.
121
+
122
+ Both generated tools and authored clients check scope authorization on every operation, including during scheduled turns and local delegated work. Scheduled sessions remain unattended: mutation tools that require human approval cannot wait for it. Authorize unattended operations explicitly with `approval` policies, or use an authenticated client in authored code; neither bypasses scope checks.
123
+
124
+ Authored tools, hooks, and channel events can read `ctx.session.schedule`: `undefined` for ordinary work, or `{ definition, instance?, occurrenceId? }` for scheduled work. Static schedules that run as a user are included; this is framework provenance, not an auth attribute. Custom scope policies can inspect `context.session.schedule` to deny or restrict scheduled callers. A new ordinary inbound message clears scheduled-turn context; approval/input responses preserve it.
125
+
126
+ ## Set timing
127
+
128
+ For “in one minute,” use `expression: { type: "delay", minutes: 1 }`. eve resolves the time when the approved write executes and rounds up to minute precision, adding up to 59 seconds before provider/execution latency. Delays accept whole minutes from 1 through 525,600. Approval time is not part of the delay. A separate client retry resolves a new time; this is not a mutation-idempotency guarantee.
129
+
130
+ Use `single` with a `YYYY-MM-DDTHH:mm` local datetime and IANA timezone for absolute times, or `cron` with a five-field expression and timezone for recurring work. Omitted timezones mean UTC. Relative delays do not need a timezone.
131
+
132
+ ```ts
133
+ const client = await schedules(requests);
134
+ const created = await client.create({
135
+ name: "daily-summary",
136
+ expression: { type: "cron", cron: "0 9 * * 1-5", timezone: "America/New_York" },
137
+ payload: { task: "Summarize open issues.", destination: "my-dm" },
138
+ });
139
+
140
+ await client.update(created.name, {
141
+ expression: { type: "cron", cron: "0 10 * * 1-5", timezone: "America/New_York" },
142
+ });
143
+ ```
144
+
145
+ Import `schedules` from `eve/experimental/schedules` and `requests` from your definition module. Run this code in an active eve context.
146
+
147
+ ## Update and control schedules
148
+
149
+ `get` and `list` return identity, timing, state, and timestamps, not payload or creator data. New schedules are active; creation has no state option. Use `disable` to pause future scheduling.
150
+
151
+ `client.update(name, patch)` changes timing, replaces the complete payload, or does both. Timing-only updates preserve the stored payload and creator and do not call `preparePayload`. Payload replacement validates the complete new input, reruns preparation, and captures the updating caller as the new creator. It is not a partial merge. In a shared scope, authorizing payload replacement therefore also authorizes a change of execution identity.
152
+
153
+ Updates preserve the management name, schedule ID, state, and dispatch target. They cannot rename a schedule or change its state; use `enable` or `disable` for state changes. Updating an inactive or completed schedule does not activate it.
154
+
155
+ Updates, enable, disable, and delete affect future scheduling, not emitted callbacks or running sessions. Use delete-and-create only when you need a separate schedule identity; replacement is not atomic.
156
+
157
+ `invoke(name)` requests an additional occurrence as the stored creator. Inactive schedules may be invoked; completed schedules cannot. Manual invocation does not consume a pending one-time fire. Enqueue acceptance does not prove callback or agent success.
158
+
159
+ ## Handle occurrences and retries
160
+
161
+ `run` receives `payload`, `occurrence`, `auth`, `to`, and `waitUntil`. It may start zero or several sessions or perform application work directly. `to(channel, target).send(message)` is bound to the resolved creator; no auth argument is needed or accepted. It starts fresh, unattended sessions through an authored channel, even when the target names an existing thread. It does not steer an existing conversation.
162
+
163
+ eve awaits the callback and its registered `waitUntil` promises before reporting `occurrence.dispatched`. That event includes `sessionIds`; it means dispatch succeeded, not that the agent completed its task or delivered a successful result. Channels own output delivery and session-level failure reporting. Unattended sessions cannot wait for approvals or questions; configure tools accordingly.
164
+
165
+ Provider retries may repeat the callback and start new sessions. This is an at-least-once callback contract, not exactly-once side effects. Use `occurrence.executionId` together with the schedule/resource identity to deduplicate application effects where the destination supports it. An observer failure can also cause callback retry. `occurrence.failed` reports permanent provider failures or exhaustion of the provider retry budget.
166
+
167
+ ## Choose a provider and test locally
168
+
169
+ The default `vercelScheduleProvider()` supports these environments:
170
+
171
+ | Environment | Storage and delivery |
172
+ | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
173
+ | Vercel production | Requires feature-flag enablement for your organization. Hosted Vercel Schedules delivers occurrences through Vercel Queues. |
174
+ | `eve dev` without local Vercel Schedules | Process-local storage, with no background clock. Call `invoke` through the client to dispatch an occurrence. |
175
+ | `vc dev` with local Schedules and Queues support | Local Vercel Schedules stores timing, and eve polls local Queues to deliver due occurrences automatically. |
176
+ | Vercel preview or self-hosted production | The default provider rejects operations. Use a different provider or the [application-managed scheduling pattern](../patterns/dynamic-scheduling). |
177
+
178
+ For automatic local delivery, start eve through `vc dev` with a Vercel CLI that supports local Schedules and Queues. Plain `eve dev` still uses the in-memory fallback. Local queue-delivery errors appear in the dev server logs and retry after five seconds.
179
+
180
+ Import `vercelScheduleProvider` from `eve/experimental/schedules/vercel` to configure the Vercel endpoint explicitly. The in-memory provider is also available from `eve/experimental/schedules/testing` for code-driven tests.
181
+
182
+ ## What to read next
183
+
184
+ - [Schedules](../schedules): author fixed cron jobs with `defineSchedule`.
185
+ - [Channels](../channels/overview): deliver schedule output to users.
186
+ - [Sessions, runs & streaming](../concepts/sessions-runs-and-streaming): inspect a scheduled run.
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Experimental",
3
+ "pages": ["dynamic-schedules"]
4
+ }
@@ -136,6 +136,7 @@ The most common UI events are:
136
136
 
137
137
  | Event | Use |
138
138
  | ----------------------- | ------------------------------------------------------------------------------ |
139
+ | `history.imported` | Render prior conversation a delivery added, before that turn's message. |
139
140
  | `message.received` | Confirm the user message landed; `data.parts` includes text and file metadata. |
140
141
  | `reasoning.appended` | Render reasoning deltas when the model provides them. |
141
142
  | `message.appended` | Render assistant text deltas. |
@@ -34,6 +34,7 @@ You can instead select an installed Workflow world package in the root `agent.ts
34
34
  import { defineAgent } from "eve";
35
35
 
36
36
  export default defineAgent({
37
+ model: "anthropic/claude-opus-5.5",
37
38
  experimental: {
38
39
  workflow: {
39
40
  world: "@acme/eve-workflow-world",
@@ -105,6 +106,8 @@ You can instead use [`eve/next`](../frontend/nextjs#dev-vs-deploy-topology) if y
105
106
 
106
107
  The standard `eve build && eve start` path starts Nitro’s schedule runner. If you adapt the output to a custom HTTP-only host or preset, run Nitro scheduled tasks or invoke the same work from your scheduler.
107
108
 
109
+ Each `eve start` process runs every authored schedule. When you run several replicas, start one normally and start the others with `eve start --no-schedules`, or set `EVE_SCHEDULES_DISABLED=1` in their environment. See [`eve start`](../../reference/cli#eve-start).
110
+
108
111
  ## Verify the service
109
112
 
110
113
  Check the health route after your proxy and authentication configuration are active:
@@ -50,7 +50,27 @@ export default githubChannel({
50
50
 
51
51
  A GitHub channel event handler cannot fire for a Slack-owned session, so platform-specific side effects do not depend on an early-return guard. On a built-in channel, an authored handler replaces that channel's default handler for the same event key. Check the channel page before overriding events that deliver replies, progress, errors, or human-input prompts. The [Slack channel](/docs/channels/slack#customize-rendering) takes renderers instead: a renderer's handler keeps Slack's default by calling `next()` and replaces it by skipping `next`.
52
52
 
53
- Use `ctx.channel.kind` inside a global hook only when the operation is otherwise agent-wide and conditional handling is intentional. For typed channel metadata in dynamic resolvers or instrumentation, import the channel definition and narrow with `isChannel`; see [OpenTelemetry runtime context](../observability/otel#add-runtime-context).
53
+ Use `ctx.channel.kind` inside a global hook only when the operation is otherwise agent-wide and conditional handling is intentional. For a channel with event handlers, such as `githubChannel`, `slackChannel`, and the other built-ins, the kind is the path-derived identity. `agent/channels/github.ts` reports `channel:github`, so `ctx.channel.kind === "github"` never matches. Import the channel definition and compare it with `isChannel` from `eve/channels`:
54
+
55
+ ```ts title="agent/hooks/github-only.ts"
56
+ import { isChannel } from "eve/channels";
57
+ import { defineHook } from "eve/hooks";
58
+
59
+ import github from "../channels/github";
60
+
61
+ export default defineHook({
62
+ events: {
63
+ async "turn.started"(_event, ctx) {
64
+ if (!isChannel(ctx.channel, github)) return;
65
+ // GitHub-owned turns only.
66
+ },
67
+ },
68
+ });
69
+ ```
70
+
71
+ A route-only `defineChannel` with no event handlers keeps the kind `"http"`, so `isChannel` does not match it in a hook. Compare `ctx.channel.kind === "http"` for those channels instead.
72
+
73
+ For typed channel metadata in dynamic resolvers or instrumentation, narrow with `isChannel` the same way; see [OpenTelemetry runtime context](../observability/otel#add-runtime-context).
54
74
 
55
75
  ## Hook structure and context
56
76
 
@@ -87,7 +87,8 @@ automatically or only through explicit tool calls.
87
87
  `fileMemory()` from `eve/memory/file` keeps one bounded document per scope and
88
88
  gives the model `save_memory` and `remove_memory` tools. It does not extract
89
89
  facts automatically; the model decides what to save. It needs no external
90
- service in `eve dev` and stores to Vercel Blob when deployed to Vercel, which
90
+ service in `eve dev` and stores to Vercel Blob when deployed to Vercel with Blob
91
+ configured, which
91
92
  makes it the shortest path to a working slot.
92
93
 
93
94
  Read [File memory](/docs/memory/file) for size limits, storage backends, and options.
@@ -3,7 +3,9 @@ title: "Dynamic Scheduling"
3
3
  description: "Compose one minute-level eve schedule, proactive channel handoff, and CRUD tools into application-managed schedules."
4
4
  ---
5
5
 
6
- Authored eve schedules are static files discovered at build time. You can build dynamic scheduling today by putting schedule rows in your application store and using one authored schedule as a dispatcher:
6
+ Use this pattern when your application needs to own schedule storage and dispatch, including on self-hosted infrastructure. For built-in creator-owned schedules, management tools, and Vercel-backed delivery, see [Experimental dynamic schedules](../experimental/dynamic-schedules).
7
+
8
+ This pattern uses a static `defineSchedule` file discovered at build time. Put schedule rows in your application store and use that authored schedule as a dispatcher:
7
9
 
8
10
  1. CRUD tools let the agent create and manage rows for the current tenant;
9
11
  2. `defineSchedule({ cron: "* * * * *" })` wakes once a minute;