Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eve-eve-087.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@upstash/agentkit-eve": minor
---

Migrate to eve 0.87 and raise the `eve` peer floor to `>=0.87.0`. `redisMemory()` no longer registers the `compaction.completed` recall hook that eve 0.84 removed, which made `eve build` reject the whole slot ("provider.recall Unknown key compaction.completed"); `save_memory` now takes its ordering sequence from the turn that recalled, since eve's memory tools context no longer carries `turn`. `UpstashSandbox` implements eve's connection-based sandbox provider contract (`prepare` via `ctx.populate`, a byte-level `connection`, `onSessionEnd`): `start` is now an idempotent upsert keyed on the session id, `resume` throws eve's `SandboxSessionGoneError` when the box is gone, and a writable `stdin` is rejected. Apps on eve 0.84–0.86 should upgrade eve (their `./sandbox` and `./memory` entries were already broken on those versions).
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ implements TanStack AI's own backend contracts (see its section below) — keep
- **eve 0.83.3 + ai 7.0.154 (2026-10-27): extension + `examples/eve-extension-demo` move to eve `^0.83.3`, ai is `7.0.154` everywhere (`@ai-sdk/react ^4.0.158`).** Same contracts as 0.83.0, peer floor unchanged. **eve 0.84.0 (2026-10-26) is NOT applied: it breaks `packages/eve-extension` and `packages/eve` sources** (needs a source migration). Published `@upstash/agentkit-eve-extension@0.16.1` fails `eve build` on eve 0.84.0 with "uses an old epoch-based distribution manifest … Rebuild the extension with this eve release". Extension typecheck on 0.84.0: `SessionContext` no longer exported from `eve/tools`; hook key `message.received` gone; tool defs no longer accept `events`. `packages/eve` on 0.84.0: `signal` removed from `MemoryDocumentReadInput`/`WriteInput`, `MemoryCompactionCompletedContext` and the `compaction.completed` recall hook gone, `MemoryToolsContext.turn` gone, plus the sandbox typings above. TanStack AI 1.0.0 still not applied (see above); ai 7.0.154 is clean (published `@upstash/agentkit-ai-sdk@0.15.1` typechecks and imports on it).
- **eve 0.85.0: the extension IS migrated (extension-only; `packages/eve` + `examples/eve-demo` stay on eve 0.82.0).** eve 0.84.0 replaced per-capability contract epochs with a `{formatVersion: 3, builtWithEve, contractMajor: 0}` manifest and a v27 session-event/dynamic API. Source changes in `packages/eve-extension`: `SessionContext` is gone from `eve/tools` (own `SessionRef = Pick<SessionFrame, "session">` in `extension/lib/session.ts`, used for the `userId` function and `resolveUserId`); dynamic tools use `defineDynamic(ctx => tool | null)` instead of `defineDynamic({events: {"session.started"}})`; the chat-history hook maps `message.received` -> `delivery.consumed` (text parts of `event.data.parts`) and `message.completed` -> `content.completed` filtered to `kind: "text"` + `phase: "reply"` (one stored assistant message per reply text part; narration is not stored). **The peer floor is `>=0.85.0`**: verified by packing the dist (built with 0.85.0) into consumers on 0.83.3 ("no valid compatibility manifest"), 0.84.0 and 0.84.1 ("was built with eve 0.85.0. Upgrade eve to 0.85.0 or later") and 0.85.0 (`eve build` clean). Any rebuild on a newer eve will raise the floor again to that eve.
- **ai 7.0.155 (2026-10-28) applied everywhere except `packages/eve` + `examples/eve-demo` (stay on 7.0.154; `@ai-sdk/react ^4.0.159`); with 7.0.155 `packages/eve` tsc fails at `src/sandbox.ts:340` (spawn return type vs eve 0.82.0 overloads) and eve-demo `next build` fails; eve 0.84.1 / 0.85.0 NOT applied.** Same state as 0.84.0: extension typecheck on 0.85.0 still fails (`SessionContext` missing from `eve/tools`, hook key `message.received` gone, `events` rejected on tool defs) and published `@upstash/agentkit-eve-extension@0.16.1` still fails `eve build` on eve 0.85.0 ("uses an old epoch-based distribution manifest"); needs a source migration. Published `@upstash/agentkit-ai-sdk@0.15.1` typechecks (`skipLibCheck: false`, with `@types/json-schema` installed, which `@ai-sdk/provider` omits) and imports on ai 7.0.155. TanStack AI 1.0.0 still not applied.
- **eve 0.87.0 + ai 7.0.156 (2026-10-29): `packages/eve` + `examples/eve-demo` are migrated and back in step with the rest of the repo on eve `^0.87.0` / ai 7.0.156 (the eve 0.82.0 hold above is over); the extension stays on eve `^0.85.0`.** Source changes in `packages/eve`: `redisMemory()` registers only `turn.started` recall (eve 0.84 removed `compaction.completed`; registering it made `eve build` fail with "provider.recall Unknown key compaction.completed"), and because eve 0.84+ gives a memory *tool* no `turn`, `turn.started` stores the turn sequence at `<replayCachePrefix>:sequence:<scope>:<session>` for `save_memory` to order its record by (`read_session` sorts on it; without a prior recall it falls back to 0). `UpstashSandbox` follows the connection-based provider contract: `prepare` returns `{snapshotId: null}` when `ctx.isEmpty`, otherwise creates a temp box, `ctx.populate(connection)` (eve strips `prepare` from the options and runs it there), snapshots (named from `ctx.templateKey`) and deletes the box; `start` is an idempotent upsert on the box name `eve-<sha256(session.id)[:16]>` (suffix fallback if a deleted record holds the name); `resume` throws `SandboxSessionGoneError`; `onSessionEnd` deletes by recorded `boxId`, or by the derived name when state is null, and is a no-op when nothing exists; `connection.stop()` is Box `pause`; `setNetworkPolicy` is a connection extension; a writable `stdin` spawn throws. **The peer floor is `>=0.87.0`**: the source typechecks with 0 errors only on 0.87.0 (0.83.x fails on `signal`/`stdin`, 0.84.0–0.86.0 on `signal` in sandbox and `MemoryDocumentReadInput`/`WriteInput`, 0.82.0 and older on the old provider handle API). `examples/eve-demo` also needed: `turn.reply` instead of `turn.message` in `evals/memory.eval.ts`; `useEveAgent` now returns `messages`/`status: idle|busy|resuming` with `tool` parts (was `data.messages`, `dynamic-tool`); `@vercel/connect` 2.5.6 (eve 0.87 peer) and `next` 16.2.9 so `packages/eve` and the demo resolve the **same** eve instance (two peer variants of eve make `MemoryProvider` types nominally incompatible). **Not verified live:** the `UpstashSandbox` live-Box suite (needs `UPSTASH_BOX_API_KEY`) was not run; the offline suite and the eve-demo mocked eval (which reaches `Box.create` from `prepare`) were. The extension was not moved to eve 0.87: a dist built on 0.87.0 is refused by eve 0.85/0.86, so adopting it forces its peer floor to `>=0.87.0` for no contract change. TanStack AI 1.0.0 still not applied (test-only typecheck/runtime breaks; the published `@upstash/agentkit-tanstack-ai@0.2.0` typechecks and imports on 1.0.0, the memory contract passes, and the demo e2e passes).
- **The 0.74.0 → 0.82.0 bump (2026-10-22) moves everything, with a source migration in `packages/eve`.** eve 0.75.0 renamed the sandbox provider handle hooks `onSessionStop`/`onSessionDelete` → `onSandboxStop`/`onSandboxDelete`, and eve 0.66.0 had removed `MutableNetworkSandboxSession` (network policy became provider-specific). `packages/eve/src/sandbox.ts` now declares its own `UpstashSandboxSession = SandboxSession & { setNetworkPolicy }` (exported; what `ctx.getSandbox(environment)` returns) and uses the new hook names, so the `eve` peer floor moved `>=0.65.0` → **`>=0.75.0`** (floor read off the 0.74.0 vs 0.75.0 tarballs: `onSessionStop` present vs absent in `shared/sandbox-provider.d.ts`; not verified by a full `tsc` against 0.75.0). `packages/eve` + `examples/eve-demo` are on eve ^0.82.0. The extension rebuild re-stamps **tool 78→96, dynamicTool 73→88, hook 39→48**; only 0.82.0 accepts them (0.75.0–0.81.1 do not), floor `>=0.74.0` → **`>=0.82.0`**. `upstashPersistence` gained an `activities` store (`ActivityStore`, one JSON doc per thread at `<prefix>:activities:<threadId>`, same TTL as messages) because `@tanstack/ai-persistence` ≥0.8.0's conformance suite requires it, so ai-persistence moves 0.7.2 → 0.10.1 and the peer floors become `@tanstack/ai` `>=0.65.0` (`ActivityRecord` type), `@tanstack/ai-persistence` `>=0.8.0` (`ActivityStore` type). ai 7.0.148, @tanstack/ai 0.71.0, -memory 0.2.16 and the demo packages are bumped.
- **The 0.72.1 → 0.74.0 bump (2026-10-08) is EXTENSION-ONLY again** (same reason: `packages/eve` still imports the removed
`MutableNetworkSandboxSession`, `TS2305` in `src/sandbox.ts`/`sandbox.test.ts` on 0.74.0; `packages/eve` + `examples/eve-demo` stay on 0.65.0). The rebuild re-stamps
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ are powered by [Upstash Redis Search](https://upstash.com/docs/redis/search/intr
documents (so they work off Vercel), and `redisMemory()` is a full provider with ranked recall and
automatic capture.
- **Code sandbox** (Eve only) — an [Upstash Box](https://github.com/upstash/box) sandbox provider for
Eve's `defineSandbox` (`UpstashSandbox`, eve ≥ 0.65).
Eve's `defineSandbox` (`UpstashSandbox`, eve ≥ 0.87).
- **Tool-call cache** — memoize deterministic tool results keyed by arguments.
- **Durable MCP tasks and events** (`@upstash/mcp-toolkit`) — a long-running MCP tool returns a task
instead of blocking: a native MCP task the client polls (Claude Code), or a task id the model
Expand Down
4 changes: 4 additions & 0 deletions docs/memory-redesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ knows its own. Neither needs to know how many records the other wrote.

## Lifecycle

> **Update (eve 0.84):** eve removed the `compaction.requested` / `compaction.completed` hooks, so the
> provider now registers only `turn.started` (recall) and `turn.completed` (capture). The rows below
> describe the original design.

| eve phase | what happens |
| --- | --- |
| `turn.started` | recall — one `$smart` query filtered to `source:agent, deleted:false`, plus a `count` of this scope's non-agent records |
Expand Down
4 changes: 2 additions & 2 deletions examples/ai-sdk-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
},
"dependencies": {
"@ai-sdk/openai": "^4.0.71",
"@ai-sdk/react": "^4.0.159",
"@ai-sdk/react": "^4.0.160",
"@upstash/agentkit-ai-sdk": "workspace:*",
"@upstash/agentkit-sdk": "workspace:*",
"@upstash/redis": "^1.38.4",
"ai": "7.0.155",
"ai": "7.0.156",
"dotenv": "^16.4.5",
"next": "16.2.9",
"react": "19.2.6",
Expand Down
20 changes: 10 additions & 10 deletions examples/eve-demo/app/_components/agent-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function AgentChat({
// Send the selected user as a header so the agent scopes memory + cached tools to this user. The
// parent remounts this component (via a `key`) when the user changes, so a fresh session starts.
const agent = useEveAgent({ headers: { "x-user-id": userId } });
const busy = agent.status === "submitted" || agent.status === "streaming";
const busy = agent.status !== "idle";

function send(text: string) {
const value = text.trim();
Expand Down Expand Up @@ -66,31 +66,31 @@ export function AgentChat({
) : null}

<div className="messages">
{agent.data.messages.length === 0 && (
{agent.messages.length === 0 && (
<p className="muted">
An eve agent backed by <code>@upstash/agentkit-eve</code>. It has memory tools
(<code>recall_memory</code> / <code>save_memory</code>), schema-driven Redis Search tools
over a books index (<code>search_books</code> / <code>aggregate_books</code> /{" "}
An eve agent backed by <code>@upstash/agentkit-eve</code>. It has memory tools (
<code>recall_memory</code> / <code>save_memory</code>), schema-driven Redis Search
tools over a books index (<code>search_books</code> / <code>aggregate_books</code> /{" "}
<code>count_books</code>), and a cached <code>get_weather</code> tool; requests are
rate-limited at the channel.
</p>
)}
{agent.data.messages.map((m) => {
{agent.messages.map((m) => {
const text = m.parts.map((part) => (part.type === "text" ? part.text : "")).join("");
return (
<div className="card" key={m.id}>
<div className="muted">{m.role === "user" ? "You" : "Assistant"}</div>
{/* Show every tool the agent called, with its state + result. */}
{m.parts.map((part, i) =>
part.type === "dynamic-tool" ? (
part.type === "tool" ? (
<div className="tool-call" key={i}>
<span className="tool-tag">🔧 {part.toolName}</span>
<span className="tool-tag">🔧 {part.name}</span>
<span className="muted"> · {part.state}</span>
{part.output != null ? (
<pre className="tool-io">{JSON.stringify(part.output, null, 2)}</pre>
) : null}
{part.errorText ? (
<pre className="tool-io tool-err">{part.errorText}</pre>
{part.error ? (
<pre className="tool-io tool-err">{part.error.message}</pre>
) : null}
</div>
) : null,
Expand Down
6 changes: 3 additions & 3 deletions examples/eve-demo/evals/memory.eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default defineEval({
let recalled = "";
for (let attempt = 0; attempt < 4; attempt += 1) {
const turn = await session.send("What colour do I like?");
recalled = turn.message ?? "";
recalled = turn.reply ?? "";
if (recalled.includes(NONCE)) break;
await new Promise((resolve) => setTimeout(resolve, 1_000));
}
Expand All @@ -87,7 +87,7 @@ export default defineEval({

// 5. The saved document comes back in the next turn's recalled context.
const recap = await session.send("Anything else you know?");
t.check(recap.message, includes("Persistent memories for profile"));
t.check(recap.message, includes("deploy target is Vercel"));
t.check(recap.reply, includes("Persistent memories for profile"));
t.check(recap.reply, includes("deploy target is Vercel"));
},
});
8 changes: 4 additions & 4 deletions examples/eve-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
"@upstash/agentkit-eve": "workspace:*",
"@upstash/box": "^0.7.5",
"@upstash/redis": "^1.38.4",
"@vercel/connect": "0.2.2",
"ai": "7.0.154",
"@vercel/connect": "2.5.6",
"ai": "7.0.156",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"eve": "^0.82.0",
"eve": "^0.87.0",
"lucide-react": "1.16.0",
"motion": "12.40.0",
"nanoid": "5.1.11",
"next": "16.2.6",
"next": "16.2.9",
"radix-ui": "1.4.3",
"react": "19.2.6",
"react-dom": "19.2.6",
Expand Down
2 changes: 1 addition & 1 deletion examples/eve-extension-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@upstash/agentkit-eve-extension": "workspace:*",
"@upstash/redis": "^1.38.4",
"@vercel/connect": "2.5.6",
"ai": "7.0.155",
"ai": "7.0.156",
"eve": "^0.85.0",
"zod": "4.4.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ai-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@ai-sdk/openai": "^4.0.71",
"@ai-sdk/provider": "^4.0.17",
"@upstash/redis": "^1.38.4",
"ai": "7.0.155",
"ai": "7.0.156",
"dotenv": "^16.4.5"
},
"peerDependencies": {
Expand Down
22 changes: 10 additions & 12 deletions packages/eve/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,23 +123,21 @@ Neither replaces the [memory tools](#memory-tools) above: those need no memory s
version, and stay the right choice for purely model-driven memory.

<details>
<summary>When each hook runs (the four lifecycle points)</summary>
<summary>When each hook runs (the two lifecycle points)</summary>

eve drives a memory slot at four points. Both integrations recall at the same two; only
eve 0.84+ drives a memory provider at two points. Both integrations recall at `turn.started`; only
`redisMemory()` writes.

| eve phase | `fileMemory({ backend: redisDocuments() })` | `redisMemory()` |
| --- | --- | --- |
| `turn.started` | read the document, inject it whole | BM25 `$smart` recall for the turn's user text → one keyed message, injected **before** the model runs |
| `turn.completed` | — | write this turn's messages (`rememberMessages`), then wait for indexing |
| `compaction.requested` | — | nothing — messages are stored as they happen, so the summarizer takes nothing with it |
| `compaction.completed` | read and inject against the new checkpoint | recall again against the new checkpoint |

Two consequences worth knowing. Capture runs **after** the response is delivered, which is why
One consequence worth knowing: capture runs **after** the response is delivered, which is why
blocking on Redis Search's `waitIndexing()` there costs the caller nothing and makes what you just
said recallable on the very next turn. And recall runs a second time at `compaction.completed` so
memory is re-injected against the fresh checkpoint rather than being folded into the summary — eve
excludes recalled records from the summarizer for the same reason.
said recallable on the very next turn. (Older eve versions also had `compaction.requested` /
`compaction.completed` hooks; eve 0.84 removed them, and with every turn's messages stored as they
happen there is nothing to rescue before the summarizer runs.)

Recall is also cached per eve `operationId` (1h). eve requires providers to treat that id as an
idempotency key — *"replaying a recall with a different result is an error"* — and a live ranked
Expand Down Expand Up @@ -229,9 +227,9 @@ conversation changes topic.
`scope.key` that is used as the storage partition. Derive it from verified session auth, never from
model input — `byPrincipal` from `eve/memory/scope` is the built-in shorthand.

**Requires eve ≥ 0.45.2** (`eve/memory` landed in 0.45.1, `eve/memory/file` in 0.45.2). The package
as a whole requires **eve ≥ 0.65** (its `eve` peer range), because the `./sandbox` provider targets the
eve 0.64+ provider API.
**Requires eve ≥ 0.87.0** (the package's `eve` peer range): the memory providers and the `./sandbox`
provider target eve 0.87's APIs (`ctx.populate`, `onSessionEnd`, `signal` on sandbox and memory
inputs), and the typings only match from that release.

</details>

Expand Down Expand Up @@ -344,7 +342,7 @@ export const environment = UpstashSandbox.environment({
export default defineSandbox(() => environment.open());
```

Requires **eve ≥ 0.65** and **`@upstash/box` ≥ 0.7.1** (an optional peer, only needed when you import
Requires **eve ≥ 0.87.0** and **`@upstash/box` ≥ 0.7.1** (an optional peer, only needed when you import
`@upstash/agentkit-eve/sandbox`). The provider reads `UPSTASH_BOX_API_KEY` when eve prepares the
environment (at `eve build`) and at run time.

Expand Down
6 changes: 3 additions & 3 deletions packages/eve/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
"devDependencies": {
"@upstash/box": "^0.7.5",
"@upstash/redis": "^1.38.4",
"ai": "7.0.154",
"ai": "7.0.156",
"dotenv": "^16.4.5",
"eve": "^0.82.0"
"eve": "^0.87.0"
},
"peerDependencies": {
"@upstash/box": ">=0.7.1",
"@upstash/redis": ">=1.38.4",
"eve": ">=0.75.0"
"eve": ">=0.87.0"
},
"peerDependenciesMeta": {
"@upstash/box": {
Expand Down
Loading
Loading