eve 0.30.8 → 0.31.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 (211) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +5 -6
  3. package/dist/src/acp/adapter.d.ts +10 -4
  4. package/dist/src/acp/adapter.js +1 -1
  5. package/dist/src/channel/adapter-context.d.ts +1 -1
  6. package/dist/src/channel/adapter.d.ts +2 -4
  7. package/dist/src/channel/adapter.js +1 -1
  8. package/dist/src/channel/channel-address.d.ts +58 -0
  9. package/dist/src/channel/channel-address.js +1 -0
  10. package/dist/src/channel/channel-operations.d.ts +72 -0
  11. package/dist/src/channel/channel-operations.js +1 -0
  12. package/dist/src/channel/compiled-channel.d.ts +10 -6
  13. package/dist/src/channel/cross-channel-receive.d.ts +13 -25
  14. package/dist/src/channel/cross-channel-receive.js +2 -2
  15. package/dist/src/channel/receive-target.d.ts +1 -2
  16. package/dist/src/channel/routes.d.ts +21 -149
  17. package/dist/src/channel/schedule.d.ts +1 -1
  18. package/dist/src/channel/schedule.js +1 -1
  19. package/dist/src/channel/send-input.d.ts +6 -0
  20. package/dist/src/channel/send-input.js +1 -0
  21. package/dist/src/channel/session.d.ts +44 -30
  22. package/dist/src/channel/session.js +1 -1
  23. package/dist/src/channel/types.d.ts +8 -35
  24. package/dist/src/chunks/{use-eve-agent-DDoDDmJ2.js → use-eve-agent-CF5D6jSV.js} +339 -367
  25. package/dist/src/chunks/{use-eve-agent-Bx-ONuyb.js → use-eve-agent-DbYZLG0h.js} +342 -368
  26. package/dist/src/cli/commands/agent-prompt/build-and-verify.md +1 -2
  27. package/dist/src/cli/commands/channel-add-conflicts.js +1 -1
  28. package/dist/src/cli/commands/info.d.ts +1 -1
  29. package/dist/src/cli/commands/info.js +1 -1
  30. package/dist/src/cli/dev/tui/runner.d.ts +1 -1
  31. package/dist/src/cli/dev/tui/runner.js +1 -1
  32. package/dist/src/cli/dev/tui/subagent-pump.js +1 -1
  33. package/dist/src/cli/dev/tui/tui.js +1 -1
  34. package/dist/src/cli/invoke/invoke.d.ts +1 -1
  35. package/dist/src/cli/invoke/invoke.js +1 -1
  36. package/dist/src/cli/invoke/result.d.ts +0 -11
  37. package/dist/src/cli/invoke/result.js +1 -1
  38. package/dist/src/client/client-error.d.ts +2 -0
  39. package/dist/src/client/client-error.js +1 -1
  40. package/dist/src/client/client.d.ts +7 -14
  41. package/dist/src/client/client.js +1 -1
  42. package/dist/src/client/eve-agent-store.d.ts +4 -4
  43. package/dist/src/client/eve-agent-store.js +1 -1
  44. package/dist/src/client/index.d.ts +2 -1
  45. package/dist/src/client/index.js +1 -1
  46. package/dist/src/client/message-response.d.ts +2 -7
  47. package/dist/src/client/message-response.js +1 -1
  48. package/dist/src/client/open-stream.js +1 -1
  49. package/dist/src/client/session-controls.d.ts +29 -0
  50. package/dist/src/client/session-controls.js +1 -0
  51. package/dist/src/client/session-utils.d.ts +0 -20
  52. package/dist/src/client/session-utils.js +1 -1
  53. package/dist/src/client/session.d.ts +26 -90
  54. package/dist/src/client/session.js +1 -1
  55. package/dist/src/client/sessions.d.ts +20 -0
  56. package/dist/src/client/sessions.js +1 -0
  57. package/dist/src/client/types.d.ts +26 -47
  58. package/dist/src/compiler/normalize-subagent.js +1 -1
  59. package/dist/src/evals/cli/eval-client.js +1 -1
  60. package/dist/src/evals/context.js +1 -1
  61. package/dist/src/evals/session.d.ts +7 -6
  62. package/dist/src/evals/session.js +1 -1
  63. package/dist/src/evals/types.d.ts +9 -9
  64. package/dist/src/execution/agent-handle-dispatch.js +1 -1
  65. package/dist/src/execution/cancel-descendant-turns-step.js +1 -1
  66. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  67. package/dist/src/execution/remote-agent-dispatch.d.ts +1 -3
  68. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  69. package/dist/src/execution/runtime-context.js +1 -1
  70. package/dist/src/execution/runtime-errors.d.ts +1 -1
  71. package/dist/src/execution/session-command-token.d.ts +2 -0
  72. package/dist/src/execution/session-command-token.js +1 -1
  73. package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
  74. package/dist/src/execution/subagent-hitl-proxy.js +1 -1
  75. package/dist/src/execution/turn-control-receiver.d.ts +2 -2
  76. package/dist/src/execution/turn-workflow.js +1 -1
  77. package/dist/src/execution/workflow-entry.js +1 -1
  78. package/dist/src/execution/workflow-runtime.js +1 -1
  79. package/dist/src/harness/cancelled-turn-emission.d.ts +1 -1
  80. package/dist/src/harness/cancelled-turn-emission.js +1 -1
  81. package/dist/src/harness/emission.d.ts +1 -1
  82. package/dist/src/harness/emission.js +1 -1
  83. package/dist/src/harness/handles/prompt.d.ts +22 -3
  84. package/dist/src/harness/handles/prompt.js +1 -1
  85. package/dist/src/harness/handles/store.d.ts +0 -5
  86. package/dist/src/harness/handles/store.js +1 -1
  87. package/dist/src/harness/runtime-actions.js +1 -1
  88. package/dist/src/harness/session-limit-enforcement.js +1 -1
  89. package/dist/src/harness/tool-loop.js +1 -1
  90. package/dist/src/internal/application/package.js +1 -1
  91. package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
  92. package/dist/src/internal/nitro/dev-runtime-source-snapshot-copy.js +1 -1
  93. package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.d.ts +15 -0
  94. package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.js +1 -0
  95. package/dist/src/internal/nitro/dev-runtime-source-snapshot.d.ts +14 -2
  96. package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
  97. package/dist/src/internal/nitro/host/dev-server-http.js +1 -1
  98. package/dist/src/internal/nitro/routes/channel-dispatch.d.ts +2 -5
  99. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  100. package/dist/src/internal/nitro/routes/channel-route-context.d.ts +4 -3
  101. package/dist/src/internal/nitro/routes/channel-route-context.js +1 -1
  102. package/dist/src/packages/eve-catalog/src/index.js +1 -1
  103. package/dist/src/protocol/cancel-turn.d.ts +9 -4
  104. package/dist/src/protocol/cancel-turn.js +1 -1
  105. package/dist/src/protocol/message.d.ts +4 -14
  106. package/dist/src/protocol/message.js +2 -2
  107. package/dist/src/protocol/reset-session.d.ts +1 -1
  108. package/dist/src/protocol/routes.d.ts +27 -47
  109. package/dist/src/protocol/routes.js +1 -1
  110. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +19 -21
  111. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  112. package/dist/src/public/channels/discord/discordChannel.d.ts +4 -4
  113. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  114. package/dist/src/public/channels/eve.d.ts +11 -10
  115. package/dist/src/public/channels/eve.js +1 -1
  116. package/dist/src/public/channels/github/dispatch.d.ts +8 -8
  117. package/dist/src/public/channels/github/dispatch.js +1 -1
  118. package/dist/src/public/channels/github/githubChannel.d.ts +3 -3
  119. package/dist/src/public/channels/github/githubChannel.js +1 -1
  120. package/dist/src/public/channels/github/pr-context.d.ts +1 -1
  121. package/dist/src/public/channels/index.d.ts +1 -1
  122. package/dist/src/public/channels/linear/linearChannel.d.ts +3 -3
  123. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  124. package/dist/src/public/channels/slack/index.d.ts +1 -1
  125. package/dist/src/public/channels/slack/interactions.d.ts +3 -4
  126. package/dist/src/public/channels/slack/interactions.js +1 -1
  127. package/dist/src/public/channels/slack/session-operations.d.ts +37 -0
  128. package/dist/src/public/channels/slack/session-operations.js +1 -0
  129. package/dist/src/public/channels/slack/slackChannel.d.ts +55 -74
  130. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  131. package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -3
  132. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  133. package/dist/src/public/channels/telegram/telegramChannel.d.ts +4 -4
  134. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  135. package/dist/src/public/channels/twilio/twilioChannel.d.ts +3 -3
  136. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  137. package/dist/src/public/definitions/callback-context.d.ts +1 -2
  138. package/dist/src/public/definitions/channel.d.ts +21 -86
  139. package/dist/src/public/definitions/channel.js +1 -1
  140. package/dist/src/public/definitions/remote-agent.js +1 -1
  141. package/dist/src/public/definitions/schedule.d.ts +16 -16
  142. package/dist/src/public/schedules/index.d.ts +1 -1
  143. package/dist/src/react/use-eve-agent.d.ts +7 -4
  144. package/dist/src/react/use-eve-agent.js +1 -1
  145. package/dist/src/runtime/agent/bootstrap-model-utils.d.ts +11 -0
  146. package/dist/src/runtime/agent/bootstrap-model-utils.js +1 -1
  147. package/dist/src/runtime/agent/mock-model-adapter.js +1 -1
  148. package/dist/src/runtime/agent/mock-model-fixtures.js +2 -2
  149. package/dist/src/runtime/prompt/compose.js +1 -1
  150. package/dist/src/runtime/subagents/dynamic-remote-agent-config.js +1 -1
  151. package/dist/src/runtime/types.d.ts +2 -2
  152. package/dist/src/services/inspect-application.d.ts +1 -1
  153. package/dist/src/services/inspect-application.js +1 -1
  154. package/dist/src/setup/channel-add-conflicts.js +1 -1
  155. package/dist/src/setup/scaffold/create/project.js +1 -1
  156. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  157. package/dist/src/setup/scaffold/create/web-template.js +29 -17
  158. package/dist/src/shared/channel-definition.d.ts +7 -7
  159. package/dist/src/shared/schedule-definition.d.ts +2 -2
  160. package/dist/src/svelte/index.js +1 -1
  161. package/dist/src/svelte/use-eve-agent.d.ts +8 -5
  162. package/dist/src/svelte/use-eve-agent.js +1 -1
  163. package/dist/src/vue/index.js +1 -1
  164. package/dist/src/vue/use-eve-agent.d.ts +8 -5
  165. package/dist/src/vue/use-eve-agent.js +1 -1
  166. package/docs/README.md +6 -6
  167. package/docs/channels/chat-sdk.mdx +5 -6
  168. package/docs/channels/custom.mdx +116 -144
  169. package/docs/channels/discord.mdx +1 -1
  170. package/docs/channels/eve.mdx +76 -30
  171. package/docs/channels/github.mdx +1 -1
  172. package/docs/channels/linear.mdx +6 -8
  173. package/docs/channels/overview.mdx +2 -2
  174. package/docs/channels/slack.mdx +46 -15
  175. package/docs/channels/telegram.mdx +1 -1
  176. package/docs/channels/twilio.mdx +1 -1
  177. package/docs/concepts/default-harness.md +11 -3
  178. package/docs/concepts/execution-model-and-durability.mdx +17 -5
  179. package/docs/concepts/sessions-runs-and-streaming.md +42 -16
  180. package/docs/evals/cases.mdx +3 -3
  181. package/docs/evals/targets.mdx +1 -1
  182. package/docs/guides/auth-and-route-protection.md +2 -1
  183. package/docs/guides/client/continuations.mdx +50 -77
  184. package/docs/guides/client/messages.mdx +29 -33
  185. package/docs/guides/client/output-schema.mdx +7 -14
  186. package/docs/guides/client/overview.mdx +40 -11
  187. package/docs/guides/client/streaming.mdx +17 -23
  188. package/docs/guides/frontend/nuxt.mdx +1 -1
  189. package/docs/guides/frontend/overview.mdx +40 -36
  190. package/docs/guides/frontend/sveltekit.mdx +1 -1
  191. package/docs/guides/frontend/use-eve-agent-svelte.mdx +19 -23
  192. package/docs/guides/frontend/use-eve-agent-vue.mdx +21 -25
  193. package/docs/guides/instrumentation.md +1 -1
  194. package/docs/patterns/dynamic-scheduling.md +16 -15
  195. package/docs/schedules.mdx +10 -11
  196. package/docs/subagents.mdx +1 -1
  197. package/docs/tools/human-in-the-loop.md +1 -1
  198. package/docs/tutorial/ship-it.mdx +1 -1
  199. package/package.json +1 -1
  200. package/dist/src/channel/cancel.d.ts +0 -6
  201. package/dist/src/channel/cancel.js +0 -1
  202. package/dist/src/channel/clear-session.d.ts +0 -4
  203. package/dist/src/channel/clear-session.js +0 -1
  204. package/dist/src/channel/compact-session.d.ts +0 -4
  205. package/dist/src/channel/compact-session.js +0 -1
  206. package/dist/src/channel/reset-session.d.ts +0 -6
  207. package/dist/src/channel/reset-session.js +0 -1
  208. package/dist/src/channel/resolve-active-session.d.ts +0 -4
  209. package/dist/src/channel/resolve-active-session.js +0 -1
  210. package/dist/src/channel/send.d.ts +0 -6
  211. package/dist/src/channel/send.js +0 -1
@@ -6,7 +6,7 @@ description: "How users reach your agent: the channel contract, the base eve HTT
6
6
  A channel is the edge adapter between a platform and your agent. It does three things:
7
7
 
8
8
  - Normalizes platform input into a user message.
9
- - Owns the `continuationToken`, the resume handle for a conversation on that surface.
9
+ - Owns the channel-local address that maps a platform conversation to its current durable session.
10
10
  - Decides delivery, meaning how, where, and whether a response goes back.
11
11
 
12
12
  eve ships a base HTTP channel plus first-class platform channels, and you can author your own. Browse the full set in the [Integrations](/integrations) gallery and choose the Channels filter.
@@ -36,7 +36,7 @@ The eve channel is the framework's default HTTP session API, the routes the term
36
36
 
37
37
  ## Custom channels
38
38
 
39
- When eve doesn't ship a channel for your surface, build one with `defineChannel` from `eve/channels`. A custom channel declares route handlers (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `WS`), an `events` map, and a `send` call inside a handler to start or resume a session. See [Custom channels](./custom) for the full walkthrough, including WebSocket routes, cross-channel hand-off, channel metadata, continuation tokens, and file uploads.
39
+ When eve doesn't ship a channel for your surface, build one with `defineChannel` from `eve/channels`. A custom channel declares route handlers (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `WS`), an `events` map, and uses `send(address, input)` to start or resume a session. See [Custom channels](./custom) for the full walkthrough, including WebSocket routes, cross-channel hand-off, channel metadata, address tokens, and file uploads.
40
40
 
41
41
  ## Relationship to the Chat SDK
42
42
 
@@ -108,7 +108,7 @@ Like `threadContext`, this helper calls `conversations.replies` and requires the
108
108
 
109
109
  #### Cancel an active turn
110
110
 
111
- Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) receive a thread-bound `ctx.cancel({ turnId? })` helper. Call it before returning `{ auth }` to stop the current turn and queue the new Slack message as replacement input, producing a debounce-like experience:
111
+ Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) receive thread-bound session operations directly on `ctx`. Call `ctx.cancel({ turnId? })` before returning `{ auth }` to stop the current turn and queue the new Slack message as replacement input, producing a debounce-like experience:
112
112
 
113
113
  ```ts title="agent/channels/slack.ts"
114
114
  export default slackChannel({
@@ -123,11 +123,28 @@ export default slackChannel({
123
123
  });
124
124
  ```
125
125
 
126
- `"accepted"` means the cancellation request was consumed; `"no_active_turn"` means the thread had no cancellable turn. Both are successful outcomes, so the hook can still return `{ auth }` to deliver the replacement message. Cancellation never sends input itself. A custom `onInteraction` handler receives the same bound helper, which is useful for a Stop button that should cancel without replacement input.
126
+ `"accepted"` means the live thread session durably queued the cancellation request. A parked session consumes it as a no-op; `"no_active_turn"` means the thread has no active session owner. Both are successful outcomes, so the hook can still return `{ auth }` to deliver the replacement message. Cancellation never sends input itself. A custom `onInteraction` handler receives the same flat operations, which is useful for a Stop button that should cancel without replacement input.
127
+
128
+ Message and interaction hooks expose every current-owner operation directly on `ctx`:
129
+
130
+ ```ts
131
+ async onAppMention(ctx) {
132
+ await ctx.send("Run a separate turn.");
133
+ await ctx.respond(inputResponses);
134
+ await ctx.cancel();
135
+ await ctx.compact();
136
+ await ctx.clear();
137
+ await ctx.reset({ reason: "Start over" });
138
+ const session = await ctx.resolveSession();
139
+ return { auth: null };
140
+ }
141
+ ```
142
+
143
+ These calls are independent examples; a handler normally chooses one. `ctx.send()` is the only method that can create a session when the thread is unowned. It derives Slack auth from the inbound user unless `auth` is supplied explicitly. Use `await ctx.resolveSession()` only when later work must stay pinned to the currently owning durable session ID.
127
144
 
128
145
  #### Reset a conversation
129
146
 
130
- Use the thread-bound `ctx.reset({ reason? })` helper when the conversation should start over instead. Reset terminally retires the session that currently owns the thread, including any in-flight turn. The next delivered message starts a new session with fresh history, state, and a new session-scoped sandbox:
147
+ Use the thread-bound `ctx.reset({ reason? })` method when the conversation should start over instead. Reset terminally retires the session that currently owns the thread, including any in-flight turn. The next delivered message starts a new session with fresh history, state, and a new session-scoped sandbox:
131
148
 
132
149
  ```ts title="agent/channels/slack.ts"
133
150
  export default slackChannel({
@@ -142,11 +159,11 @@ export default slackChannel({
142
159
  });
143
160
  ```
144
161
 
145
- Reset returns `{ status: "reset", previousSessionId }` when the thread had a session, or `{ status: "no_active_session" }` when it was already free. Both outcomes are successful. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same bound reset helper, which is useful for a New conversation button.
162
+ Reset returns `{ status: "reset", previousSessionId }` when the thread had a session, or `{ status: "no_active_session" }` when it was already free. Both outcomes are successful. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button.
146
163
 
147
164
  ### Other Events API callbacks
148
165
 
149
- 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. `ctx.receive(options)` is the current Slack channel's pre-bound form of the schedule API's `receive(slack, options)`: call it zero, one, or many times to start turns. Each call accepts `message`, `target`, and `auth`, and returns the resulting session.
166
+ 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 })` zero, one, or many times to start turns. Each call returns the resulting session.
150
167
 
151
168
  ```ts title="agent/channels/slack.ts"
152
169
  import { connectSlackCredentials } from "@vercel/connect/eve";
@@ -156,25 +173,39 @@ const onboardingChannels = ["C0123ABC", "C0456DEF"];
156
173
 
157
174
  export default slackChannel({
158
175
  credentials: connectSlackCredentials("slack/my-agent"),
159
- async onEvent({ receive }, event) {
176
+ async onEvent(ctx, event) {
160
177
  if (event.type !== "team_join") return;
161
178
 
162
179
  await Promise.all(
163
180
  onboardingChannels.map((channelId) =>
164
- receive({
165
- message: `A user joined the Slack workspace. Onboard them from this event:\n${JSON.stringify(event)}`,
166
- target: { channelId },
167
- auth: null,
168
- }),
181
+ ctx.send(
182
+ `A user joined the Slack workspace. Onboard them from this event:\n${JSON.stringify(event)}`,
183
+ {
184
+ target: { channelId },
185
+ auth: null,
186
+ },
187
+ ),
169
188
  ),
170
189
  );
171
190
  },
172
191
  });
173
192
  ```
174
193
 
175
- The webhook invocation already keeps an awaited `onEvent` handler alive. Use `ctx.waitUntil(promise)` for deliberately detached work, matching a handler-form schedule. `ctx.slack.request(operation, body)` provides workspace-scoped Slack Web API access, while `ctx.envelope` carries delivery metadata such as `team_id`, `event_id`, and `event_time`. Calls to `ctx.receive` automatically seed the callback's team id into Slack session state.
194
+ The webhook invocation already keeps an awaited `onEvent` handler alive. Use `ctx.waitUntil(promise)` for deliberately detached work, matching a handler-form schedule. `ctx.slack.request(operation, body)` provides workspace-scoped Slack Web API access, while `ctx.envelope` carries delivery metadata such as `team_id`, `event_id`, and `event_time`. Calls to `ctx.send` automatically seed the callback's team id into Slack session state.
195
+
196
+ Because a generic event is not necessarily tied to one thread, put the target in each operation's input:
176
197
 
177
- Because a generic event is not necessarily tied to one thread, its session helpers take the target explicitly: `ctx.cancel({ channelId, threadTs, turnId? })` stops in-flight work while retaining the session, and `ctx.reset({ channelId, threadTs, reason? })` retires the session. Pair either one with `ctx.receive(...)` when an event should deliver replacement input, or call it alone to stop or reset the thread.
198
+ ```ts
199
+ const target = { channelId, threadTs };
200
+
201
+ await ctx.send("Follow up", { auth: null, target });
202
+ await ctx.respond(inputResponses, { auth: null, target });
203
+ await ctx.cancel({ target, turnId });
204
+ await ctx.compact({ target });
205
+ await ctx.clear({ target });
206
+ await ctx.reset({ reason: "Start over", target });
207
+ const session = await ctx.resolveSession({ target });
208
+ ```
178
209
 
179
210
  `onEvent` is the raw fallback after the message hooks. If an event is not claimed by `onAppMention`, `onDirectMessage`, or `onMessage`, an authored `onEvent` receives it; otherwise eve applies the built-in mention/DM default or ignores it.
180
211
 
@@ -233,7 +264,7 @@ The default handlers reply in-thread and show progress. Typing indicators post a
233
264
 
234
265
  Outbound text preserves bare `@` tokens as literal text. To mention a user, embed Slack's `<@USER_ID>` syntax directly or use `channel.thread.mentionUser(userId)`.
235
266
 
236
- When a session starts without a `threadTs` (say, from a schedule or `receive(slack, ...)`), eve gives it a unique temporary continuation token. The first agent post anchors the session to the Slack message timestamp, and later posts and mentions resume that same session. Pass `initialMessage` with a `Card` to land a structured anchor first instead. `threadTs` and `initialMessage` are mutually exclusive.
267
+ When a session starts without a `threadTs` (say, from a schedule's `to(slack, target).send(...)`), eve gives it a unique temporary continuation token. The first agent post anchors the session to the Slack message timestamp, and later posts and mentions resume that same session. Pass `initialMessage` with a `Card` to land a structured anchor first instead. `threadTs` and `initialMessage` are mutually exclusive.
237
268
 
238
269
  The example below overrides `onAppMention` to gate on an authored message and posts the completed reply to the thread. Event handlers receive `(eventData, channel, ctx)`, with Slack platform handles on `channel.thread` and `channel.slack`:
239
270
 
@@ -272,7 +303,7 @@ events: {
272
303
 
273
304
  ### Proactive sessions
274
305
 
275
- Start a session without an inbound message through `receive(slack, { message, target, auth })` from a schedule `run` handler, or `args.receive(slack, ...)` from another channel. The proactive target shape is `{ channelId }`.
306
+ Start a session without an inbound message through `ctx.to(slack, target).send(message, { auth })` from another channel, or `to(slack, target).send(message, { auth })` from a schedule `run` handler. The proactive target shape is `{ channelId, threadTs?, initialMessage? }`.
276
307
 
277
308
  ### Attachments
278
309
 
@@ -51,7 +51,7 @@ Human-in-the-loop (HITL) turns option requests into inline-keyboard buttons and
51
51
 
52
52
  ### Proactive sessions
53
53
 
54
- Start a session without an inbound message through `receive(telegram, { message, target, auth })` from a schedule `run` handler, or `args.receive(telegram, ...)` from another channel. `target.chatId` is required. Add `messageThreadId` to land in a specific forum topic.
54
+ Start a session without an inbound message through `to(telegram, target).send(message, { auth })` from a schedule `run` handler, or `ctx.to(telegram, target).send(message, { auth })` from another channel. `target.chatId` is required. Add `messageThreadId` to land in a specific forum topic.
55
55
 
56
56
  Private proactive chats stay keyed to the chat, or to the chat plus `messageThreadId` when you target a topic. Group and supergroup proactive sends anchor to the bot message id returned by Telegram, so replies to different bot messages can resume different sessions in the same chat. If Telegram does not return a recognized chat type for an outbound send, eve keeps the session unanchored instead of guessing.
57
57
 
@@ -66,7 +66,7 @@ SMS and voice have no native button or card affordance, so HITL prompts do not r
66
66
 
67
67
  ### Proactive sessions
68
68
 
69
- Start a session without an inbound message through `receive(twilio, { message, target, auth })` from a schedule `run` handler, or `args.receive(twilio, ...)` from another channel. `target.phoneNumber` is required, and the channel needs `messaging.from` or `messaging.messagingServiceSid` for the outbound sender.
69
+ Start a session without an inbound message through `to(twilio, target).send(message, { auth })` from a schedule `run` handler, or `ctx.to(twilio, target).send(message, { auth })` from another channel. `target.phoneNumber` is required, and the channel needs `messaging.from` or `messaging.messagingServiceSid` for the outbound sender.
70
70
 
71
71
  ### Attachments
72
72
 
@@ -20,9 +20,17 @@ export default defineAgent({
20
20
 
21
21
  Compaction also preserves the framework's own tool state automatically. It resets read-before-write tracking (so a write afterward re-reads the file whose read evidence was summarized away) and re-injects the active todo list, so the model keeps its task list across the summary. There is no per-tool hook to configure.
22
22
 
23
- Clients and channels can also request compaction between turns. Call `ClientSession.compact()` or a custom route's `compact({ continuationToken })` helper. The request does not append a user message; if a turn is running, eve queues it until that turn settles. A successful manual compaction emits the same `compaction.requested` and `compaction.completed` events as automatic compaction, followed by `session.waiting`.
24
-
25
- To discard model-message history instead of summarizing it, call `ClientSession.clear()` or a custom route's `clear({ continuationToken })` helper. Clearing preserves the session identity, system prompt, configured tools and skills, durable state, limits, and sandbox. Its stream boundary is `context.cleared` followed by `session.waiting`.
23
+ Clients and channels can also request compaction between turns. Call
24
+ `ClientSession.compact()`, a channel route's `compact(address)`, or
25
+ `attachSession(sessionId).compact()`. The request does not append a user message;
26
+ if a turn is running, eve queues it until that turn settles. A successful manual
27
+ compaction emits the same `compaction.requested` and `compaction.completed`
28
+ events as automatic compaction, followed by `session.waiting`.
29
+
30
+ To discard model-message history instead of summarizing it, call the corresponding
31
+ `clear()` method on any of those handles. Clearing preserves the session identity,
32
+ system prompt, configured tools and skills, durable state, limits, and sandbox.
33
+ Its stream boundary is `context.cleared` followed by `session.waiting`.
26
34
 
27
35
  ## Built-in tools
28
36
 
@@ -68,11 +68,23 @@ Some work has to wait, including a human approving a [tool](../tools), an intera
68
68
 
69
69
  ## Message delivery and queueing
70
70
 
71
- eve does not maintain a durable FIFO queue of user messages for a session. The `continuationToken` is a resume handle for the session's current workflow hook, not a general message-queue address.
72
-
73
- Only one active session can own a continuation token. When a session starts with a token, eve commits the park hook before processing its first turn and fails a competing session if another run already owns that token. A tokenless session claims its token after the first turn establishes one. Competing input is not forwarded to the owner.
74
-
75
- When a session is waiting, a delivery to the current continuation token wakes the session and starts the next turn. When a turn is already active, the hook may accept additional deliveries, but the runtime only drains them at specific workflow boundaries. If more than one delivery is ready when the driver checks, eve may fold them into the next turn; that drain is best-effort and depends on workflow and transport timing.
71
+ eve does not maintain a durable FIFO queue of user messages for a session. An
72
+ ID-addressed HTTP delivery and a channel-address delivery both target the
73
+ session's current command inbox; neither is a general message queue.
74
+
75
+ Only one active session can own a channel continuation token. A channel-created
76
+ session commits both its stable session-ID alias and its channel alias before
77
+ processing the first turn, and fails creation if another run already owns the
78
+ channel alias. Rekeying replaces only that channel alias. An HTTP-created
79
+ session has the stable ID alias and no channel continuation token. Competing
80
+ channel input is not forwarded to the owner.
81
+
82
+ When a session is waiting, a delivery through its ID or current channel address
83
+ wakes the session and starts the next turn. When a turn is already active, the
84
+ inbox may accept additional deliveries, but the runtime only drains them at
85
+ specific workflow boundaries. If more than one delivery is ready when the driver
86
+ checks, eve may fold them into the next turn; that drain is best-effort and
87
+ depends on workflow and transport timing.
76
88
 
77
89
  So don't rely on concurrent sends to the same session behaving like a typical ordered chat queue. For deterministic behavior, send one user turn at a time and wait for `session.waiting` before sending the next message to the same session. If your channel can receive bursts while the agent is working, keep your own per-session queue in the channel or app layer, then deliver the next message after the session parks again. Separate sessions still run independently.
78
90
 
@@ -1,18 +1,20 @@
1
1
  ---
2
2
  title: "Sessions, Runs & Streaming"
3
- description: "The session and run contract you touch: continuation tokens, stream handles, the NDJSON event stream, and reconnecting."
3
+ description: "The ID-addressed session contract: messages, controls, the NDJSON event stream, and reconnecting."
4
4
  ---
5
5
 
6
6
  Every eve app speaks the same stable HTTP API to a [durable session](./execution-model-and-durability). This page is the contract you hold: the handles you get back, the events you stream, and how to reconnect.
7
7
 
8
- ## The two handles
8
+ ## Identity by surface
9
9
 
10
- Two handles do two jobs, and mixing them up is the most common mistake. One handle creates and resumes a session; a different one streams and inspects it.
10
+ The HTTP API and TypeScript client use one durable `sessionId` for messages,
11
+ controls, and streams. Every operation targets that exact session; none follows
12
+ or creates a replacement implicitly.
11
13
 
12
- - **`continuationToken`**: the resume handle. Use it to send a follow-up message to the same conversation. Owned by the channel.
13
- - **`sessionId` / `runId`**: the stream-and-inspect handle. Use it to attach to the event stream and watch a run. Owned by the runtime.
14
-
15
- A session has one active continuation at a time: each follow-up uses the current `continuationToken`, and a stale one is rejected.
14
+ Authored channels also have channel-local continuation tokens. A token addresses
15
+ whichever session currently owns a platform conversation, such as a Slack thread.
16
+ That identity stays behind the channel boundary and is never accepted or returned
17
+ by the eve HTTP session API. See [Custom channels](../channels/custom#address-and-session-handles).
16
18
 
17
19
  Sessions last 30 days by default; configure `limits.sessionTimeoutMs` in
18
20
  `agent.ts`, or set it to `false` to disable the deadline. At expiration, eve
@@ -30,7 +32,8 @@ curl -X POST http://127.0.0.1:2000/eve/v1/session \
30
32
  -d '{"message":"Summarize the latest forecast."}'
31
33
  ```
32
34
 
33
- eve responds right away. The JSON body carries a `sessionId` and a `continuationToken`, and the `x-eve-session-id` header names the durable session to stream.
35
+ eve responds right away with the durable `sessionId` in the JSON body and
36
+ `x-eve-session-id` header.
34
37
 
35
38
  ## Stream a session
36
39
 
@@ -66,7 +69,7 @@ The stream is newline-delimited JSON (NDJSON), one event per line:
66
69
  | `turn.completed` | The turn finished. |
67
70
  | `turn.failed` | The turn failed; carries `{ code, message, details? }`. |
68
71
  | `turn.cancelled` | The turn was cancelled before finishing; always followed by `session.waiting`. |
69
- | `session.waiting` | The session parked for the next input; carries the current channel-owned `continuationToken`. |
72
+ | `session.waiting` | The session parked and is ready for the next message. |
70
73
  | `session.failed` | The session failed. |
71
74
  | `session.completed` | The session reached a terminal end. |
72
75
 
@@ -137,12 +140,12 @@ Authored [hooks](../guides/hooks) receive the same envelope, but observe each ev
137
140
 
138
141
  ## Send a follow-up message
139
142
 
140
- Once the session is waiting (you'll see `session.waiting`), POST your follow-up to the session endpoint with `event.data.continuationToken`:
143
+ Once the session is waiting (you'll see `session.waiting`), POST your follow-up to its ID-addressed messages endpoint:
141
144
 
142
145
  ```bash
143
146
  curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId> \
144
147
  -H 'content-type: application/json' \
145
- -d '{"continuationToken":"<token>","message":"Now send the short version."}'
148
+ -d '{"message":"Now send the short version."}'
146
149
  ```
147
150
 
148
151
  The follow-up reuses the same durable session: same history, same state.
@@ -166,9 +169,30 @@ curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId>/cancel
166
169
  # {"ok":true,"sessionId":"<sessionId>","status":"accepted"}
167
170
  ```
168
171
 
169
- `"accepted"` means a cancellation hook accepted the request. Confirm cancellation on the stream as `turn.cancelled` followed by `session.waiting`; the session then accepts the next message normally. If the turn is waiting on active local or remote subagents, eve also requests cancellation of every adopted child, recursively, before settling the parent. Each child reports its own cancellation boundary on its child-session stream; the parent does not emit `subagent.completed` for cancelled work. `"no_active_turn"` means no resumable cancellation target exists, including an unknown session or an already-settled turn. Both statuses are success, so clients can fire and forget. See the [eve channel](../channels/eve) for the full route contract.
172
+ `"accepted"` means the live session durably queued the request. Confirm an actual cancellation on the stream as `turn.cancelled` followed by `session.waiting`; the session then accepts the next message normally. If the turn is waiting on active local or remote subagents, eve also requests cancellation of every adopted child, recursively, before settling the parent. Each child reports its own cancellation boundary on its child-session stream; the parent does not emit `subagent.completed` for cancelled work. A live but already-parked session also returns `"accepted"` and consumes the command as a no-op. `"no_active_turn"` means the session or channel address is unknown or terminal. Both statuses are success, so clients can fire and forget. See the [eve channel](../channels/eve) for the full route contract.
173
+
174
+ The HTTP route returns `202` for `"accepted"` and `200` for
175
+ `"no_active_turn"`. Only the accepted result includes `sessionId`.
176
+
177
+ Custom channel routes request the same cancellation through
178
+ `from(address).cancel()` or `attachSession(sessionId).cancel()`. See
179
+ [custom channels](../channels/custom#channel-operations-and-session-handles).
180
+
181
+ ## Compact, clear, and reset
182
+
183
+ All session controls are ID-addressed and accept no continuation token:
184
+
185
+ ```bash
186
+ curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId>/compact
187
+ curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId>/clear
188
+ curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId>/reset \
189
+ -H 'content-type: application/json' \
190
+ -d '{"reason":"Start over"}'
191
+ ```
170
192
 
171
- Custom channel routes request the same cancellation without knowing the session id: the `cancel` route helper is addressed by the channel-local continuation token, and `Session.cancel()` by session id. See [custom channels](../channels/custom#cancel-a-turn).
193
+ Compaction summarizes context, clear removes model-message history in place,
194
+ and reset terminally retires the session. A reset ID never becomes a new session;
195
+ create another session explicitly for a fresh conversation.
172
196
 
173
197
  ## Reconnect and rewind
174
198
 
@@ -186,7 +210,9 @@ A negative `startIndex` reads relative to the stream's current tail. For example
186
210
  curl "http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream?startIndex=-1"
187
211
  ```
188
212
 
189
- This gives a consumer that only persisted `sessionId` a lightweight way to recover the current `continuationToken`. Because a tail-relative position does not resolve to an absolute consumed-event count, client tail reads do not automatically reconnect or advance the stored cursor.
213
+ Because a tail-relative position does not resolve to an absolute consumed-event
214
+ count, client tail reads do not automatically reconnect or advance the stored
215
+ cursor.
190
216
 
191
217
  For a catch-up read that stops instead of following the live stream, pass `includeTailIndex=1`. The response then carries the `x-eve-stream-tail-index` header: the zero-based index of the last durably recorded event, or `-1` before the first. Read from your cursor until it passes that tail, then disconnect — reconnecting from the updated cursor if the connection drops first:
192
218
 
@@ -201,7 +227,7 @@ The lookup is opt-in; requests without the parameter get no header. The TypeScri
201
227
 
202
228
  For scripts, server-to-server calls, tests, evals, and custom UIs, `eve/client` wraps these routes in a typed client so you don't hand-roll the POST and NDJSON stream loop.
203
229
 
204
- Start with the [TypeScript SDK](../guides/client/overview) guide. It covers basic usage, sending messages, continuations, streaming, and per-turn `outputSchema` results.
230
+ Start with the [TypeScript SDK](../guides/client/overview) guide. It covers basic usage, sending messages, session state, streaming, and per-turn `outputSchema` results.
205
231
 
206
232
  ## Inspect the agent over HTTP
207
233
 
@@ -227,6 +253,6 @@ The order is structural, not incidental. By the time a resolver or hook reads ch
227
253
  ## What to read next
228
254
 
229
255
  - [Execution model & durability](./execution-model-and-durability): what makes a session durable and how parked work resumes.
230
- - [Channels](../channels/overview): what owns the continuation token and delivery.
256
+ - [Channels](../channels/overview): how platform addresses map to durable sessions.
231
257
  - [TypeScript SDK](../guides/client/overview): call these routes from scripts and server-side code.
232
258
  - [Frontend](../guides/frontend/overview): `useEveAgent` instead of raw routes.
@@ -101,12 +101,12 @@ export default defineEval({
101
101
 
102
102
  `t` drives the primary session; `t.newSession()` returns an independent `EveEvalSession` against the same target, whose events feed the same run-level assertions.
103
103
 
104
- - `t.send(input)` sends a turn and waits for it to settle. It accepts the same input as `ClientSession.send()` (a string or a structured message) and resolves to a turn carrying `.message` and `.expectOk()`.
105
- - `t.start(input)` starts a turn but returns as soon as the server accepts it. The returned live turn exposes `.sessionId`, `.waitForEvent(...)`, `.cancel()`, and `.result()` for coordinating with work that is still running.
104
+ - `t.send(message, options?)` sends a turn and waits for it to settle. It matches `ClientSession.send()` and resolves to a turn carrying `.message` and `.expectOk()`.
105
+ - `t.start(message)` starts a text turn but returns as soon as the server accepts it. The returned live turn exposes `.sessionId`, `.waitForEvent(...)`, `.cancel()`, and `.result()` for coordinating with work that is still running.
106
106
  - `t.cancel()` requests cooperative cancellation of the primary session's active turn. Both `accepted` and `no_active_turn` are successful outcomes.
107
107
  - `t.sendFile(text, path, mediaType?)` attaches a local file as a data URL.
108
108
  - `t.requireInputRequest(filter?)` records a gate, requires exactly one pending request, and returns it. Filters match tool name, action input, prompt, display, and option ids.
109
- - `t.respond(...responses)` answers specific pending input requests and sends them as the next turn.
109
+ - `t.respond(responses, options?)` answers specific pending input requests and sends them as the next turn.
110
110
  - `t.respondAll(optionId)` answers every pending input request with the same option and sends the responses as the next turn.
111
111
  - `t.reply` is the last assistant message (or `null`); `t.sessionId` is the current session id; `t.events` is the full typed event stream captured so far.
112
112
 
@@ -24,7 +24,7 @@ export default defineEval({
24
24
 
25
25
  - `t.target.fetch(path, init)` performs an authenticated fetch against the target, useful for channel and webhook ingress. See [Authentication](#authentication) for how the runner authenticates.
26
26
  - `t.target.dispatchSchedule(id)` triggers a [schedule](../schedules) through the dev-only schedule route and returns the session ids it created. It works only against a target with dev routes enabled (the local `eve eval` dev server, or a deployment running in development mode), and throws otherwise.
27
- - `t.target.attachSession(sessionId, { startIndex? })` consumes one turn from a session created outside the eval, by a channel or a schedule, so its events feed the run-level assertions. `startIndex` skips events before that position, so a session already partway through its stream resumes from where you left off rather than replaying from the start. When the consumed turn parks (`session.waiting`), the attached session recovers the current continuation token from the stream, so `session.send(...)` and `session.respond(...)` continue the same durable session.
27
+ - `t.target.attachSession(sessionId, { startIndex? })` consumes one turn from a session created outside the eval, by a channel or a schedule, so its events feed the run-level assertions. `startIndex` skips events before that position, so a session already partway through its stream resumes from where you left off rather than replaying from the start. The attached session stays bound to that exact ID, so `session.send(...)` and `session.respond(...)` continue it after the turn parks (`session.waiting`).
28
28
  - `t.target.watchTurn(sessionId, { startIndex? })` starts consuming an externally-created turn immediately and returns a live-turn handle. Use `waitForEvent(...)` to coordinate with mid-turn work, `cancel()` to request cancellation, and `result()` to consume through the boundary. The live turn's `.session` is the attached `EveEvalSession` for follow-up sends after settlement.
29
29
 
30
30
  Sessions attached this way are full `EveEvalSession`s: you can keep driving them and assert directly on that session (`session.succeeded()`, `session.calledTool(...)`). Aggregate assertions on `t` continue to read the whole run, including every attached session.
@@ -16,6 +16,7 @@ The route-auth policy lives on the HTTP channel factory (`agent/channels/eve.ts`
16
16
 
17
17
  - `POST /eve/v1/session`
18
18
  - `POST /eve/v1/session/:sessionId`
19
+ - `POST /eve/v1/session/:sessionId/{cancel,compact,clear,reset}`
19
20
  - `GET /eve/v1/session/:sessionId/stream`
20
21
 
21
22
  These routes are protected by the channel's auth policy. eve fails closed by default: production traffic is rejected unless you configure an authenticator that accepts it, and anonymous access requires an explicit `none()`.
@@ -181,7 +182,7 @@ import { createUnauthorizedResponse } from "eve/channels/auth";
181
182
 
182
183
  export default defineChannel({
183
184
  routes: [
184
- POST("/message", async (req, { send }) => {
185
+ POST("/message", async (req) => {
185
186
  if (!isAllowed(req)) {
186
187
  return createUnauthorizedResponse({
187
188
  status: 403, // defaults to 401; code defaults to "forbidden" / "unauthorized"
@@ -1,137 +1,110 @@
1
1
  ---
2
- title: "Continuations"
3
- description: "Persist and resume eve client sessions with continuation tokens, session IDs, and stream cursors."
2
+ title: "Session State"
3
+ description: "Persist and resume eve client sessions with durable session IDs and stream cursors."
4
4
  ---
5
5
 
6
- Every eve client turn returns two handles, and mixing them up is a common mistake. The TypeScript client tracks both for you:
7
-
8
- - `continuationToken`: the resume handle. Use it to send the next user turn.
9
- - `sessionId`: the stream-and-inspect handle. Use it to attach to event history.
10
-
11
- `ClientSession` also tracks `streamIndex`, the count of events already consumed. Together these three fields make a `SessionState`.
6
+ The TypeScript client uses one durable session ID for messages, controls, and
7
+ streams. A `ClientSession` adds a local `streamIndex` cursor so reconnects do not
8
+ replay events already consumed by that handle.
12
9
 
13
10
  ## Read and persist state
14
11
 
15
- After a streamed turn finishes, read `session.state`:
12
+ Create the session with its first turn, then persist `session.state` after
13
+ consuming the response:
16
14
 
17
15
  ```ts
18
- const session = client.session();
16
+ const { session, response } = await client.sessions.create({
17
+ message: "Create a launch checklist.",
18
+ });
19
19
 
20
- const response = await session.send("Create a launch checklist.");
21
20
  await response.result();
22
-
23
21
  await saveSessionState(session.state);
24
22
  ```
25
23
 
26
- As soon as `send()` returns, `session.state.sessionId` identifies the accepted in-flight turn for cancellation or stream reconnection. Consume the response before saving a fully advanced cursor: the continuation token and `streamIndex` update as events reach the turn boundary.
27
-
28
- Store the full state object:
24
+ The state is deliberately small:
29
25
 
30
26
  ```ts
31
- interface SessionState {
32
- continuationToken?: string;
33
- sessionId?: string;
27
+ interface ClientSessionState {
28
+ sessionId: string;
34
29
  streamIndex: number;
35
30
  }
36
31
  ```
37
32
 
38
- The continuation token resumes the conversation. The session ID and stream index let the client reconnect to the right stream position without replaying events it already consumed.
39
-
40
- `SessionState` is a cursor, not a chat transcript. If your app shows historical messages, persist the stream events separately and store them under your own chat or thread ID. On reload, pass the saved `SessionState` back to `client.session(savedState)` or `useEveAgent({ initialSession })`, and pass the saved events to your renderer or `useEveAgent({ initialEvents })`.
33
+ It is a remote stream cursor, not a transcript. Persist events separately when
34
+ your application renders chat history.
41
35
 
42
36
  ## Resume a saved session
43
37
 
44
- Pass the saved state back into `client.session()`:
38
+ Attach a fixed handle to the saved ID and restore its cursor:
45
39
 
46
40
  ```ts
47
- import type { SessionState } from "eve/client";
41
+ import type { ClientSessionState } from "eve/client";
48
42
 
49
- const saved = (await loadSessionState()) as SessionState;
50
- const session = client.session(saved);
43
+ const saved = (await loadSessionState()) as ClientSessionState;
44
+ const session = client.sessions.attach(saved.sessionId, {
45
+ streamIndex: saved.streamIndex,
46
+ });
51
47
 
52
48
  const response = await session.send("Now shorten it.");
53
- const result = await response.result();
54
- console.log(result.message);
55
- ```
56
-
57
- If all you have is a continuation token, pass it as shorthand:
58
-
59
- ```ts
60
- const session = client.session(continuationToken);
61
- const response = await session.send("Continue where we left off.");
62
- await response.result();
49
+ console.log((await response.result()).message);
63
50
  ```
64
51
 
65
- The shorthand can send a follow-up, but it doesn't know the previous stream cursor. Prefer full `SessionState` when you control persistence.
66
-
67
- ## Waiting, completed, and failed sessions
52
+ `attach()` performs no request. Every later operation targets exactly
53
+ `saved.sessionId`; it never follows or creates a replacement.
68
54
 
69
- When a turn ends with `session.waiting`, the event carries the current channel-owned `data.continuationToken` and the client preserves the state so the next send continues the conversation.
55
+ ## Waiting, completed, and reset sessions
70
56
 
71
- When a turn ends with `session.completed` or `session.failed`, the client resets its local state. The next send starts a fresh durable session:
57
+ A session that emits `session.waiting` accepts another message through the same
58
+ handle. A terminal or reset session does not. Start a fresh conversation
59
+ explicitly with another `client.sessions.create(firstTurn)` call.
72
60
 
73
61
  ```ts
74
- const response = await session.send("Do this one-shot task.");
75
- const result = await response.result();
76
-
77
- if (result.status === "completed") {
78
- // session.state is now a fresh cursor: { streamIndex: 0 }
79
- }
62
+ const reset = await session.reset({ reason: "Start over" });
63
+ const { session: fresh, response } = await client.sessions.create({ message: "Begin again." });
80
64
  ```
81
65
 
82
- This matches the runtime contract, where only waiting sessions can accept the next user input.
66
+ The old `session` remains pinned to its retired ID; `fresh` owns a different ID.
83
67
 
84
68
  ## Multiple sessions
85
69
 
86
- Create a separate `ClientSession` per conversation:
70
+ Create one handle per conversation:
87
71
 
88
72
  ```ts
89
- const research = client.session();
90
- const support = client.session();
91
-
92
- const researchResponse = await research.send("Research competitors.");
93
- await researchResponse.result();
94
-
95
- const supportResponse = await support.send("Draft a support reply.");
96
- await supportResponse.result();
97
-
73
+ const { session: research, response: researchResponse } = await client.sessions.create({
74
+ message: "Research competitors.",
75
+ });
76
+ const { session: support, response: supportResponse } = await client.sessions.create({
77
+ message: "Draft a support reply.",
78
+ });
79
+
80
+ await Promise.all([researchResponse.result(), supportResponse.result()]);
98
81
  await save("research", research.state);
99
82
  await save("support", support.state);
100
83
  ```
101
84
 
102
- The shared `Client` only owns host, auth, headers, and reconnect settings. Conversation state lives on each `ClientSession`.
103
-
104
- For a multi-chat UI, this usually means one application row per chat with two eve fields: the latest `SessionState` and the ordered event log for rendering. Do not reuse one `ClientSession` across sidebar conversations, and do not treat `streamIndex` as your database row index; it is only the remote stream cursor for that eve session.
85
+ The shared `Client` owns host, auth, headers, and redirect policy. Each
86
+ `ClientSession` owns its fixed ID and stream cursor.
105
87
 
106
88
  ## Reconnect an existing stream
107
89
 
108
- When a session already has a `sessionId`, `session.stream()` reattaches to its stream from the saved cursor. Resuming a saved `SessionState` after a restart is the common reason to do this:
90
+ `stream()` starts from the handle's saved cursor and advances it as events are
91
+ read:
109
92
 
110
93
  ```ts
111
- const session = client.session(savedState);
94
+ const session = client.sessions.attach(saved.sessionId, {
95
+ streamIndex: saved.streamIndex,
96
+ });
112
97
 
113
98
  for await (const event of session.stream()) {
114
99
  console.log(event.type);
115
100
  }
116
101
  ```
117
102
 
118
- `stream()` attaches to an existing run; to send new user input, use `send()`. For overriding the cursor with `startIndex` and the full reconnection model, see [Streaming](./streaming#open-a-stream-manually).
119
-
120
- If you only persisted `sessionId`, read the latest durable event to recover the current token from a waiting session:
121
-
122
- ```ts
123
- const session = client.session({ sessionId, streamIndex: 0 });
124
-
125
- for await (const event of session.stream({ startIndex: -1 })) {
126
- if (event.type === "session.waiting") {
127
- await saveContinuationToken(event.data.continuationToken);
128
- }
129
- break;
130
- }
131
- ```
103
+ Use `send()` for new input. For explicit cursors, tail-relative reads, and
104
+ bounded catch-up, see [Streaming](./streaming#open-a-stream-manually).
132
105
 
133
106
  ## What to read next
134
107
 
135
108
  - [Streaming](./streaming): stream events and reconnect by index
136
109
  - [Sessions, runs & streaming](../../concepts/sessions-runs-and-streaming): the raw HTTP contract
137
- - [eve channel](../../channels/eve): where continuation tokens come from
110
+ - [eve channel](../../channels/eve): the ID-addressed routes