Skip to content

Commit fed987f

Browse files
benfrank241claude
andauthored
feat: add Chat SDK integration for persistent chat bot memory (#442)
Adds @vectorize-io/hindsight-chat, a wrapper for the Vercel Chat SDK that gives any chat bot (Slack, Discord, Teams, etc.) long-term memory via Hindsight. Includes withHindsightChat() handler wrapper with auto-recall, auto-retain, and memoriesAsSystemPrompt() formatting. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8cd65b9 commit fed987f

File tree

12 files changed

+4041
-0
lines changed

12 files changed

+4041
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
7+
# Test coverage
8+
coverage/
9+
10+
# IDE
11+
.vscode/
12+
.idea/
13+
*.swp
14+
*.swo
15+
*~
16+
17+
# OS
18+
.DS_Store
19+
Thumbs.db
20+
21+
# Logs
22+
*.log
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# Environment
28+
.env
29+
.env.local
30+
.env.*.local
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# @vectorize-io/hindsight-chat
2+
3+
Give your [Vercel Chat SDK](https://github.com/vercel/chat) bots persistent, per-user memory with a single handler wrapper. Works with Slack, Discord, Teams, Google Chat, GitHub, and Linear.
4+
5+
## Quick Start
6+
7+
```bash
8+
npm install @vectorize-io/hindsight-chat
9+
```
10+
11+
```typescript
12+
import { Chat } from 'chat';
13+
import { HindsightClient } from '@vectorize-io/hindsight-client';
14+
import { withHindsightChat } from '@vectorize-io/hindsight-chat';
15+
import { streamText } from 'ai';
16+
import { openai } from '@ai-sdk/openai';
17+
18+
const chat = new Chat({ connectors: [/* your connectors */] });
19+
const hindsight = new HindsightClient({ apiKey: process.env.HINDSIGHT_API_KEY });
20+
21+
chat.onNewMention(
22+
withHindsightChat(
23+
{
24+
client: hindsight,
25+
bankId: (msg) => msg.author.userId, // per-user memory
26+
},
27+
async (thread, message, ctx) => {
28+
await thread.subscribe();
29+
30+
const result = await streamText({
31+
model: openai('gpt-4o'),
32+
system: ctx.memoriesAsSystemPrompt(),
33+
messages: [{ role: 'user', content: message.text }],
34+
});
35+
36+
// Stream the response
37+
const chunks: string[] = [];
38+
for await (const chunk of result.textStream) {
39+
chunks.push(chunk);
40+
}
41+
const fullResponse = chunks.join('');
42+
await thread.post(fullResponse);
43+
44+
// Store the conversation in memory
45+
await ctx.retain(
46+
`User: ${message.text}\nAssistant: ${fullResponse}`
47+
);
48+
}
49+
)
50+
);
51+
```
52+
53+
## Configuration
54+
55+
### `withHindsightChat(options, handler)`
56+
57+
Returns a standard Chat SDK handler `(thread, message) => Promise<void>`.
58+
59+
#### Options
60+
61+
| Option | Type | Default | Description |
62+
|--------|------|---------|-------------|
63+
| `client` | `HindsightClient` | *required* | Hindsight client instance |
64+
| `bankId` | `string \| (msg) => string` | *required* | Memory bank ID or resolver function |
65+
| `recall.enabled` | `boolean` | `true` | Auto-recall memories before handler |
66+
| `recall.budget` | `'low' \| 'mid' \| 'high'` | `'mid'` | Processing budget for recall |
67+
| `recall.maxTokens` | `number` | API default | Max tokens for recall results |
68+
| `recall.types` | `FactType[]` | all | Filter to specific fact types |
69+
| `recall.includeEntities` | `boolean` | `true` | Include entity observations |
70+
| `retain.enabled` | `boolean` | `false` | Auto-retain inbound messages |
71+
| `retain.async` | `boolean` | `true` | Fire-and-forget retain |
72+
| `retain.tags` | `string[]` || Tags for retained memories |
73+
| `retain.metadata` | `Record<string, string>` || Metadata for retained memories |
74+
75+
### Context (`ctx`)
76+
77+
The third argument passed to your handler:
78+
79+
| Property/Method | Description |
80+
|----------------|-------------|
81+
| `ctx.bankId` | Resolved bank ID |
82+
| `ctx.memories` | Array of recalled memories |
83+
| `ctx.entities` | Entity observations (or null) |
84+
| `ctx.memoriesAsSystemPrompt(options?)` | Format memories for LLM system prompt |
85+
| `ctx.retain(content, options?)` | Store content in memory |
86+
| `ctx.recall(query, options?)` | Search memories |
87+
| `ctx.reflect(query, options?)` | Reason over memories |
88+
89+
## Examples
90+
91+
### Subscribed Message Handler
92+
93+
```typescript
94+
chat.onSubscribedMessage(
95+
withHindsightChat(
96+
{
97+
client: hindsight,
98+
bankId: (msg) => msg.author.userId,
99+
recall: { budget: 'high', maxTokens: 1000 },
100+
},
101+
async (thread, message, ctx) => {
102+
const result = await generateText({
103+
model: openai('gpt-4o'),
104+
system: ctx.memoriesAsSystemPrompt(),
105+
messages: [{ role: 'user', content: message.text }],
106+
});
107+
await thread.post(result.text);
108+
}
109+
)
110+
);
111+
```
112+
113+
### Auto-Retain Inbound Messages
114+
115+
```typescript
116+
chat.onNewMention(
117+
withHindsightChat(
118+
{
119+
client: hindsight,
120+
bankId: (msg) => msg.author.userId,
121+
retain: { enabled: true, tags: ['slack', 'inbound'] },
122+
},
123+
async (thread, message, ctx) => {
124+
// Inbound message is already being retained automatically
125+
const result = await generateText({
126+
model: openai('gpt-4o'),
127+
system: ctx.memoriesAsSystemPrompt(),
128+
messages: [{ role: 'user', content: message.text }],
129+
});
130+
await thread.post(result.text);
131+
132+
// Retain the assistant response separately
133+
await ctx.retain(`Assistant: ${result.text}`, {
134+
tags: ['slack', 'outbound'],
135+
});
136+
}
137+
)
138+
);
139+
```
140+
141+
### Static Bank ID (Shared Memory)
142+
143+
```typescript
144+
// All users share the same memory bank
145+
chat.onNewMention(
146+
withHindsightChat(
147+
{ client: hindsight, bankId: 'shared-team-memory' },
148+
async (thread, message, ctx) => {
149+
// ...
150+
}
151+
)
152+
);
153+
```
154+
155+
## Error Handling
156+
157+
Memory failures never break your bot. Auto-recall and auto-retain errors are logged as warnings and the handler continues with empty memories. Manual `ctx.retain()`, `ctx.recall()`, and `ctx.reflect()` calls propagate errors normally so you can handle them as needed.
158+
159+
## License
160+
161+
MIT

0 commit comments

Comments
 (0)