From d06a0259cccecc748addd8963ecc9ee8ed63cb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Wed, 18 Feb 2026 13:06:03 +0100 Subject: [PATCH] feat: improve ai sdk tools (#394) --- .../docs/developer/configuration.md | 4 +- .../docs/sdks/integrations/ai-sdk.md | 366 ------------------ .../docs/sdks/integrations/ai-sdk.mdx | 97 +++++ .../examples/integrations/ai-sdk.ts | 122 ++++++ .../ai-sdk/package-lock.json | 4 +- .../ai-sdk/src/tools/index.test.ts | 328 ++++++++-------- .../ai-sdk/src/tools/index.ts | 324 +++++----------- 7 files changed, 496 insertions(+), 749 deletions(-) delete mode 100644 hindsight-docs/docs/sdks/integrations/ai-sdk.md create mode 100644 hindsight-docs/docs/sdks/integrations/ai-sdk.mdx create mode 100644 hindsight-docs/examples/integrations/ai-sdk.ts diff --git a/hindsight-docs/docs/developer/configuration.md b/hindsight-docs/docs/developer/configuration.md index eeb82c0d..687d8828 100644 --- a/hindsight-docs/docs/developer/configuration.md +++ b/hindsight-docs/docs/developer/configuration.md @@ -68,7 +68,7 @@ Hindsight supports three PostgreSQL vector extensions: #### **pgvector** (HNSW - default) - In-memory index using Hierarchical Navigable Small World algorithm - Works well for most embeddings and dataset sizes -- Fast for small-medium datasets (<10M vectors) +- Fast for small-medium datasets (<10M vectors) - Higher memory usage for large datasets - Most widely deployed and supported @@ -97,7 +97,7 @@ Hindsight supports three PostgreSQL vector extensions: - When disk I/O is not a bottleneck **When to use pgvector (HNSW):** -- Small-medium datasets (<10M vectors) +- Small-medium datasets (<10M vectors) - Maximum query speed when all data fits in memory - Simple nearest-neighbor queries without filters - Standard PostgreSQL deployment preference diff --git a/hindsight-docs/docs/sdks/integrations/ai-sdk.md b/hindsight-docs/docs/sdks/integrations/ai-sdk.md deleted file mode 100644 index c538fc24..00000000 --- a/hindsight-docs/docs/sdks/integrations/ai-sdk.md +++ /dev/null @@ -1,366 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Vercel AI SDK - -Official Hindsight integration for the [Vercel AI SDK](https://ai-sdk.dev). - -## Features - -- **7 Memory Tools**: Core memory operations (retain, recall, reflect), mental models (create, query), documents (get), and directives (create) -- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent` -- **Multi-User Support**: Dynamic bank IDs per tool call for multi-user/multi-tenant scenarios -- **Full Parameter Support**: Complete access to all Hindsight API parameters -- **Type-Safe**: Full TypeScript support with Zod schemas for validation - -## Installation - -```bash -npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod -``` - -## Quick Start - -### 1. Set up your Hindsight client - -```typescript -import { HindsightClient } from '@vectorize-io/hindsight-client'; - -const hindsightClient = new HindsightClient({ - apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000', -}); -``` - -### 2. Create Hindsight tools - -```typescript -import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; - -const tools = createHindsightTools({ - client: hindsightClient, -}); -``` - -### 3. Use with AI SDK - -```typescript -import { generateText } from 'ai'; -import { anthropic } from '@ai-sdk/anthropic'; - -const result = await generateText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - prompt: 'Remember that Alice loves hiking and prefers spicy food', -}); - -console.log(result.text); -``` - -## Memory Tools - -The integration provides seven tools that the AI model can use to manage memory: - -### `retain` - Store Information - -The model calls this tool to store information for future recall. - -**Parameters:** -- `bankId` (required): Memory bank ID (usually the user ID) -- `content` (required): Content to store -- `documentId` (optional): Document ID for grouping/upserting related memories -- `timestamp` (optional): ISO timestamp for when the memory occurred -- `context` (optional): Additional context about the memory -- `metadata` (optional): Key-value metadata for filtering - -**Example tool call:** -```typescript -{ - bankId: "user-123", - content: "Alice loves hiking and goes to Yosemite every summer", - context: "User preferences", - timestamp: "2024-01-15T10:30:00Z" -} -``` - -**Returns:** -```typescript -{ - success: true, - itemsCount: 1 -} -``` - -### `recall` - Search Memories - -The model calls this tool to search for relevant information in memory. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `query` (required): What to search for -- `types` (optional): Filter by fact types (`['world', 'experience', 'opinion']`) -- `maxTokens` (optional): Maximum tokens to return (default: 4096) -- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` -- `queryTimestamp` (optional): Query from a specific time (ISO format) -- `includeEntities` (optional): Include entity observations -- `includeChunks` (optional): Include raw document chunks - -**Example tool call:** -```typescript -{ - bankId: "user-123", - query: "What does Alice like to do outdoors?", - types: ["world", "experience"], - maxTokens: 2048, - budget: "mid" -} -``` - -**Returns:** -```typescript -{ - results: [ - { - id: "mem-123", - text: "Alice loves hiking", - type: "world", - entities: ["Alice"], - context: "User preferences", - occurred_start: "2024-01-15T10:30:00Z", - document_id: "doc-456", - metadata: { source: "chat" } - } - ], - entities: { - "Alice": { - canonical_name: "Alice", - mention_count: 15, - observations: [...] - } - } -} -``` - -### `reflect` - Synthesize Insights - -The model calls this tool to analyze memories and generate contextual insights. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `query` (required): Question to reflect on -- `context` (optional): Additional context for reflection -- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` - -**Example tool call:** -```typescript -{ - bankId: "user-123", - query: "What outdoor activities does Alice enjoy?", - context: "Planning a weekend trip", - budget: "mid" -} -``` - -**Returns:** -```typescript -{ - text: "Alice is an avid hiker who particularly enjoys visiting Yosemite National Park during summer months. She has expressed strong preferences for mountain trails over beach activities.", - basedOn: [ - { - id: "mem-123", - text: "Alice loves hiking", - type: "world", - context: "User preferences", - occurred_start: "2024-01-15T10:30:00Z" - } - ] -} -``` - -### `createMentalModel` - Create Knowledge Consolidation - -The model calls this tool to create a mental model that automatically consolidates memories into structured knowledge. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `mentalModelId` (optional): Custom ID for the mental model (auto-generated if not provided) -- `name` (optional): Name for the mental model -- `sourceQuery` (optional): Query defining which memories to consolidate -- `tags` (optional): Tags for organizing mental models -- `maxTokens` (optional): Maximum tokens for the content -- `autoRefresh` (optional): Auto-refresh after new consolidations (default: false) - -**Example tool call:** -```typescript -{ - bankId: "user-123", - name: "User Preferences", - sourceQuery: "What are the user's preferences?", - tags: ["preferences"], - autoRefresh: true -} -``` - -**Returns:** -```typescript -{ - mentalModelId: "mm-456", - createdAt: "2024-01-15T10:30:00Z" -} -``` - -### `queryMentalModel` - Retrieve Consolidated Knowledge - -The model calls this tool to retrieve synthesized insights from an existing mental model. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `mentalModelId` (required): ID of the mental model to query - -**Example tool call:** -```typescript -{ - bankId: "user-123", - mentalModelId: "mm-456" -} -``` - -**Returns:** -```typescript -{ - content: "The user prefers outdoor activities, particularly hiking. They enjoy mountain trails and visit Yosemite regularly during summer.", - name: "User Preferences", - updatedAt: "2024-01-20T15:45:00Z" -} -``` - -### `getDocument` - Retrieve Stored Document - -The model calls this tool to retrieve a stored document by its ID. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `documentId` (required): ID of the document to retrieve - -**Example tool call:** -```typescript -{ - bankId: "user-123", - documentId: "doc-789" -} -``` - -**Returns:** -```typescript -{ - originalText: "User profile: Alice, Software Engineer, loves hiking...", - id: "doc-789", - createdAt: "2024-01-10T09:00:00Z", - updatedAt: "2024-01-15T14:30:00Z" -} -``` - -### `createDirective` - Create Behavioral Rule - -The model calls this tool to create a directive—a hard rule injected into prompts during reflect operations. - -**Parameters:** -- `bankId` (required): Memory bank ID -- `name` (required): Human-readable name for the directive -- `content` (required): The directive text to inject -- `priority` (optional): Higher priority directives are injected first (default: 0) -- `isActive` (optional): Whether this directive is active (default: true) -- `tags` (optional): Tags for filtering (e.g., user-specific directives) - -**Example tool call:** -```typescript -{ - bankId: "user-123", - name: "Response Format", - content: "Always provide responses in bullet-point format", - priority: 10, - tags: ["formatting"] -} -``` - -**Returns:** -```typescript -{ - id: "dir-321", - name: "Response Format", - content: "Always provide responses in bullet-point format", - tags: ["formatting"], - createdAt: "2024-01-15T10:30:00Z" -} -``` - -## Usage Examples - -### Using with `generateText` - -```typescript -import { HindsightClient } from '@vectorize-io/hindsight-client'; -import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; -import { generateText } from 'ai'; -import { anthropic } from '@ai-sdk/anthropic'; - -const hindsightClient = new HindsightClient({ - apiUrl: 'http://localhost:8000', -}); - -const tools = createHindsightTools({ client: hindsightClient }); - -const result = await generateText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - system: `You are a helpful assistant with long-term memory. Use the recall tool to check for relevant memories before responding.`, - prompt: 'Remember that Alice loves hiking and prefers spicy food', -}); - -console.log(result.text); -``` - -### Using with `streamText` - -```typescript -import { streamText } from 'ai'; - -const result = streamText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - system: `You have persistent memory. Use retain to store important information and recall to retrieve it.`, - prompt: 'What do you know about Alice?', -}); - -for await (const chunk of result.textStream) { - process.stdout.write(chunk); -} -``` - -### Using with `ToolLoopAgent` - -```typescript -import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai'; - -const agent = new ToolLoopAgent({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - instructions: `You are a personal assistant with long-term memory. Always check recall before responding and use retain to store important information.`, - stopWhen: stepCountIs(10), -}); - -const result = await agent.generate({ - prompt: 'What did I say I wanted to work on this week?', -}); -``` - -### Multi-User Support - -```typescript -const result = await generateText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - system: `You are a helpful assistant. The user's ID is: ${userId}. Always pass this as the bankId parameter to memory tools.`, - prompt: 'Remember that I prefer dark mode', -}); -``` diff --git a/hindsight-docs/docs/sdks/integrations/ai-sdk.mdx b/hindsight-docs/docs/sdks/integrations/ai-sdk.mdx new file mode 100644 index 00000000..0f9ee62b --- /dev/null +++ b/hindsight-docs/docs/sdks/integrations/ai-sdk.mdx @@ -0,0 +1,97 @@ +--- +sidebar_position: 4 +--- + +# Vercel AI SDK + +The `@vectorize-io/hindsight-ai-sdk` package integrates [Hindsight](https://hindsight.vectorize.io) memory with the [Vercel AI SDK](https://ai-sdk.dev). It provides five ready-to-use tools for retaining, recalling, and reflecting on long-term memories. + +import CodeSnippet from '@site/src/components/CodeSnippet'; + +import aiSdkTs from '!!raw-loader!@site/examples/integrations/ai-sdk.ts'; + +## Installation + +```bash +npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai +``` + +## Setup + +Create a Hindsight client and pass it to `createHindsightTools` along with a `bankId`. The `bankId` identifies the memory store for this session—typically a user ID. + + + +:::tip Per-request bank IDs +In multi-user applications, create `tools` inside your request handler so each request closes over the correct `bankId`. See the [Next.js example](#in-a-nextjs-route-handler) below. +::: + +## Usage + +### With `generateText` + + + +### With `streamText` + + + +### With `ToolLoopAgent` + + + +### In a Next.js Route Handler + + + +--- + +## Tools Reference + +Five tools are registered. The `bankId` is fixed at creation time—the agent cannot change it. + +| Tool | What the agent provides | What the constructor controls | +|------|------------------------|-------------------------------| +| `retain` | `content`, `documentId`, `timestamp`, `context` | `async`, `tags`, `metadata` | +| `recall` | `query`, `queryTimestamp` | `budget`, `types`, `maxTokens`, `includeEntities`, `includeChunks` | +| `reflect` | `query`, `context` | `budget` | +| `getMentalModel` | `mentalModelId` | — | +| `getDocument` | `documentId` | — | + +**Why this split?** Semantic inputs (what to remember, what to search for) belong to the agent. Infrastructure concerns (cost budget, tagging strategy, async mode) belong to the application. + +--- + +## Constructor Options + +All options except `client` and `bankId` are optional. Each tool's options are grouped under the tool name. + + + +### `retain` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `async` | `boolean` | `false` | Fire-and-forget — do not wait for ingestion to complete | +| `tags` | `string[]` | — | Tags attached to every retained memory | +| `metadata` | `Record` | — | Metadata attached to every retained memory | +| `description` | `string` | built-in | Override the tool description shown to the model | + +### `recall` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `budget` | `'low' \| 'mid' \| 'high'` | `'mid'` | Controls retrieval depth and latency | +| `types` | `('world' \| 'experience' \| 'observation')[]` | all | Restrict results to these fact types | +| `maxTokens` | `number` | API default | Cap the total tokens returned | +| `includeEntities` | `boolean` | `false` | Include entity observations in results | +| `includeChunks` | `boolean` | `false` | Include raw source chunks in results | +| `description` | `string` | built-in | Override the tool description shown to the model | + +### `reflect` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `budget` | `'low' \| 'mid' \| 'high'` | `'mid'` | Controls synthesis depth and latency | +| `maxTokens` | `number` | API default | Maximum tokens for the response | +| `description` | `string` | built-in | Override the tool description shown to the model | diff --git a/hindsight-docs/examples/integrations/ai-sdk.ts b/hindsight-docs/examples/integrations/ai-sdk.ts new file mode 100644 index 00000000..1983feb0 --- /dev/null +++ b/hindsight-docs/examples/integrations/ai-sdk.ts @@ -0,0 +1,122 @@ +/** + * Hindsight AI SDK integration examples + * These snippets are embedded in the documentation via CodeSnippet. + */ + +// [docs:setup] +import { HindsightClient } from '@vectorize-io/hindsight-client'; +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; + +const client = new HindsightClient({ baseUrl: 'http://localhost:8888' }); + +const tools = createHindsightTools({ + client, + bankId: 'user-123', +}); +// [/docs:setup] + +// [docs:generate-text] +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const { text } = await generateText({ + model: openai('gpt-4o'), + tools, + maxSteps: 5, + system: 'You are a helpful assistant with long-term memory.', + prompt: 'Remember that I prefer dark mode and large fonts.', +}); +// [/docs:generate-text] + +// [docs:stream-text] +import { streamText } from 'ai'; + +const result = streamText({ + model: openai('gpt-4o'), + tools, + maxSteps: 5, + system: 'You are a helpful assistant with long-term memory.', + prompt: 'What are my display preferences?', +}); + +for await (const chunk of result.textStream) { + process.stdout.write(chunk); +} +// [/docs:stream-text] + +// [docs:tool-loop-agent] +import { generateText, ToolLoopAgent, stepCountIs } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { HindsightClient } from '@vectorize-io/hindsight-client'; +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; + +const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL! }); + +const agent = new ToolLoopAgent({ + model: openai('gpt-4o'), + tools: createHindsightTools({ client, bankId: 'user-123' }), + stopWhen: stepCountIs(10), + system: 'You are a helpful assistant with long-term memory.', +}); + +const result = await agent.generate({ + prompt: 'Remember that my favorite editor is Neovim', +}); +// [/docs:tool-loop-agent] + +// [docs:next-api-route] +// app/api/chat/route.ts +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { HindsightClient } from '@vectorize-io/hindsight-client'; +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; + +const hindsightClient = new HindsightClient({ + baseUrl: process.env.HINDSIGHT_API_URL!, +}); + +export async function POST(req: Request) { + const { messages, userId } = await req.json(); + + // Tools are created per-request, closing over the current user's bankId + const tools = createHindsightTools({ + client: hindsightClient, + bankId: userId, + }); + + return streamText({ + model: openai('gpt-4o'), + tools, + maxSteps: 5, + system: 'You are a helpful assistant with long-term memory.', + messages, + }).toDataStreamResponse(); +} +// [/docs:next-api-route] + +// [docs:constructor-options] +const tools = createHindsightTools({ + client, + bankId: userId, + + retain: { + async: true, // fire-and-forget (default: false) + tags: ['env:prod', 'app:support'], // always attached to every retained memory + metadata: { version: '2.0' }, // always attached to every retained memory + }, + + recall: { + budget: 'high', // processing depth: low | mid | high (default: 'mid') + types: ['experience', 'world'], // restrict to these fact types (default: all) + maxTokens: 2048, // cap token budget (default: API default) + includeEntities: true, // include entity observations (default: false) + includeChunks: true, // include raw source chunks (default: false) + }, + + reflect: { + budget: 'mid', // processing depth (default: 'mid') + }, + +}); + +// [/docs:constructor-options] diff --git a/hindsight-integrations/ai-sdk/package-lock.json b/hindsight-integrations/ai-sdk/package-lock.json index e0756474..20045054 100644 --- a/hindsight-integrations/ai-sdk/package-lock.json +++ b/hindsight-integrations/ai-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vectorize-io/hindsight-ai-sdk", - "version": "0.4.8", + "version": "0.4.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vectorize-io/hindsight-ai-sdk", - "version": "0.4.8", + "version": "0.4.11", "license": "MIT", "devDependencies": { "@types/node": "^22.0.0", diff --git a/hindsight-integrations/ai-sdk/src/tools/index.test.ts b/hindsight-integrations/ai-sdk/src/tools/index.test.ts index b431cda1..4df6a2fd 100644 --- a/hindsight-integrations/ai-sdk/src/tools/index.test.ts +++ b/hindsight-integrations/ai-sdk/src/tools/index.test.ts @@ -13,31 +13,34 @@ describe('createHindsightTools', () => { }); describe('tool creation', () => { - it('should create all three tools', () => { - const tools = createHindsightTools({ client: mockClient }); + it('should create all tools', () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); expect(tools).toHaveProperty('retain'); expect(tools).toHaveProperty('recall'); expect(tools).toHaveProperty('reflect'); + expect(tools).toHaveProperty('getMentalModel'); + expect(tools).toHaveProperty('getDocument'); expect(typeof tools.retain.execute).toBe('function'); expect(typeof tools.recall.execute).toBe('function'); expect(typeof tools.reflect.execute).toBe('function'); }); it('should use default descriptions when not provided', () => { - const tools = createHindsightTools({ client: mockClient }); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); expect(tools.retain.description).toContain('Store information in long-term memory'); expect(tools.recall.description).toContain('Search memory for relevant information'); expect(tools.reflect.description).toContain('Analyze memories to form insights'); }); - it('should use custom descriptions when provided', () => { + it('should use custom descriptions from nested options', () => { const tools = createHindsightTools({ client: mockClient, - retainDescription: 'Custom retain description', - recallDescription: 'Custom recall description', - reflectDescription: 'Custom reflect description', + bankId: 'test-bank', + retain: { description: 'Custom retain description' }, + recall: { description: 'Custom recall description' }, + reflect: { description: 'Custom reflect description' }, }); expect(tools.retain.description).toBe('Custom retain description'); @@ -47,8 +50,8 @@ describe('createHindsightTools', () => { }); describe('retain tool', () => { - it('should call client.retain with correct parameters', async () => { - const tools = createHindsightTools({ client: mockClient }); + it('should call client.retain with agent inputs and constructor defaults', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); vi.mocked(mockClient.retain).mockResolvedValue({ success: true, bank_id: 'test-bank', @@ -56,21 +59,21 @@ describe('createHindsightTools', () => { async: false, }); - const result = await tools.retain.execute({ - bankId: 'test-bank', - content: 'Test content', - }); + const result = await tools.retain.execute({ content: 'Test content' }); expect(mockClient.retain).toHaveBeenCalledWith('test-bank', 'Test content', { documentId: undefined, timestamp: undefined, context: undefined, + tags: undefined, + metadata: undefined, + async: false, }); expect(result).toEqual({ success: true, itemsCount: 5 }); }); - it('should pass optional parameters to client.retain', async () => { - const tools = createHindsightTools({ client: mockClient }); + it('should pass agent-provided optional inputs', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); vi.mocked(mockClient.retain).mockResolvedValue({ success: true, bank_id: 'test-bank', @@ -79,7 +82,6 @@ describe('createHindsightTools', () => { }); await tools.retain.execute({ - bankId: 'test-bank', content: 'Test content', documentId: 'doc-123', timestamp: '2024-01-01T00:00:00Z', @@ -90,100 +92,123 @@ describe('createHindsightTools', () => { documentId: 'doc-123', timestamp: '2024-01-01T00:00:00Z', context: 'Test context', + tags: undefined, + metadata: undefined, + async: false, }); }); - it('should transform response correctly', async () => { - const tools = createHindsightTools({ client: mockClient }); + it('should apply constructor-level retain options', async () => { + const tools = createHindsightTools({ + client: mockClient, + bankId: 'test-bank', + retain: { + async: true, + tags: ['env:prod', 'app:support'], + metadata: { version: '1.0' }, + }, + }); vi.mocked(mockClient.retain).mockResolvedValue({ success: true, bank_id: 'test-bank', - items_count: 10, - async: false, + items_count: 1, + async: true, }); - const result = await tools.retain.execute({ - bankId: 'test-bank', - content: 'Test content', - }); + await tools.retain.execute({ content: 'Test content' }); - expect(result).toEqual({ success: true, itemsCount: 10 }); + expect(mockClient.retain).toHaveBeenCalledWith('test-bank', 'Test content', { + documentId: undefined, + timestamp: undefined, + context: undefined, + tags: ['env:prod', 'app:support'], + metadata: { version: '1.0' }, + async: true, + }); }); }); describe('recall tool', () => { - it('should call client.recall with correct parameters', async () => { - const tools = createHindsightTools({ client: mockClient }); + it('should call client.recall with agent inputs and constructor defaults', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); vi.mocked(mockClient.recall).mockResolvedValue({ - results: [ - { - id: 'fact-1', - text: 'Test fact', - type: 'preference', - }, - ], + results: [{ id: 'fact-1', text: 'Test fact', type: 'preference' }], }); - const result = await tools.recall.execute({ - bankId: 'test-bank', - query: 'Test query', - }); + const result = await tools.recall.execute({ query: 'Test query' }); expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test query', { types: undefined, maxTokens: undefined, - budget: undefined, + budget: 'mid', queryTimestamp: undefined, - includeEntities: undefined, - includeChunks: undefined, + includeEntities: false, + includeChunks: false, }); expect(result.results).toHaveLength(1); expect(result.results[0].id).toBe('fact-1'); }); - it('should pass all optional parameters to client.recall', async () => { - const tools = createHindsightTools({ client: mockClient }); - vi.mocked(mockClient.recall).mockResolvedValue({ - results: [], - }); + it('should pass agent-provided queryTimestamp', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.recall).mockResolvedValue({ results: [] }); await tools.recall.execute({ - bankId: 'test-bank', query: 'Test query', - types: ['preference', 'fact'], - maxTokens: 1000, - budget: 'high', queryTimestamp: '2024-01-01T00:00:00Z', - includeEntities: true, - includeChunks: true, }); + expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test query', { + types: undefined, + maxTokens: undefined, + budget: 'mid', + queryTimestamp: '2024-01-01T00:00:00Z', + includeEntities: false, + includeChunks: false, + }); + }); + + it('should apply constructor-level recall options', async () => { + const tools = createHindsightTools({ + client: mockClient, + bankId: 'test-bank', + recall: { + types: ['preference', 'fact'], + maxTokens: 1000, + budget: 'high', + includeEntities: true, + includeChunks: true, + }, + }); + vi.mocked(mockClient.recall).mockResolvedValue({ results: [] }); + + await tools.recall.execute({ query: 'Test query' }); + expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test query', { types: ['preference', 'fact'], maxTokens: 1000, budget: 'high', - queryTimestamp: '2024-01-01T00:00:00Z', + queryTimestamp: undefined, includeEntities: true, includeChunks: true, }); }); it('should handle empty results', async () => { - const tools = createHindsightTools({ client: mockClient }); - vi.mocked(mockClient.recall).mockResolvedValue({ - results: undefined as any, - }); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.recall).mockResolvedValue({ results: undefined as any }); - const result = await tools.recall.execute({ - bankId: 'test-bank', - query: 'Test query', - }); + const result = await tools.recall.execute({ query: 'Test query' }); expect(result.results).toEqual([]); }); it('should include entities when present', async () => { - const tools = createHindsightTools({ client: mockClient }); + const tools = createHindsightTools({ + client: mockClient, + bankId: 'test-bank', + recall: { includeEntities: true }, + }); const entities = { 'entity-1': { entity_id: 'entity-1', @@ -191,59 +216,39 @@ describe('createHindsightTools', () => { observations: [{ text: 'Alice loves hiking' }], }, }; + vi.mocked(mockClient.recall).mockResolvedValue({ results: [], entities }); - vi.mocked(mockClient.recall).mockResolvedValue({ - results: [], - entities, - }); - - const result = await tools.recall.execute({ - bankId: 'test-bank', - query: 'Test query', - includeEntities: true, - }); + const result = await tools.recall.execute({ query: 'Test query' }); expect(result.entities).toEqual(entities); }); }); describe('reflect tool', () => { - it('should call client.reflect with correct parameters', async () => { - const tools = createHindsightTools({ client: mockClient }); + it('should call client.reflect with agent inputs and constructor defaults', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); vi.mocked(mockClient.reflect).mockResolvedValue({ text: 'Reflection result', - based_on: [ - { - id: 'fact-1', - text: 'Supporting fact', - }, - ], + based_on: [{ id: 'fact-1', text: 'Supporting fact' }], }); - const result = await tools.reflect.execute({ - bankId: 'test-bank', - query: 'What are my preferences?', - }); + const result = await tools.reflect.execute({ query: 'What are my preferences?' }); expect(mockClient.reflect).toHaveBeenCalledWith('test-bank', 'What are my preferences?', { context: undefined, - budget: undefined, + budget: 'mid', }); expect(result.text).toBe('Reflection result'); expect(result.basedOn).toHaveLength(1); }); - it('should pass optional parameters to client.reflect', async () => { - const tools = createHindsightTools({ client: mockClient }); - vi.mocked(mockClient.reflect).mockResolvedValue({ - text: 'Reflection result', - }); + it('should pass agent-provided context', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.reflect).mockResolvedValue({ text: 'Reflection result' }); await tools.reflect.execute({ - bankId: 'test-bank', query: 'What are my preferences?', context: 'User context', - budget: 'mid', }); expect(mockClient.reflect).toHaveBeenCalledWith('test-bank', 'What are my preferences?', { @@ -252,44 +257,43 @@ describe('createHindsightTools', () => { }); }); - it('should handle empty text response with fallback', async () => { - const tools = createHindsightTools({ client: mockClient }); - vi.mocked(mockClient.reflect).mockResolvedValue({ - text: undefined as any, - }); - - const result = await tools.reflect.execute({ + it('should apply constructor-level reflect budget', async () => { + const tools = createHindsightTools({ + client: mockClient, bankId: 'test-bank', - query: 'Test query', + reflect: { budget: 'low' }, }); + vi.mocked(mockClient.reflect).mockResolvedValue({ text: 'Reflection result' }); + + await tools.reflect.execute({ query: 'Test query' }); + + expect(mockClient.reflect).toHaveBeenCalledWith('test-bank', 'Test query', { + context: undefined, + budget: 'low', + }); + }); + + it('should handle empty text response with fallback', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.reflect).mockResolvedValue({ text: undefined as any }); + + const result = await tools.reflect.execute({ query: 'Test query' }); expect(result.text).toBe('No insights available yet.'); }); it('should include basedOn facts when present', async () => { - const tools = createHindsightTools({ client: mockClient }); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); const basedOn = [ - { - id: 'fact-1', - text: 'User prefers spicy food', - type: 'preference', - }, - { - id: 'fact-2', - text: 'User is allergic to nuts', - type: 'health', - }, + { id: 'fact-1', text: 'User prefers spicy food', type: 'preference' }, + { id: 'fact-2', text: 'User is allergic to nuts', type: 'health' }, ]; - vi.mocked(mockClient.reflect).mockResolvedValue({ text: 'Based on your history, you prefer spicy Asian cuisine', based_on: basedOn, }); - const result = await tools.reflect.execute({ - bankId: 'test-bank', - query: 'What do I like?', - }); + const result = await tools.reflect.execute({ query: 'What do I like?' }); expect(result.basedOn).toEqual(basedOn); }); @@ -297,66 +301,70 @@ describe('createHindsightTools', () => { describe('error handling', () => { it('should propagate errors from client.retain', async () => { - const tools = createHindsightTools({ client: mockClient }); - const error = new Error('Retain failed'); - vi.mocked(mockClient.retain).mockRejectedValue(error); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.retain).mockRejectedValue(new Error('Retain failed')); - await expect( - tools.retain.execute({ - bankId: 'test-bank', - content: 'Test content', - }) - ).rejects.toThrow('Retain failed'); + await expect(tools.retain.execute({ content: 'Test content' })).rejects.toThrow('Retain failed'); }); it('should propagate errors from client.recall', async () => { - const tools = createHindsightTools({ client: mockClient }); - const error = new Error('Recall failed'); - vi.mocked(mockClient.recall).mockRejectedValue(error); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.recall).mockRejectedValue(new Error('Recall failed')); - await expect( - tools.recall.execute({ - bankId: 'test-bank', - query: 'Test query', - }) - ).rejects.toThrow('Recall failed'); + await expect(tools.recall.execute({ query: 'Test query' })).rejects.toThrow('Recall failed'); }); it('should propagate errors from client.reflect', async () => { - const tools = createHindsightTools({ client: mockClient }); - const error = new Error('Reflect failed'); - vi.mocked(mockClient.reflect).mockRejectedValue(error); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.reflect).mockRejectedValue(new Error('Reflect failed')); - await expect( - tools.reflect.execute({ - bankId: 'test-bank', - query: 'Test query', - }) - ).rejects.toThrow('Reflect failed'); + await expect(tools.reflect.execute({ query: 'Test query' })).rejects.toThrow('Reflect failed'); }); }); - describe('budget schema', () => { - it('should accept valid budget values', async () => { - const tools = createHindsightTools({ client: mockClient }); + describe('budget defaults', () => { + it('should default recall budget to mid', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.recall).mockResolvedValue({ results: [] }); + + await tools.recall.execute({ query: 'Test' }); + + expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test', expect.objectContaining({ budget: 'mid' })); + }); + + it('should default reflect budget to mid', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank' }); + vi.mocked(mockClient.reflect).mockResolvedValue({ text: 'ok' }); + + await tools.reflect.execute({ query: 'Test' }); + + expect(mockClient.reflect).toHaveBeenCalledWith('test-bank', 'Test', expect.objectContaining({ budget: 'mid' })); + }); + + it('should accept low/mid/high budget values', async () => { vi.mocked(mockClient.recall).mockResolvedValue({ results: [] }); for (const budget of ['low', 'mid', 'high'] as const) { - await tools.recall.execute({ - bankId: 'test-bank', - query: 'Test', - budget, - }); - - expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test', { - types: undefined, - maxTokens: undefined, - budget, - queryTimestamp: undefined, - includeEntities: undefined, - includeChunks: undefined, - }); + const tools = createHindsightTools({ client: mockClient, bankId: 'test-bank', recall: { budget } }); + await tools.recall.execute({ query: 'Test' }); + expect(mockClient.recall).toHaveBeenCalledWith('test-bank', 'Test', expect.objectContaining({ budget })); } }); }); + + describe('bankId enforcement', () => { + it('should always use the bankId from constructor options', async () => { + const tools = createHindsightTools({ client: mockClient, bankId: 'forced-bank' }); + vi.mocked(mockClient.retain).mockResolvedValue({ + success: true, + bank_id: 'forced-bank', + items_count: 1, + async: false, + }); + + await tools.retain.execute({ content: 'Test' }); + + expect(mockClient.retain).toHaveBeenCalledWith('forced-bank', 'Test', expect.anything()); + }); + }); }); diff --git a/hindsight-integrations/ai-sdk/src/tools/index.ts b/hindsight-integrations/ai-sdk/src/tools/index.ts index 47fd8d5b..6102c537 100644 --- a/hindsight-integrations/ai-sdk/src/tools/index.ts +++ b/hindsight-integrations/ai-sdk/src/tools/index.ts @@ -7,6 +7,12 @@ import { z } from 'zod'; export const BudgetSchema = z.enum(['low', 'mid', 'high']); export type Budget = z.infer; +/** + * Fact types for filtering recall results. + */ +export const FactTypeSchema = z.enum(['world', 'experience', 'observation']); +export type FactType = z.infer; + /** * Recall result item from Hindsight */ @@ -105,15 +111,6 @@ export interface MentalModelResponse { trigger?: MentalModelTrigger; } -/** - * Create mental model response from Hindsight - */ -export interface CreateMentalModelResponse { - mental_model_id: string; - bank_id: string; - created_at: string; -} - /** * Document response from Hindsight */ @@ -128,36 +125,6 @@ export interface DocumentResponse { tags?: string[]; } -/** - * Directive response from Hindsight - */ -export interface DirectiveResponse { - id: string; - bank_id: string; - name: string; - content: string; - priority: number; - is_active: boolean; - tags: string[]; - created_at: string; - updated_at: string; -} - -/** - * Create directive response from Hindsight - */ -export interface CreateDirectiveResponse { - id: string; - bank_id: string; - name: string; - content: string; - priority: number; - is_active: boolean; - tags: string[]; - created_at: string; - updated_at: string; -} - /** * Hindsight client interface - matches @vectorize-io/hindsight-client */ @@ -179,7 +146,7 @@ export interface HindsightClient { bankId: string, query: string, options?: { - types?: string[]; + types?: FactType[]; maxTokens?: number; budget?: Budget; trace?: boolean; @@ -197,21 +164,10 @@ export interface HindsightClient { options?: { context?: string; budget?: Budget; + maxTokens?: number; } ): Promise; - createMentalModel( - bankId: string, - options?: { - id?: string; - name?: string; - sourceQuery?: string; - tags?: string[]; - maxTokens?: number; - trigger?: MentalModelTrigger; - } - ): Promise; - getMentalModel( bankId: string, mentalModelId: string @@ -221,148 +177,125 @@ export interface HindsightClient { bankId: string, documentId: string ): Promise; - - createDirective( - bankId: string, - options: { - name: string; - content: string; - priority?: number; - isActive?: boolean; - tags?: string[]; - } - ): Promise; - - listDirectives( - bankId: string, - options?: { - tags?: string[]; - tagsMatch?: 'any' | 'all' | 'exact'; - activeOnly?: boolean; - limit?: number; - offset?: number; - } - ): Promise<{ directives: DirectiveResponse[]; total: number }>; } export interface HindsightToolsOptions { /** Hindsight client instance */ client: HindsightClient; - /** - * Custom description for the retain tool. - */ - retainDescription?: string; - /** - * Custom description for the recall tool. - */ - recallDescription?: string; - /** - * Custom description for the reflect tool. - */ - reflectDescription?: string; - /** - * Custom description for the createMentalModel tool. - */ - createMentalModelDescription?: string; - /** - * Custom description for the queryMentalModel tool. - */ - queryMentalModelDescription?: string; - /** - * Custom description for the getDocument tool. - */ - getDocumentDescription?: string; + /** Memory bank ID to use for all tool calls (e.g. the user ID) */ + bankId: string; + + /** Options for the retain tool */ + retain?: { + /** Fire-and-forget retain without waiting for completion (default: false) */ + async?: boolean; + /** Tags always attached to every retained memory (default: undefined) */ + tags?: string[]; + /** Metadata always attached to every retained memory (default: undefined) */ + metadata?: Record; + /** Custom tool description */ + description?: string; + }; + + /** Options for the recall tool */ + recall?: { + /** Restrict results to these fact types: 'world', 'experience', 'observation' (default: undefined = all types) */ + types?: FactType[]; + /** Maximum tokens to return (default: undefined = API default) */ + maxTokens?: number; + /** Processing budget controlling latency vs. depth (default: 'mid') */ + budget?: Budget; + /** Include entity observations in results (default: false) */ + includeEntities?: boolean; + /** Include raw source chunks in results (default: false) */ + includeChunks?: boolean; + /** Custom tool description */ + description?: string; + }; + + /** Options for the reflect tool */ + reflect?: { + /** Processing budget controlling latency vs. depth (default: 'mid') */ + budget?: Budget; + /** Maximum tokens for the response (default: undefined = API default) */ + maxTokens?: number; + /** Custom tool description */ + description?: string; + }; + + /** Options for the getMentalModel tool */ + getMentalModel?: { + /** Custom tool description */ + description?: string; + }; + + /** Options for the getDocument tool */ + getDocument?: { + /** Custom tool description */ + description?: string; + }; } /** * Creates AI SDK tools for Hindsight memory operations. * - * Features: - * - Dynamic bank ID per call (supports multi-user/multi-bank scenarios) - * - Full API parameter support for retain, recall, and reflect - * - Ready to use with streamText, generateText, or ToolLoopAgent + * The bank ID and all infrastructure concerns (budget, tags, async mode, etc.) + * are fixed at creation time. The agent only controls semantic inputs: + * content, queries, names, and timestamps. * * @example * ```ts * const tools = createHindsightTools({ * client: hindsightClient, + * bankId: userId, + * recall: { budget: 'high', includeEntities: true }, + * retain: { async: true, tags: ['env:prod'] }, * }); * - * // Use with AI SDK * const result = await generateText({ - * model: openai('gpt-4'), + * model: openai('gpt-4o'), * tools, - * prompt: 'Remember that Alice loves hiking', + * messages, * }); * ``` */ export function createHindsightTools({ client, - retainDescription, - recallDescription, - reflectDescription, - createMentalModelDescription, - queryMentalModelDescription, - getDocumentDescription, + bankId, + retain: retainOpts = {}, + recall: recallOpts = {}, + reflect: reflectOpts = {}, + getMentalModel: getMentalModelOpts = {}, + getDocument: getDocumentOpts = {}, }: HindsightToolsOptions) { + // Agent-controlled params only: content, timestamp, documentId, context const retainParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), content: z.string().describe('Content to store in memory'), documentId: z.string().optional().describe('Optional document ID for grouping/upserting content'), timestamp: z.string().optional().describe('Optional ISO timestamp for when the memory occurred'), context: z.string().optional().describe('Optional context about the memory'), - tags: z.array(z.string()).optional().describe('Optional tags for visibility scoping'), - metadata: z.record(z.string(), z.string()).optional().describe('Optional user-defined metadata'), }); + // Agent-controlled params only: query, queryTimestamp const recallParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), query: z.string().describe('What to search for in memory'), - types: z.array(z.string()).optional().describe('Filter by fact types'), - maxTokens: z.number().optional().describe('Maximum tokens to return'), - budget: BudgetSchema.optional().describe('Processing budget: low, mid, or high'), queryTimestamp: z.string().optional().describe('Query from a specific point in time (ISO format)'), - includeEntities: z.boolean().optional().describe('Include entity observations in results'), - includeChunks: z.boolean().optional().describe('Include raw chunks in results'), }); + // Agent-controlled params only: query, context const reflectParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), query: z.string().describe('Question to reflect on based on memories'), context: z.string().optional().describe('Additional context for the reflection'), - budget: BudgetSchema.optional().describe('Processing budget: low, mid, or high'), }); - const createMentalModelParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), - mentalModelId: z.string().optional().describe('Optional custom ID for the mental model (auto-generated if not provided)'), - name: z.string().optional().describe('Optional name for the mental model'), - sourceQuery: z.string().optional().describe('Query to define what memories to consolidate'), - tags: z.array(z.string()).optional().describe('Optional tags for organizing mental models'), - maxTokens: z.number().optional().describe('Maximum tokens for the mental model content'), - autoRefresh: z.boolean().optional().describe('Auto-refresh mental model after new consolidations (default: false)'), - }); - - const queryMentalModelParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), - mentalModelId: z.string().describe('ID of the mental model to query'), + const getMentalModelParams = z.object({ + mentalModelId: z.string().describe('ID of the mental model to retrieve'), }); const getDocumentParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), documentId: z.string().describe('ID of the document to retrieve'), }); - const createDirectiveParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), - name: z.string().describe('Human-readable name for the directive'), - content: z.string().describe('The directive text to inject into prompts'), - priority: z.number().optional().describe('Higher priority directives are injected first (default 0)'), - isActive: z.boolean().optional().describe('Whether this directive is active (default true)'), - tags: z.array(z.string()).optional().describe('Tags for filtering'), - }); - - type RetainInput = z.infer; type RetainOutput = { success: boolean; itemsCount: number }; @@ -372,37 +305,31 @@ export function createHindsightTools({ type ReflectInput = z.infer; type ReflectOutput = { text: string; basedOn?: ReflectFact[] }; - type CreateMentalModelInput = z.infer; - type CreateMentalModelOutput = { mentalModelId: string; createdAt: string }; - - type QueryMentalModelInput = z.infer; - type QueryMentalModelOutput = { content: string; name?: string; updatedAt: string }; + type GetMentalModelInput = z.infer; + type GetMentalModelOutput = { content: string; name?: string; updatedAt: string }; type GetDocumentInput = z.infer; type GetDocumentOutput = { originalText: string; id: string; createdAt: string; updatedAt: string } | null; - type CreateDirectiveInput = z.infer; - type CreateDirectiveOutput = { id: string; name: string; content: string; tags: string[]; createdAt: string }; - return { retain: tool({ description: - retainDescription ?? + retainOpts.description ?? `Store information in long-term memory. Use this when information should be remembered for future interactions, such as user preferences, facts, experiences, or important context.`, inputSchema: retainParams, execute: async (input) => { console.log('[AI SDK Tool] Retain input:', { - bankId: input.bankId, + bankId, documentId: input.documentId, - tags: input.tags, hasContent: !!input.content, }); - const result = await client.retain(input.bankId, input.content, { + const result = await client.retain(bankId, input.content, { documentId: input.documentId, timestamp: input.timestamp, context: input.context, - tags: input.tags, - metadata: input.metadata as Record | undefined, + tags: retainOpts.tags, + metadata: retainOpts.metadata, + async: retainOpts.async ?? false, }); return { success: result.success, itemsCount: result.items_count }; }, @@ -410,17 +337,17 @@ export function createHindsightTools({ recall: tool({ description: - recallDescription ?? + recallOpts.description ?? `Search memory for relevant information. Use this to find previously stored information that can help personalize responses or provide context.`, inputSchema: recallParams, execute: async (input) => { - const result = await client.recall(input.bankId, input.query, { - types: input.types, - maxTokens: input.maxTokens, - budget: input.budget, + const result = await client.recall(bankId, input.query, { + types: recallOpts.types, + maxTokens: recallOpts.maxTokens, + budget: recallOpts.budget ?? 'mid', queryTimestamp: input.queryTimestamp, - includeEntities: input.includeEntities, - includeChunks: input.includeChunks, + includeEntities: recallOpts.includeEntities ?? false, + includeChunks: recallOpts.includeChunks ?? false, }); return { results: result.results ?? [], @@ -431,13 +358,14 @@ export function createHindsightTools({ reflect: tool({ description: - reflectDescription ?? + reflectOpts.description ?? `Analyze memories to form insights and generate contextual answers. Use this to understand patterns, synthesize information, or answer questions that require reasoning over stored memories.`, inputSchema: reflectParams, execute: async (input) => { - const result = await client.reflect(input.bankId, input.query, { + const result = await client.reflect(bankId, input.query, { context: input.context, - budget: input.budget, + budget: reflectOpts.budget ?? 'mid', + maxTokens: reflectOpts.maxTokens, }); return { text: result.text ?? 'No insights available yet.', @@ -446,34 +374,13 @@ export function createHindsightTools({ }, }), - createMentalModel: tool({ + getMentalModel: tool({ description: - createMentalModelDescription ?? - `Create a mental model that automatically consolidates memories into structured knowledge. Mental models are continuously updated as new memories are added, making them ideal for maintaining up-to-date user preferences, behavioral patterns, and accumulated wisdom.`, - inputSchema: createMentalModelParams, + getMentalModelOpts.description ?? + `Retrieve a mental model to get consolidated knowledge synthesized from memories. Mental models provide synthesized insights that are faster and more efficient to retrieve than searching through raw memories.`, + inputSchema: getMentalModelParams, execute: async (input) => { - const result = await client.createMentalModel(input.bankId, { - id: input.mentalModelId, - name: input.name, - sourceQuery: input.sourceQuery, - tags: input.tags, - maxTokens: input.maxTokens, - trigger: input.autoRefresh !== undefined ? { refresh_after_consolidation: input.autoRefresh } : undefined, - }); - return { - mentalModelId: result.mental_model_id, - createdAt: result.created_at, - }; - }, - }), - - queryMentalModel: tool({ - description: - queryMentalModelDescription ?? - `Query an existing mental model to retrieve consolidated knowledge. Mental models provide synthesized insights from memories, making them faster and more efficient than searching through raw memories.`, - inputSchema: queryMentalModelParams, - execute: async (input) => { - const result = await client.getMentalModel(input.bankId, input.mentalModelId); + const result = await client.getMentalModel(bankId, input.mentalModelId); return { content: result.content ?? 'No content available yet.', name: result.name, @@ -484,11 +391,11 @@ export function createHindsightTools({ getDocument: tool({ description: - getDocumentDescription ?? + getDocumentOpts.description ?? `Retrieve a stored document by its ID. Documents are used to store structured data like application state, user profiles, or any data that needs exact retrieval.`, inputSchema: getDocumentParams, execute: async (input) => { - const result = await client.getDocument(input.bankId, input.documentId); + const result = await client.getDocument(bankId, input.documentId); if (!result) { return null; } @@ -501,27 +408,6 @@ export function createHindsightTools({ }, }), - createDirective: tool({ - description: - `Create a directive - a hard rule that is injected into prompts during reflect operations. Directives are explicit instructions that guide agent behavior. Use tags to control when directives are applied (e.g., user-specific directives with 'user:username' tags).`, - inputSchema: createDirectiveParams, - execute: async (input) => { - const result = await client.createDirective(input.bankId, { - name: input.name, - content: input.content, - priority: input.priority, - isActive: input.isActive, - tags: input.tags, - }); - return { - id: result.id, - name: result.name, - content: result.content, - tags: result.tags, - createdAt: result.created_at, - }; - }, - }), }; }