Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f4f2ef2
feat(ai): add memory types
AlemTuzlak May 10, 2026
fca4624
feat(ai): add memory helper functions
AlemTuzlak May 10, 2026
42904a2
feat(ai): expose @tanstack/ai/memory subpath
AlemTuzlak May 10, 2026
474eb4a
test(ai): add failing memory middleware tests
AlemTuzlak May 10, 2026
397098c
feat(ai): add memoryMiddleware
AlemTuzlak May 10, 2026
c60faa0
fix(ai): tighten memory middleware test types for noUncheckedIndexedA…
AlemTuzlak May 10, 2026
f9945a7
feat(ai-event-client): add memory devtools events
AlemTuzlak May 10, 2026
c88c65d
feat(ai): emit memory devtools events from middleware
AlemTuzlak May 10, 2026
ab7dc97
feat(ai-memory): scaffold new package
AlemTuzlak May 10, 2026
01ba8a8
test(ai-memory): add shared adapter contract suite
AlemTuzlak May 10, 2026
72cc2b6
feat(ai-memory): add inMemoryMemoryAdapter
AlemTuzlak May 10, 2026
40be462
fix(ai-memory): tighten in-memory adapter lint compliance
AlemTuzlak May 10, 2026
055cd50
feat(ai-memory): add redisMemoryAdapter
AlemTuzlak May 10, 2026
d6df979
docs(ai): add tanstack-ai-memory skill
AlemTuzlak May 10, 2026
e0913b2
docs(ai-memory): add in-memory adapter skill
AlemTuzlak May 10, 2026
32b15de
docs(ai-memory): add redis adapter skill
AlemTuzlak May 10, 2026
74e7136
docs: add memory middleware concept and quickstart pages
AlemTuzlak May 10, 2026
1dd988a
chore: changeset for memory middleware
AlemTuzlak May 10, 2026
c93e7f6
chore: final formatting
AlemTuzlak May 10, 2026
ecd38ac
fix(ai, ai-memory): clean up lint and knip findings
AlemTuzlak May 10, 2026
d1fb337
fix(ai, ai-memory): address whole-feature audit findings
AlemTuzlak May 10, 2026
6576f7c
ci: apply automated fixes
autofix-ci[bot] May 10, 2026
54bec71
fix(ai): address CR Round 1 core middleware findings
AlemTuzlak May 10, 2026
2c3588c
fix(ai-memory): redis adapter scope semantics
AlemTuzlak May 10, 2026
5600b3b
feat(ai-memory): nodeRedisAsRedisLike helper for node-redis v4+
AlemTuzlak May 10, 2026
2eb1425
test(ai, ai-memory): tighten flaky and vacuous CR assertions
AlemTuzlak May 10, 2026
ac100b8
chore(ai-memory): set initial version to 0.0.0 for first publish
AlemTuzlak May 10, 2026
9fcb483
fix(ai, ai-memory): address CR Round 2 bucket-a findings
AlemTuzlak May 10, 2026
64a3872
fix(ai): close error-path observability gaps in memory middleware
AlemTuzlak May 10, 2026
59ec97e
fix(ai, ai-memory): close remaining scope-value-validation gaps
AlemTuzlak May 10, 2026
8a8d599
fix(ai-memory): escape _ in scope values to prevent placeholder colli…
AlemTuzlak May 10, 2026
94359d1
chore: refresh pnpm-lock.yaml for ai-memory ioredis peer dep
AlemTuzlak May 10, 2026
d17ae31
ci: apply automated fixes
autofix-ci[bot] May 10, 2026
ed23b50
docs: consolidate memory pages into a top-level Memory section
AlemTuzlak May 10, 2026
224f805
fix(ai, ai-memory): address CodeRabbit code review feedback
AlemTuzlak May 10, 2026
b478e8e
docs, chore: address CodeRabbit polish feedback
AlemTuzlak May 10, 2026
60405e6
Merge origin/main into feat/memory-middleware
tombeckenham Jul 20, 2026
5d38c02
fix(ai, ai-memory): address memory-middleware review findings
tombeckenham Jul 20, 2026
6556263
Merge remote-tracking branch 'origin/main' into pr-541-memory-middleware
jherr Jul 20, 2026
f86e9da
docs(memory): fix kiira doc-snippet type errors
jherr Jul 21, 2026
3dec6dd
Merge branch 'main' into pr-541-memory-middleware
jherr Jul 21, 2026
e17b096
refactor(memory): recall/save adapter contract, consolidate into @tan…
jherr Jul 22, 2026
3c1427c
ci: apply automated fixes
autofix-ci[bot] Jul 22, 2026
848c47a
refactor(memory): nest in-memory/redis under providers, add provider …
jherr Jul 22, 2026
6174843
feat(panel): add in-memory Memory demo page
jherr Jul 22, 2026
2fbd813
feat(devtools): surface memory state in the AI DevTools
jherr Jul 22, 2026
3268a7a
docs(memory): polish memory guides and rename node-redis wrapper
jherr Jul 23, 2026
614325d
Merge remote-tracking branch 'origin/main' into pr-541-memory-middleware
jherr Jul 23, 2026
12946cc
ci: apply automated fixes
autofix-ci[bot] Jul 23, 2026
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
29 changes: 29 additions & 0 deletions .changeset/devtools-memory-inspector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@tanstack/ai-memory': minor
'@tanstack/ai-event-client': minor
'@tanstack/ai-client': minor
'@tanstack/ai-devtools-core': minor
---

**Surface server-side memory state in the TanStack AI DevTools.**

The DevTools panel now has a **Memory** tab for any chat wired with
`memoryMiddleware`. It shows, per scope (session), an operations timeline (each
turn's recall — query, fragment count, injected system-prompt size, whether
memory tools were exposed, duration) and the current stored records/facts when
the adapter implements the optional `inspect`/`listFacts` methods.

Because memory runs on the server (whose event bus never reaches the browser),
the middleware transports its state to the panel over the chat stream as a
`memory:state` `CUSTOM` event, which `@tanstack/ai-client`'s devtools bridge
re-emits as browser `memory:*` events — the same pattern generation results use.
The snapshot reflects memory as of the start of each turn; opening the panel
mid-conversation replays the latest state so the tab isn't empty.

- `@tanstack/ai-memory` — `memoryMiddleware` injects a `memory:state` `CUSTOM`
chunk carrying recall metrics + an `inspect`/`listFacts` snapshot; exports
`MEMORY_STATE_EVENT` and `MemoryStateEventValue`.
- `@tanstack/ai-event-client` — adds the `memory:snapshot` devtools event.
- `@tanstack/ai-client` — the chat devtools bridge re-emits `memory:*` from the
transported chunk and replays the last snapshot on `devtools:request-state`.
- `@tanstack/ai-devtools-core` — new Memory tab + per-scope memory store slice.
44 changes: 44 additions & 0 deletions .changeset/memory-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
'@tanstack/ai': minor
'@tanstack/ai-event-client': minor
'@tanstack/ai-memory': minor
---

**Add server-side memory via a `recall`/`save` adapter contract in `@tanstack/ai-memory`.**

Memory is now a single, provider-agnostic contract with two verbs — `recall` and
`save` — which is the shape every memory backend (in-process, Redis, and hosted
vendors) naturally exposes. `memoryMiddleware` recalls relevant memory into the
system prompt (and optionally injects vendor tools) before the model runs, then
defers `save` of the finished turn via `ctx.defer` so streaming is never blocked.
Extraction, ranking, and rendering live inside each adapter — the middleware is thin.

`@tanstack/ai-memory` (new package) — everything ships here:

- Root: `memoryMiddleware`, the `MemoryAdapter` contract
(`recall` / `save` / optional `inspect` / `listFacts`), and the `MemoryScope` /
`MemoryTurn` / `RecallResult` / `SaveReceipt` types.
- `@tanstack/ai-memory/in-memory` → `inMemory()` — zero-dependency adapter for dev,
tests, and single-process demos. Pass an `embedder` for semantic scoring and/or an
`extract` function to persist derived facts.
- `@tanstack/ai-memory/redis` → `redis({ redis, prefix? })` — production adapter for
plain Redis. `ioredis` wires in directly; `redis` (node-redis v4+) via the
`fromNodeRedis(client)` wrapper. Both are optional peer dependencies.
- `@tanstack/ai-memory/hindsight` → `hindsight()`, `@tanstack/ai-memory/mem0` →
`mem0()`, `@tanstack/ai-memory/honcho` → `honcho()` — hosted-vendor adapters. Their
SDKs (`@vectorize-io/hindsight-client`, `@honcho-ai/sdk`) are optional peers loaded
lazily; mem0 talks to its server over plain HTTP (no SDK). Vendors can expose LLM
tools through `recall` (e.g. hindsight's retain/recall/reflect).
- A shared `recall`/`save` contract-test suite (`@tanstack/ai-memory/tests/contract`)
that any adapter — including third-party ones — can run.

`@tanstack/ai`:

- **Removes the (unreleased) `@tanstack/ai/memory` subpath.** The middleware,
contract, and helpers all moved to `@tanstack/ai-memory`.

`@tanstack/ai-event-client`:

- The five `memory:*` devtools events (`memory:retrieve:started` / `:completed`,
`memory:persist:started` / `:completed`, `memory:error`) now carry recall/save
payloads (adapter id, fragment/receipt counts, `phase: 'recall' | 'save'`).
37 changes: 36 additions & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
{
"label": "Devtools",
"to": "getting-started/devtools",
"addedAt": "2026-04-15"
"addedAt": "2026-04-15",
"updatedAt": "2026-07-22"
},
{
"label": "Quick Start: Vue",
Expand Down Expand Up @@ -460,6 +461,40 @@
}
]
},
{
"label": "Memory",
"children": [
{
"label": "Overview",
"to": "memory/overview",
"addedAt": "2026-07-21",
"updatedAt": "2026-07-22"
},
{
"label": "Quickstart",
"to": "memory/quickstart",
"addedAt": "2026-07-21",
"updatedAt": "2026-07-22"
},
{
"label": "Adapters",
"to": "memory/adapters",
"addedAt": "2026-07-21",
"updatedAt": "2026-07-22"
},
{
"label": "Custom Adapter",
"to": "memory/custom-adapter",
"addedAt": "2026-07-21",
"updatedAt": "2026-07-22"
},
{
"label": "Operating",
"to": "memory/operating",
"addedAt": "2026-07-22"
}
]
},
{
"label": "Advanced",
"children": [
Expand Down
10 changes: 10 additions & 0 deletions docs/getting-started/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ TanStack Devtools is a unified devtools panel for inspecting and debugging TanSt
- **Tool Call Inspection** - Inspect input and output of tool calls.
- **Tool Fixture Replay** - Build tool payloads from a tool's standard-schema input, append the result into chat messages, and save fixtures in localStorage for repeated UI iteration.
- **State Visualization** - Visualize chat state and message history.
- **Memory Inspector** - For chats wired with `memoryMiddleware`, see what memory recalled and injected each turn plus the current stored records and facts.
- **Error Tracking** - Monitor errors and exceptions in AI interactions.

## Hook Dashboard
Expand Down Expand Up @@ -74,6 +75,15 @@ When a `useChat` hook receives tools, the devtools panel lists those tools and t

Applying a tool fixture appends the tool call and result into the real chat messages for that hook. Saved fixtures are stored in browser localStorage under the AI devtools namespace so they are available the next time you open the panel.

## Memory Inspector

When a chat is wired with [`memoryMiddleware`](../memory/overview.md), the hook's **Memory** tab shows what the server-side memory backend did for that conversation, grouped by scope (session):

- **Operations timeline** - Each turn's recall: the query, how many fragments came back, how many characters were injected into the system prompt, whether memory-provided tools were exposed, and the recall duration.
- **Stored records & facts** - The current contents of the memory store for the scope, when the adapter implements the optional `inspect`/`listFacts` methods (the built-in `inMemory()` and `redis()` adapters do). Adapters without introspection still show the operations timeline.

Because memory runs on the server, its state is transported to the panel over the chat stream (a `CUSTOM` event the client re-emits) rather than a separate channel — the same way generation results reach the panel. The snapshot reflects memory as of the start of each turn, so a turn's own writes appear in the next turn's snapshot. Opening the panel after a turn replays the latest memory state, so the tab is populated even when you open devtools mid-conversation.

## Event Sources

Client-visible state is emitted by the headless client. Server-only details, such as middleware and provider stream events that never exist on the client, are emitted from the server counterpart. Events include a source descriptor and stable envelope id so the panel can link related events and avoid displaying duplicates.
Expand Down
200 changes: 200 additions & 0 deletions docs/memory/adapters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
title: Adapters
id: memory-adapters
order: 3
description: "Every built-in and vendor memory adapter in @tanstack/ai-memory, with all of their options and an example of each: inMemory, redis, hindsight, mem0, honcho."
keywords:
- tanstack ai
- memory
- adapters
- inMemory
- redis
- hindsight
- mem0
- honcho
- options
---

Every adapter implements the same `recall`/`save` contract, so they're interchangeable
in `memoryMiddleware`. This page is the full option reference: each adapter's options with
an example of each.

- [Common options](#common-options), shared by `inMemory()` and `redis()`
- Adapters: [`inMemory()`](#inmemory), [`redis()`](#redis), [`hindsight()`](#hindsight), [`mem0()`](#mem0), [`honcho()`](#honcho)

## Common options

`inMemory()` and `redis()` are both client-side rankers built on the same pipeline, so
they share these options.

| Option | Type | Default | Purpose |
|--------|------|---------|---------|
| `topK` | `number` | `6` | Max hits returned by `recall`. |
| `minScore` | `number` | `0.15` | Drop hits scoring below this. |
| `kinds` | `Array<MemoryKind>` | all | Restrict recall to these record kinds (`'message'`, `'summary'`, `'fact'`, `'preference'`). |
| `embedder` | `{ embed(text): Promise<number[]> }` | none | Enable semantic scoring (embeds on both `recall` and `save`). |
| `extract` | `(turn, scope) => ExtractedFact[]` | none | Persist derived facts on `save`, alongside the raw turn. |
| `render` | `(hits) => string` | built-in | Replace the prompt renderer. |

Every option, in one adapter:

```ts
import { inMemory } from '@tanstack/ai-memory/in-memory'

// `embedText` stands in for your embedding client (OpenAI, Cohere, a local model).
declare function embedText(text: string): Promise<Array<number>>

const memory = inMemory({
topK: 8, // return up to 8 hits
minScore: 0.2, // ignore weak matches
kinds: ['message', 'fact', 'preference'], // skip summaries
embedder: { embed: embedText }, // semantic + lexical scoring
extract: (turn) => [
// store a derived fact in addition to the raw turn
{ text: `User said: ${turn.user}`, kind: 'fact', importance: 0.8 },
],
render: (hits) =>
// custom prompt block instead of the default renderer
`What I remember:\n${hits.map((h) => `- ${h.record.text}`).join('\n')}`,
})
```

**`extract`** returns `ExtractedFact[]` (`{ text, kind?, importance?, metadata? }`). Return
`undefined` for a no-op. It's where an LLM-based fact extractor plugs in without the
adapter taking a hard dependency on any model.

**`embedder`** is invoked on the recall path (to embed the query) and again on save (to
embed stored text). Without it, scoring is lexical + recency only.

## `inMemory()`

Zero-dependency, `Map`-backed. Takes only the [common options](#common-options) above.
Records vanish on restart, so use it for dev, tests, and single-process demos.

```ts
import { inMemory } from '@tanstack/ai-memory/in-memory'

const memory = inMemory() // all options are optional
```

## `redis()`

Plain-Redis adapter. Adds two options to the [common options](#common-options), and
requires a client.

| Option | Type | Default | Purpose |
|--------|------|---------|---------|
| `redis` | `RedisLike` | (required) | Your Redis client (`ioredis`, or node-redis via `fromNodeRedis`). |
| `prefix` | `string` | `'tanstack-ai:memory'` | Key namespace. |

```ts
import Redis from 'ioredis'
import { redis } from '@tanstack/ai-memory/redis'

const memory = redis({
redis: new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379'), // required
prefix: 'myapp:memory', // key namespace
topK: 8, // common options apply here too
minScore: 0.2,
})
```

Using **node-redis** (`redis` package) instead of `ioredis`? Its camelCase API doesn't
match `RedisLike`, so wrap it with `fromNodeRedis`:

```ts
import { createClient } from 'redis'
import { redis, fromNodeRedis } from '@tanstack/ai-memory/redis'

const client = createClient({ url: process.env.REDIS_URL })
await client.connect()

const memory = redis({ redis: fromNodeRedis(client) })
```

`ioredis` and `redis` are both optional peer dependencies. Install whichever you use.

## `hindsight()`

Hosted adapter backed by Hindsight. Owns extraction/ranking server-side and exposes
`retain`/`recall`/`reflect` LLM tools through `recall`. `@vectorize-io/hindsight-client`
is an optional peer, loaded lazily.

| Option | Type | Default | Purpose |
|--------|------|---------|---------|
| `user` | `string` | `scope.userId` | Durable user id used in the bank key (`{user}__{sessionId}`). |
| `baseUrl` | `string` | `HINDSIGHT_URL` / `http://localhost:8888` | Server URL. |
| `budget` | `'low' \| 'mid' \| 'high'` | `'mid'` | Recall budget. |
| `onToolRetain` | `(receipt) => void` | none | Fired when the model calls `hindsight_retain`. |
| `onToolRecall` | `(query, result) => void` | none | Fired when the model calls `hindsight_recall`. |

```ts
import { hindsight } from '@tanstack/ai-memory/hindsight'

const memory = hindsight({
user: 'alice', // bank = alice__{sessionId}
baseUrl: 'https://hindsight.internal', // default: HINDSIGHT_URL
budget: 'high', // deeper recall
onToolRetain: (receipt) => console.log('model retained', receipt.ok),
onToolRecall: (query, result) =>
console.log('model recalled', query, result.fragments?.length),
})
```

## `mem0()`

Hosted adapter backed by a mem0 server, over plain HTTP (no SDK peer). Requires a running
mem0 server.

| Option | Type | Default | Purpose |
|--------|------|---------|---------|
| `user` | `string` | `scope.userId` / `'demo-user'` | mem0 `user_id`. |
| `baseUrl` | `string` | `MEM0_URL` / `http://localhost:8000` | Server URL. |
| `apiKey` | `string` | `MEM0_ADMIN_API_KEY` | Bearer token. |
| `rerank` | `boolean` | `true` | Ask mem0 to rerank search results. |
| `threshold` | `number` | `0.1` | Minimum search score. |

```ts
import { mem0 } from '@tanstack/ai-memory/mem0'

const memory = mem0({
user: 'alice', // mem0 user_id
baseUrl: 'https://mem0.internal', // default: MEM0_URL
apiKey: process.env.MEM0_ADMIN_API_KEY, // bearer token
rerank: true, // rerank results
threshold: 0.2, // stricter score floor
})
```

## `honcho()`

Hosted adapter backed by Honcho. `recall` returns a synthesized dialectic answer over the
user's representation (no discrete fragments). `@honcho-ai/sdk` is an optional peer,
loaded lazily.

| Option | Type | Default | Purpose |
|--------|------|---------|---------|
| `user` | `string` | `scope.userId` / `'demo-user'` | User peer id. |
| `baseURL` | `string` | `HONCHO_URL` / `http://localhost:8001` | Server URL. |
| `workspaceId` | `string` | `HONCHO_APP_NAME` / `'ai-memory'` | Workspace id. |
| `apiKey` | `string` | `HONCHO_API_KEY` / `'dev-no-auth'` | API key. |
| `assistantId` | `string` | `'assistant'` | Assistant peer id. |

```ts
import { honcho } from '@tanstack/ai-memory/honcho'

const memory = honcho({
user: 'alice', // user peer
baseURL: 'https://honcho.internal', // default: HONCHO_URL
workspaceId: 'my-app', // default: HONCHO_APP_NAME
apiKey: process.env.HONCHO_API_KEY, // default: 'dev-no-auth'
assistantId: 'support-bot', // default: 'assistant'
})
```

## Where to go next

- [Overview](./overview): the `recall`/`save` contract and how a turn flows
- [Quickstart](./quickstart): wire an adapter into a real `chat()` call
- [Operating memory](./operating): options, telemetry, devtools events, and failures
- [Custom Adapter](./custom-adapter): implement `recall`/`save` for a backend that isn't shipped
Loading
Loading