> ## Documentation Index
> Fetch the complete documentation index at: https://upstash-dx-2885.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework

> Add long-term memory, searchable chat history, RAG, rate limiting, tool caching, and sandboxes to Vercel's Eve agent framework with Upstash Redis — no separate vector database.

[Upstash AgentKit](https://github.com/upstash/agentkit) builds AI agents on Upstash Redis: memory,
conversation history, caching, and RAG, with no separate vector database. The semantic features run on
[Upstash Redis Search](/redis/search/introduction) and its `$smart` fuzzy operator.

Two packages bring AgentKit to **Eve, the Vercel agent framework**. They work together in one agent:

| Package                           | What it is                                                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `@upstash/agentkit-eve-extension` | An [Eve extension](https://eve.dev/docs/extensions): one mount file adds memory, searchable chat history, and RAG. |
| `@upstash/agentkit-eve`           | Per-file building blocks, plus the two things an extension cannot contribute: rate limiting and a sandbox backend. |

Mount the extension for the bundled setup. Add the package when you need a rate-limit gate, an Upstash
Box sandbox, or control over an individual tool file.

Start from an eve project (0.25.2 or later). Scaffold one, which installs `eve` and an AI-SDK provider
for you:

```bash theme={null}
npx eve@latest init my-agent
# or, to start with a Next.js app:
npx eve@latest init my-agent --channel-web-nextjs
```

<Note>
  AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
  Pass your own `@upstash/redis` client as `redis` to any helper to override.
</Note>

## The Eve extension: memory, chat history, and RAG

Everything in this part comes from `@upstash/agentkit-eve-extension`, configured in one mount file.

### How to mount the Upstash extension in Vercel Eve

```bash theme={null}
npm install @upstash/agentkit-eve-extension
```

One file under `agent/extensions/` mounts everything. Every config field is optional, and the smallest
mount gives the model long-term memory:

```ts theme={null}
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit();
```

The filename supplies the namespace, so contributions compose as `agentkit__recall_memory`,
`agentkit__save_memory`, and so on. The extension also merges a short instructions fragment into your
system prompt telling the model when to save and recall.

The three feature groups below are independent of each other. Memory holds facts the model decided to
keep. Chat history is the transcript of what was said. Search is retrieval over documents you write
into your own index. Each has its own Redis keyspace and its own search index.

### How to add memory to Vercel Eve

Long-term memory the model reads and writes itself. A bare mount already has it; the `memory` field
only tunes recall:

```ts theme={null}
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  memory: { topK: 5, minScore: 1 },
});
```

<Accordion title="Tools, options, and the userId tenant boundary">
  * `agentkit__recall_memory` — searches the user's memories; called with no `query` it returns all of them.
  * `agentkit__save_memory` — stores one durable fact about the user.
  * `memory.topK` — max memories a recall returns.
  * `memory.minScore` — relevance floor. Scores are unbounded BM25 values, not `[0,1]`.

  `userId` is the only tenant boundary. It defaults to Eve's verified session auth
  (`auth.current?.principalId`, then `auth.initiator?.principalId`, then the session id), so
  configure a real authenticator (`vercelOidc()`, an OIDC/JWT provider like Clerk, …) if you want the
  principal to be trustworthy. You can also set it to a string, which puts every caller in one shared
  scope, or derive it per call:

  ```ts theme={null}
  export default agentkit({
    userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  });
  ```

  Memories are stored at `agentkit:memory:<userId>:<id>`.
</Accordion>

### How to add searchable chat history to Vercel Eve

`chatHistory: true` persists every user and assistant message to Redis as the session streams, and
gives the model two tools over that store. A user can then ask about something settled in a previous
conversation:

```ts theme={null}
// agent/extensions/agentkit.ts
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({ chatHistory: true });
```

* `agentkit__search_chat_history` runs a `$smart` (typo-tolerant) search over what was said and returns
  the matching chats as summaries: `sessionId`, `title`, `updatedAt`, `messageCount`, `score`. The
  current conversation is excluded, since it's already in context.
* `agentkit__read_chat_history` reads one of those chats back by `sessionId`, newest messages last.

Both tools take `userId` from the session, so the model cannot widen a lookup past the current user's
own transcripts.

<Accordion title="Options, storage, and reading history from your own code">
  Pass an object in place of `true` to tune storage:

  * `chatHistory.prefix` — base key prefix (default `agentkit:chat`).
  * `chatHistory.indexName` — [Redis Search](/redis/search/introduction) index name (defaults to the
    identifier-safe `prefix`).
  * `chatHistory.ttlSeconds` — per-chat TTL. Omit for no expiry.

  Each session is one JSON document at `agentkit:chat:<userId>:<sessionId>`, holding the raw transcript
  plus `$smart`-indexed user and model text. A search returns summaries, and a read is capped at 50
  messages per call with a `truncated` flag, so neither can flood the context window.

  Your own code can read the same store with `ChatHistory` from `@upstash/agentkit-sdk`
  (`listChats` / `searchChats` / `getChat`), which is how you'd build a history sidebar or run evals
  over past sessions. Redis is the durable record here, since Eve's own workflow store is pruned after
  a run completes.

  These tools look history up on demand. They don't resume a session: Eve does that through its own
  session cursor.
</Accordion>

### How to add RAG to Vercel Eve

Point the extension at an [Upstash Redis Search](/redis/search/introduction) index and the model gets
`search`, `search_aggregate`, and `search_count` tools over it. You build the schema with `s` from
`@upstash/redis`, so your mount file imports it. Add the package to your app:

```bash theme={null}
npm install @upstash/redis
```

```ts theme={null}
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
  },
});
```

<Accordion title="Options and how the tools are described to the model">
  * **`search.schema`** *(required)* — built with `s` from `@upstash/redis`.
  * `search.indexName` — defaults to `"agentkit:search"`; ties all three tools to one index.
  * `search.prefix` — key prefix for indexed JSON docs (defaults to `"<indexName>:"`).
  * `search.defaultLimit` — default page size for `search` (10).

  Tool descriptions are generated from your schema (field names, types, and the filter operators that
  apply to each), so the model learns the index without any prompt text from you. You write the
  documents yourself with `redis.json.set` under the prefix, and the index is created on first read.

  Omit `search` and these three tools don't exist at all. Like the chat-history tools, they resolve at
  session start, which is why they don't appear in a static tool listing.
</Accordion>

### Extension configuration reference

```ts theme={null}
// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  // optional: string, or (ctx) => string. Defaults to the verified principal, then the session id.
  userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  // optional: an explicit client; defaults to Redis.fromEnv()
  // redis: new Redis({ url, token }),
  // optional: tune memory recall
  memory: { topK: 5, minScore: 1 },
  // optional: omit and the search tools don't exist
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
    prefix: "books:", // optional
    defaultLimit: 10, // optional
  },
  // optional: off by default. `true`, or an object to tune storage
  chatHistory: { ttlSeconds: 60 * 60 * 24 * 30 },
});
```

<Accordion title="Dropping or overriding a contribution">
  Mount as a directory and override a slot by filename. This is how you drop a tool you don't want,
  for example to capture chat history without letting the model read it back:

  ```
  agent/extensions/agentkit/
    extension.ts                     # the mount: export default agentkit({ ... })
    tools/read_chat_history.ts       # your override for agentkit__read_chat_history
  ```

  ```ts theme={null}
  // agent/extensions/agentkit/tools/read_chat_history.ts
  import { disableTool } from "eve/tools";

  export default disableTool();
  ```

  You can also re-define the memory tools, say to gate saves behind approval, by importing them from
  `@upstash/agentkit-eve-extension/tools` and spreading them into your own `defineTool`.
</Accordion>

<Note>
  `@upstash/agentkit-eve` below offers memory and RAG as standalone tool files too. Use those when you
  want to configure one tool at a time, and use the package alongside the extension for rate limiting
  and sandboxes.
</Note>

## The Eve package: rate limiting, sandboxes, and tool files

Everything in this part comes from `@upstash/agentkit-eve`, written as individual `agent/` files.
Rate limiting and the sandbox backend live here because an extension cannot contribute a channel or
a sandbox.

### How to add rate limiting to Vercel Eve

```bash theme={null}
npm install @upstash/agentkit-eve @upstash/redis
```

`createRateLimitAuth` is a ready `AuthFn` that throttles inbound requests. Drop it into your channel's
[auth walk](https://eve.dev/docs/guides/auth-and-route-protection) ahead of your real authenticators.

```ts theme={null}
// agent/channels/eve.ts
import { createRateLimitAuth, Ratelimit } from "@upstash/agentkit-eve";
import { localDev, vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";

export default eveChannel({
  auth: [
    createRateLimitAuth({
      limiter: Ratelimit.slidingWindow(20, "1 m"),
      identifier: (req) => req.headers.get("x-forwarded-for") ?? "anonymous",
    }),
    localDev(),
    vercelOidc(),
  ],
});
```

<AccordionGroup>
  <Accordion title="Options and the required identifier">
    * **`limiter`** *(required)* — e.g. `Ratelimit.slidingWindow(20, "1 m")` or `fixedWindow(...)`.
    * **`identifier`** *(required)* — a string, or `(request) => string`. There's no implicit `"global"`:
      one shared bucket lets a single abusive caller exhaust the window for everyone, so derive it per
      request (an auth user id, an API key, or `x-forwarded-for` for per-IP).
    * `prefix` — base key prefix; keys are `<prefix>:<identifier>` (default `agentkit:rateLimit`).
    * `message` — 403 body when over the limit.
    * `redis` — defaults to `Redis.fromEnv()`.

    It's a gate: under the limit it returns `null` to fall through to the next `AuthFn`; over it throws
    a 403.
  </Accordion>

  <Accordion title="Why only POST requests are counted">
    Eve runs each turn as two authenticated requests: the message `POST` (which invokes the model) and a
    follow-up `GET …/stream` that opens the reply stream. The auth walk runs on both, so counting both
    would charge every turn twice. `createRateLimitAuth` counts only the `POST`s, so one turn costs one
    token: a `Ratelimit.slidingWindow(20, "1 m")` allows 20 turns per minute, not 10. The session-read
    `GET`s pass through unthrottled.
  </Accordion>
</AccordionGroup>

### How to add a sandbox to Vercel Eve

A drop-in replacement for Eve's `vercel()` backend, powered by
[Upstash Box](https://github.com/upstash/box). Swap the import and keep the rest of your
[sandbox file](https://eve.dev/docs/sandbox) the same.

```bash theme={null}
npm install @upstash/box
```

```ts theme={null}
// agent/sandbox.ts
import { defineSandbox } from "eve/sandbox";
import { upstash } from "@upstash/agentkit-eve/sandbox"; // was: eve/sandbox/vercel

export default defineSandbox({
  backend: upstash({ runtime: "node", size: "medium" }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use({ networkPolicy: "allow-all" }); // open egress to install packages
    await sandbox.run({ command: "apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use(); // inherits the secure deny-all default
  },
});
```

<AccordionGroup>
  <Accordion title="Config: Box's BoxConfig">
    `upstash(config)` takes the `@upstash/box` `BoxConfig` verbatim, meaning whatever you'd pass to
    `Box.create({...})`: `runtime`, `size`, `apiKey` (defaults to `UPSTASH_BOX_API_KEY`), `keepAlive`,
    `initCommand`, `env`, `skills`, `mcpServers`, `timeout`, and so on. It also takes an optional
    `redis` (defaults to `Redis.fromEnv()`). `networkPolicy` is **not** a config knob (see below).
    `@upstash/box` is an optional peer dependency, needed only when you import
    `@upstash/agentkit-eve/sandbox`.
  </Accordion>

  <Accordion title="Security: network egress is deny-all by default">
    The sandbox runs untrusted, model-generated code, so open egress would mean SSRF / data
    exfiltration / reaching your own infrastructure from inside the box. Open it per-session, in
    `bootstrap`'s `use(...)` or the session `use(...)`, and never as a config knob. Note that `env` passed
    to `upstash({ env })` is readable by code running in the box; don't pass secrets you wouldn't want
    it to see.
  </Accordion>

  <Accordion title="Brokering credentials (injecting headers)">
    Box network policies are plain domain/CIDR allow-lists. Eve's per-domain firewall rules (`transform`
    header injection, `forwardURL`) have no Box equivalent, so passing them in `use({ networkPolicy })`
    **throws** rather than silently sending the request unauthenticated:

    ```ts theme={null}
    // ❌ throws — Box can't inject headers via a per-session policy
    export default defineSandbox({
      backend: upstash({ runtime: "node" }),
      async onSession({ use }) {
        await use({
          networkPolicy: {
            allow: { "api.example.com": [{ transform: [{ headers: { authorization: "Bearer …" } }] }] },
          },
        });
      },
    });
    ```

    Broker credentials with Box's `attachHeaders` instead (set at backend creation; a proxy on the box
    injects them), and open the domain with a plain allow-list:

    ```ts theme={null}
    // ✅ headers injected at the firewall; the secret never enters the box
    export default defineSandbox({
      backend: upstash({
        runtime: "node",
        attachHeaders: { "api.example.com": { Authorization: "Bearer …" } },
      }),
      async onSession({ use }) {
        await use({ networkPolicy: { allow: ["api.example.com"] } });
      },
    });
    ```
  </Accordion>

  <Accordion title="Lifecycle: one box per conversation">
    **Reuse.** Eve re-opens a session several times per turn, and the backend reattaches to the same Box
    instead of creating a new one each time. Boxes default to Box's pause-based idle lifecycle
    (`keepAlive: false`): auto-paused when idle, resumed on reattach, reaped by Box. Pass
    `keepAlive: true` only for an always-running box you manage yourself.

    **Template registry.** Eve builds your template (seed files + `bootstrap`) at build/startup, but
    session creation runs per request in a different process, so the snapshot id is stored in a durable
    Redis registry (`redis`, defaulting to `Redis.fromEnv()`). Eve roots its tools at `/workspace` while
    a Box session lives at `/workspace/home`; the backend bridges the two automatically.
  </Accordion>
</AccordionGroup>

### How to cache tools in Vercel Eve

Like Eve's `defineTool`, but the `execute` result is memoized in Redis.

```ts theme={null}
// agent/tools/get_weather.ts
import { z } from "zod";
import { defineCachedTool } from "@upstash/agentkit-eve";

export default defineCachedTool({
  description: "Get the current weather for a city.",
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => fetchWeather(city),
  toolName: "get_weather",
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

<Accordion title="Options">
  * `description` / `inputSchema` / `execute` — the usual `defineTool` fields; `execute`'s result is memoized.
  * **`toolName`** *(required)* — the tool segment of the cache key.
  * **`userId`** *(required)* — a string, or `(input, ctx) => string`; scopes the cache per user.
  * `ttlSeconds` — per-result TTL (default: no expiry).
  * `redis` — defaults to `Redis.fromEnv()`.

  Keys are `agentkit:toolCache:<userId>:<toolName>:<hash>`.
</Accordion>

### Memory and RAG as individual tool files

The same two features the extension mounts, written as standalone `agent/tools/` files. Use these when
you want to configure each tool on its own.

Memory takes one file per tool:

```ts theme={null}
// agent/tools/recall_memory.ts
import { defineMemoryRecallTool } from "@upstash/agentkit-eve";

export default defineMemoryRecallTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

```ts theme={null}
// agent/tools/save_memory.ts
import { defineMemorySaveTool } from "@upstash/agentkit-eve";

export default defineMemorySaveTool({
  userId: (_, ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
});
```

RAG is `defineSearchTools`, the counterpart to the
[AI SDK adapter's](/redis/sdks/agentkit/ai-sdk#how-to-add-rag-with-the-ai-sdk) `createSearchTools`:

```ts theme={null}
// agent/tools/search_books.ts
import { s } from "@upstash/redis";
import { defineSearchTools } from "@upstash/agentkit-eve";

export default defineSearchTools({
  schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
  indexName: "books",
}).search; // aggregate_books.ts → .aggregate, count_books.ts → .count
```

<Accordion title="Options and the one-file-per-tool rule">
  `defineMemoryRecallTool` / `defineMemorySaveTool` take a required `userId` (string or
  `(input, ctx) => string`), plus `topK`, `minScore`, and `redis`. `defineSearchTools` takes a required
  `schema`, plus `indexName`, `prefix`, `defaultLimit`, and `redis`.

  Each tool file must be self-contained, so call `defineSearchTools` in each one and export the member
  you want, repeating the same `schema` and `indexName` across `search_books.ts`, `aggregate_books.ts`,
  and `count_books.ts`. The index is created on first use, and every returned tool is already
  `defineTool`-branded.

  This package has no chat history. It comes from the extension, or from `ChatHistory` in
  `@upstash/agentkit-sdk` if you're writing the code yourself.
</Accordion>

## Working with Eve's `agent/` files

Eve's runtime snapshots each tool/channel/hook file and resolves only **package** imports from it. It
does **not** include shared `agent/`-source modules such as an `agent/lib/redis.ts`. So inside `agent/`:

* Import only from packages, never from other `agent/` files.
* Lean on the defaults. `redis` falls back to `Redis.fromEnv()` in every helper, so you almost never pass it.
* Repeat config (schema, names) in each file instead of sharing a module.

Shared app code, like a seeder a page calls, belongs in your project `lib/` and is imported by the app,
not by `agent/` files. Extensions are exempt from all of this. The extension package ships as one
compiled unit, which is why its whole configuration fits in a single mount file.

## How to run the Vercel Eve example apps

Two complete `eve` apps live in the AgentKit repo:

* [`examples/eve-extension-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-extension-demo)
  is a minimal agent whose whole configuration is one extension mount, with memory, chat history, and
  book search turned on.
* [`examples/eve-demo`](https://github.com/upstash/agentkit/tree/main/examples/eve-demo) uses the
  file-by-file package (memory, search, cached tools, a rate-limit gate, and an Upstash Box sandbox),
  with a chat UI that renders tool calls inline.

<CardGroup cols={2}>
  <Card title="AgentKit on GitHub" icon="github" href="https://github.com/upstash/agentkit">
    Source, packages, and the full example apps.
  </Card>

  <Card title="Eve" icon="up-right-from-square" href="https://eve.dev">
    The Vercel agent framework this adapter targets.
  </Card>
</CardGroup>
