improve docker and mcp

This commit is contained in:
Nicolò Boschi 2025-12-04 15:38:55 +01:00
parent e0cfec1666
commit b83fd3a5c3
10 changed files with 635 additions and 644 deletions

View file

@ -159,6 +159,15 @@ RUN pg0 start --help && \
ENV PG0_HOME=/home/hindsight/.pg0
# Pre-download ML models to avoid runtime download
RUN /app/api/.venv/bin/python -c "\
from sentence_transformers import SentenceTransformer, CrossEncoder; \
print('Downloading embedding model...'); \
SentenceTransformer('BAAI/bge-small-en-v1.5'); \
print('Downloading cross-encoder model...'); \
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2'); \
print('Models cached successfully')"
EXPOSE 8888
ENV HINDSIGHT_API_HOST=0.0.0.0
@ -282,6 +291,15 @@ RUN pg0 start --help && \
ENV PG0_HOME=/home/hindsight/.pg0
# Pre-download ML models to avoid runtime download
RUN /app/api/.venv/bin/python -c "\
from sentence_transformers import SentenceTransformer, CrossEncoder; \
print('Downloading embedding model...'); \
SentenceTransformer('BAAI/bge-small-en-v1.5'); \
print('Downloading cross-encoder model...'); \
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2'); \
print('Models cached successfully')"
EXPOSE 8888 9999
ENV HINDSIGHT_API_HOST=0.0.0.0

View file

@ -62,14 +62,13 @@ def create_app(
# Mount MCP server if enabled
if mcp_api_enabled:
try:
from .mcp import create_mcp_server
from .mcp import create_mcp_app
# Create MCP server with shared memory instance
mcp_server = create_mcp_server(memory=memory)
# Mount at specified path using sse_app for compatibility with mcp-remote
app.mount(mcp_mount_path, mcp_server.sse_app())
logger.info(f"MCP server enabled at {mcp_mount_path}")
# Create MCP app with dynamic bank_id support
# Supports: /mcp/{bank_id}/sse (bank-specific SSE endpoint)
mcp_app = create_mcp_app(memory=memory)
app.mount(mcp_mount_path, mcp_app)
logger.info(f"MCP server enabled at {mcp_mount_path}/{{bank_id}}/sse")
except ImportError as e:
logger.error(f"MCP server requested but dependencies not available: {e}")
logger.error("Install with: pip install hindsight-api[mcp]")

View file

@ -3,6 +3,8 @@
import json
import logging
import os
from contextvars import ContextVar
from typing import Optional
from fastmcp import FastMCP
from hindsight_api import MemoryEngine
@ -17,6 +19,14 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
# Context variable to hold the current bank_id from the URL path
_current_bank_id: ContextVar[Optional[str]] = ContextVar("current_bank_id", default=None)
def get_current_bank_id() -> Optional[str]:
"""Get the current bank_id from context (set from URL path)."""
return _current_bank_id.get()
def create_mcp_server(memory: MemoryEngine) -> FastMCP:
"""
@ -28,125 +38,71 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
Returns:
Configured FastMCP server instance
"""
# Create FastMCP server
mcp = FastMCP("hindsight-mcp-server")
@mcp.tool()
async def hindsight_put(bank_id: str, content: str, context: str, explanation: str = "") -> str:
async def retain(content: str, context: str = "general") -> str:
"""
**CRITICAL: Store important user information to long-term memory.**
** PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
- This tool is STRICTLY per-user. Each user MUST have a unique `bank_id`.
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `bank_id`.
- DO NOT use this tool if you cannot identify the specific user.
- DO NOT share memories between different users - each user's memories are isolated by their `bank_id`.
- If you don't have a user identifier, DO NOT use this tool at all.
Store important information to long-term memory.
Use this tool PROACTIVELY whenever the user shares:
- Personal facts, preferences, or interests (e.g., "I love hiking", "I'm a vegetarian")
- Important events or milestones (e.g., "I got promoted", "My birthday is June 15")
- User history, experiences, or background (e.g., "I used to work at Google", "I studied CS at MIT")
- Decisions, opinions, or stated preferences (e.g., "I prefer Python over JavaScript")
- Goals, plans, or future intentions (e.g., "I'm planning to visit Japan next year")
- Relationships or people mentioned (e.g., "My manager Sarah", "My wife Alice")
- Personal facts, preferences, or interests
- Important events or milestones
- User history, experiences, or background
- Decisions, opinions, or stated preferences
- Goals, plans, or future intentions
- Relationships or people mentioned
- Work context, projects, or responsibilities
- Any other information the user would want remembered for future conversations
**When to use**: Immediately after user shares personal information. Don't ask permission - just store it naturally.
**Context guidelines**: Use descriptive contexts like "personal_preferences", "work_history", "family", "hobbies",
"career_goals", "project_details", etc. This helps organize and retrieve related memories later.
Args:
bank_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
This MUST be consistent across all interactions with the same user.
Example: "user_12345", "alice@example.com", "session_abc123"
content: The fact/memory to store (be specific and include relevant details)
context: Categorize the memory (e.g., 'personal_preferences', 'work_history', 'hobbies', 'family')
explanation: Optional explanation for why this memory is being stored
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
"""
try:
# Log explanation if provided
if explanation:
pass # Explanation provided
# Store memory using put_batch_async
bank_id = get_current_bank_id()
await memory.put_batch_async(
bank_id=bank_id,
contents=[{"content": content, "context": context}]
)
return f"Fact stored successfully"
return "Memory stored successfully"
except Exception as e:
logger.error(f"Error storing fact: {e}", exc_info=True)
logger.error(f"Error storing memory: {e}", exc_info=True)
return f"Error: {str(e)}"
@mcp.tool()
async def hindsight_search(bank_id: str, query: str, max_tokens: int = 4096, explanation: str = "") -> str:
async def recall(query: str, max_results: int = 10) -> str:
"""
**CRITICAL: Search user's memory to provide personalized, context-aware responses.**
Search memories to provide personalized, context-aware responses.
** PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
- This tool is STRICTLY per-user. Each user MUST have a unique `bank_id`.
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `bank_id`.
- DO NOT use this tool if you cannot identify the specific user.
- DO NOT search across multiple users - each user's memories are isolated by their `bank_id`.
- If you don't have a user identifier, DO NOT use this tool at all.
Use this tool PROACTIVELY at the start of conversations or when making recommendations to:
- Check user's preferences before making suggestions (e.g., "what foods does the user like?")
- Recall user's history to provide continuity (e.g., "what projects has the user worked on?")
- Remember user's goals and context (e.g., "what is the user trying to accomplish?")
- Avoid repeating information or asking questions you should already know
- Personalize responses based on user's background, interests, and past interactions
- Reference past conversations or events the user mentioned
**When to use**:
- Start of conversation: Search for relevant context about the user
- Before recommendations: Check user preferences and past experiences
- When user asks about something they may have mentioned before
- To provide continuity across conversations
**Search tips**: Use natural language queries like "user's programming language preferences",
"user's work experience", "user's dietary restrictions", "what does the user know about X?"
Use this tool PROACTIVELY to:
- Check user's preferences before making suggestions
- Recall user's history to provide continuity
- Remember user's goals and context
- Personalize responses based on past interactions
Args:
bank_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
This MUST be consistent across all interactions with the same user.
Example: "user_12345", "alice@example.com", "session_abc123"
query: Natural language search query to find relevant memories
max_tokens: Maximum tokens for search context (default: 4096)
explanation: Optional explanation for why this search is being performed
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
max_results: Maximum number of results to return (default: 10)
"""
try:
# Log all parameters for debugging
logger.info(f"hindsight_search called with: query={query!r}, max_tokens={max_tokens}, explanation={explanation!r}")
# Log explanation if provided
if explanation:
pass # Explanation provided
# Search using recall_async
bank_id = get_current_bank_id()
from hindsight_api.engine.memory_engine import Budget
search_result = await memory.recall_async(
bank_id=bank_id,
query=query,
fact_type=["world", "bank", "opinion"], # Search all fact types
max_tokens=max_tokens,
fact_type=["world", "bank", "opinion"],
budget=Budget.LOW
)
# Convert results to dict format
results = [
{
"id": fact.id,
"text": fact.text,
"type": fact.fact_type,
"context": fact.context,
"event_date": fact.event_date, # Already a string from the database
"document_id": fact.document_id
"event_date": fact.event_date,
}
for fact in search_result.results
for fact in search_result.results[:max_results]
]
return json.dumps({"results": results}, indent=2)
@ -155,3 +111,104 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
return json.dumps({"error": str(e), "results": []})
return mcp
class MCPMiddleware:
"""ASGI middleware that extracts bank_id from path and sets context."""
def __init__(self, app, memory: MemoryEngine):
self.app = app
self.memory = memory
self.mcp_server = create_mcp_server(memory)
# Use sse_app - http_app requires lifespan management that's complex with middleware
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
self.mcp_app = self.mcp_server.sse_app()
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.mcp_app(scope, receive, send)
return
path = scope.get("path", "")
# Strip any mount prefix (e.g., /mcp) that FastAPI might not have stripped
root_path = scope.get("root_path", "")
if root_path and path.startswith(root_path):
path = path[len(root_path):] or "/"
# Also handle case where mount path wasn't stripped (e.g., /mcp/...)
if path.startswith("/mcp/"):
path = path[4:] # Remove /mcp prefix
# Extract bank_id from path: /{bank_id}/ or /{bank_id}
# http_app expects requests at /
if not path.startswith("/") or len(path) <= 1:
# No bank_id in path - return error
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
return
# Extract bank_id from first path segment
parts = path[1:].split("/", 1)
if not parts[0]:
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
return
bank_id = parts[0]
new_path = "/" + parts[1] if len(parts) > 1 else "/"
# Set bank_id context
token = _current_bank_id.set(bank_id)
try:
new_scope = scope.copy()
new_scope["path"] = new_path
# Wrap send to rewrite the SSE endpoint URL to include bank_id
# The SSE app sends "event: endpoint\ndata: /messages\n" but we need
# the client to POST to /{bank_id}/messages instead
async def send_wrapper(message):
if message["type"] == "http.response.body":
body = message.get("body", b"")
if body and b"/messages" in body:
# Rewrite /messages to /{bank_id}/messages in SSE endpoint event
body = body.replace(
b"data: /messages",
f"data: /{bank_id}/messages".encode()
)
message = {**message, "body": body}
await send(message)
await self.mcp_app(new_scope, receive, send_wrapper)
finally:
_current_bank_id.reset(token)
async def _send_error(self, send, status: int, message: str):
"""Send an error response."""
body = json.dumps({"error": message}).encode()
await send({
"type": "http.response.start",
"status": status,
"headers": [(b"content-type", b"application/json")],
})
await send({
"type": "http.response.body",
"body": body,
})
def create_mcp_app(memory: MemoryEngine):
"""
Create an ASGI app that handles MCP requests.
URL pattern: /mcp/{bank_id}/
The bank_id is extracted from the URL path and made available to tools.
Args:
memory: MemoryEngine instance
Returns:
ASGI application
"""
return MCPMiddleware(None, memory)

View file

@ -10,13 +10,9 @@ import warnings
warnings.filterwarnings("ignore", message="websockets.legacy is deprecated")
warnings.filterwarnings("ignore", message="websockets.server.WebSocketServerProtocol is deprecated")
import asyncio
import atexit
import logging
import os
import argparse
import signal
import sys
from hindsight_api import MemoryEngine
from hindsight_api.api import create_app
@ -25,36 +21,6 @@ from hindsight_api.api import create_app
os.environ["TOKENIZERS_PARALLELISM"] = "false"
def _cleanup_pg0():
"""Synchronous cleanup function to stop pg0 on exit."""
global _memory
if _memory is not None and _memory._pg0 is not None:
try:
# Run async stop in a new event loop
loop = asyncio.new_event_loop()
loop.run_until_complete(_memory._pg0.stop())
loop.close()
print("\npg0 stopped.")
except Exception as e:
print(f"\nError stopping pg0: {e}")
# Register cleanup on normal exit
atexit.register(_cleanup_pg0)
def _signal_handler(signum, frame):
"""Handle SIGINT/SIGTERM to ensure pg0 cleanup."""
print(f"\nReceived signal {signum}, shutting down...")
_cleanup_pg0()
sys.exit(0)
# Register signal handlers for graceful shutdown
signal.signal(signal.SIGINT, _signal_handler)
signal.signal(signal.SIGTERM, _signal_handler)
# Create app at module level (required for uvicorn import string)
_memory = MemoryEngine(
db_url=os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0"),

View file

@ -6,30 +6,35 @@ sidebar_position: 3
Model Context Protocol (MCP) tools exposed by the Hindsight MCP server.
## Endpoint
```
/mcp/{bank_id}/sse
```
The `bank_id` is extracted from the URL path and used for all tool operations. The MCP server uses Server-Sent Events (SSE) transport.
## Available Tools
### hindsight_put
### retain
Store a new memory for a user.
Store a new memory.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `bank_id` | string | yes | Unique identifier for the user (e.g., user_id, email) |
| `content` | string | yes | Memory content to store |
| `context` | string | yes | Category for the memory (e.g., 'personal_preferences', 'work_history') |
| `explanation` | string | no | Optional explanation for why this memory is being stored |
| `context` | string | no | Category for the memory (default: 'general') |
**Example:**
```json
{
"name": "hindsight_put",
"name": "retain",
"arguments": {
"bank_id": "user_12345",
"content": "User prefers Python for data analysis",
"context": "programming_preferences"
"context": "preferences"
}
}
```
@ -37,31 +42,28 @@ Store a new memory for a user.
**Response:**
```
Fact stored successfully
Memory stored successfully
```
---
### hindsight_search
### recall
Search memories for a user.
Search memories.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `bank_id` | string | yes | Unique identifier for the user (e.g., user_id, email) |
| `query` | string | yes | Natural language search query |
| `max_tokens` | integer | no | Maximum tokens for results (default: 4096) |
| `explanation` | string | no | Optional explanation for why this search is being performed |
| `max_results` | integer | no | Maximum results to return (default: 10) |
**Example:**
```json
{
"name": "hindsight_search",
"name": "recall",
"arguments": {
"bank_id": "user_12345",
"query": "What does the user do for work?"
}
}
@ -76,9 +78,8 @@ Search memories for a user.
"id": "550e8400-e29b-41d4-a716-446655440000",
"text": "User works at Google as a software engineer",
"type": "world",
"context": "work_history",
"event_date": null,
"document_id": null
"context": "work",
"event_date": null
}
]
}
@ -88,30 +89,12 @@ Search memories for a user.
## Usage Guidelines
The MCP tools are designed for **per-user memory**:
- Each user MUST have a unique `bank_id` (user ID, email, session ID, etc.)
- Memories are isolated by `bank_id` — users cannot access each other's memories
- Use consistent `bank_id` values across all interactions with the same user
**When to use `hindsight_put`:**
**When to use `retain`:**
- User shares personal facts, preferences, or interests
- Important events or milestones are mentioned
- Decisions, opinions, or goals are stated
- Any information the user would want remembered
**When to use `hindsight_search`:**
**When to use `recall`:**
- Start of conversation to get user context
- Before making recommendations
- To provide continuity across conversations
- When user asks about something they may have mentioned before
---
## Error Responses
MCP tools return errors as strings:
```
Error: Memory bank 'unknown-bank' not found
```

View file

@ -6,12 +6,11 @@ Hindsight uses several machine learning models for different tasks.
| Model Type | Purpose | Default | Configurable |
|------------|---------|---------|--------------|
| **Embedding** | Vector representations for semantic search | `all-MiniLM-L6-v2` | Yes |
| **Embedding** | Vector representations for semantic search | `BAAI/bge-small-en-v1.5` | Yes |
| **Cross-Encoder** | Reranking search results | `ms-marco-MiniLM-L-6-v2` | Yes |
| **Temporal Parser** | Understanding time expressions | `t5-small` | Yes |
| **LLM** | Fact extraction, reasoning, generation | Provider-specific | Yes |
All local models (embedding, cross-encoder, temporal) are automatically downloaded from HuggingFace on first run.
All local models (embedding, cross-encoder) are automatically downloaded from HuggingFace on first run.
---
@ -19,20 +18,20 @@ All local models (embedding, cross-encoder, temporal) are automatically download
Converts text into dense vector representations for semantic similarity search.
**Default:** `sentence-transformers/all-MiniLM-L6-v2` (384 dimensions, ~90MB)
**Default:** `BAAI/bge-small-en-v1.5` (384 dimensions, ~130MB)
**Alternatives:**
| Model | Dimensions | Use Case |
|-------|------------|----------|
| `all-MiniLM-L6-v2` | 384 | Default, fast, good quality |
| `all-mpnet-base-v2` | 768 | Higher accuracy, slower |
| `paraphrase-multilingual-MiniLM-L12-v2` | 384 | Multilingual (50+ languages) |
| `BAAI/bge-small-en-v1.5` | 384 | Default, fast, good quality |
| `BAAI/bge-base-en-v1.5` | 768 | Higher accuracy, slower |
| `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | 384 | Multilingual (50+ languages) |
**Configuration:**
```bash
export HINDSIGHT_API_EMBEDDING_MODEL=sentence-transformers/all-mpnet-base-v2
export HINDSIGHT_API_EMBEDDING_MODEL=BAAI/bge-base-en-v1.5
export HINDSIGHT_API_EMBEDDING_DEVICE=cuda # or mps for Apple Silicon
export HINDSIGHT_API_EMBEDDING_BATCH_SIZE=64
```
@ -63,31 +62,6 @@ export HINDSIGHT_API_RERANK_ENABLED=true # Set to false to disable
---
## Temporal Parser
Parses natural language time expressions into structured dates.
**Examples:**
- "last spring" → 2024-03-20 to 2024-06-20
- "two weeks ago" → calculated date range
**Default:** `google/t5-small` (~240MB)
**Alternatives:**
| Model | Use Case |
|-------|----------|
| `t5-small` | Default, compact |
| `t5-base` | Better accuracy for complex expressions |
**Configuration:**
```bash
export HINDSIGHT_API_TEMPORAL_MODEL=google/t5-base
```
---
## LLM
Used for fact extraction, entity resolution, opinion generation, and answer synthesis.

View file

@ -4,7 +4,7 @@ sidebar_position: 3
# CLI Reference
The Hindsight CLI provides command-line access to memory operations and agent management.
The Hindsight CLI provides command-line access to memory operations and bank management.
## Installation
@ -14,228 +14,194 @@ curl -fsSL https://raw.githubusercontent.com/vectorize-io/hindsight/refs/heads/m
## Configuration
Set environment variables or use command flags:
Configure the API URL:
```bash
# Interactive configuration
hindsight configure
# Or set directly
hindsight configure --api-url http://localhost:8888
# Or use environment variable (highest priority)
export HINDSIGHT_API_URL=http://localhost:8888
export HINDSIGHT_AGENT_ID=my-agent
```
## Commands
## Core Commands
### Memory Operations
### Retain (Store Memory)
#### put
Store a memory:
Store a single memory:
```bash
hindsight put <agent_id> "Alice works at Google as a software engineer"
hindsight memory retain <bank_id> "Alice works at Google as a software engineer"
# With context
hindsight put <agent_id> "Bob loves hiking" --context "hobby discussion"
hindsight memory retain <bank_id> "Bob loves hiking" --context "hobby discussion"
# With event date
hindsight put <agent_id> "Meeting with Carol" --date "2024-01-15"
# Queue for background processing
hindsight memory retain <bank_id> "Meeting notes" --async
```
#### put-files
### Retain Files
Store file contents as memories:
Bulk import from files:
```bash
# Store a single file
hindsight put-files <agent_id> notes.txt
# Single file
hindsight memory retain-files <bank_id> notes.txt
# Store multiple files
hindsight put-files <agent_id> file1.txt file2.md file3.json
# Directory (recursive by default)
hindsight memory retain-files <bank_id> ./documents/
# With context
hindsight put-files <agent_id> meeting-notes.txt --context "team meeting"
hindsight memory retain-files <bank_id> meeting-notes.txt --context "team meeting"
# Background processing
hindsight memory retain-files <bank_id> ./data/ --async
```
#### search
### Recall (Search)
Search memories:
Search memories using semantic similarity:
```bash
hindsight search <agent_id> "What does Alice do?"
hindsight memory recall <bank_id> "What does Alice do?"
# With options
hindsight search <agent_id> "hiking recommendations" --budget 100 --top-k 5
hindsight memory recall <bank_id> "hiking recommendations" \
--budget high \
--max-tokens 8192
# Verbose output
hindsight search <agent_id> "query" -v
# Filter by fact type
hindsight memory recall <bank_id> "query" --fact-type world,opinion
# Show trace information
hindsight memory recall <bank_id> "query" --trace
```
#### think
### Reflect (Generate Response)
Generate a response using memories and opinions:
Generate a response using memories and bank personality:
```bash
hindsight think <agent_id> "What do you know about Alice?"
hindsight memory reflect <bank_id> "What do you know about Alice?"
# Verbose mode shows reasoning
hindsight think <agent_id> "Should I recommend Python or Java?" -v
# With additional context
hindsight memory reflect <bank_id> "Should I learn Python?" --context "career advice"
# Higher budget for complex questions
hindsight memory reflect <bank_id> "Summarize my week" --budget high
```
### Memory bank Management
## Bank Management
#### memory banks
List all memory banks:
### List Banks
```bash
hindsight memory banks
hindsight bank list
```
Output:
```
Available memory banks:
- alice-agent
- bob-agent
- tech-advisor
```
#### profile
View memory bank profile:
### View Profile
```bash
hindsight profile <agent_id>
hindsight bank profile <bank_id>
```
Output:
```
Memory bank: my-agent
Personality:
Openness: 0.80
Conscientiousness: 0.60
Extraversion: 0.50
Agreeableness: 0.70
Neuroticism: 0.30
Bias Strength: 0.70
Background:
I am a helpful AI assistant interested in technology.
```
#### set-personality
Update personality traits:
### View Statistics
```bash
hindsight set-personality <agent_id> \
--openness 0.8 \
--conscientiousness 0.6 \
--extraversion 0.5 \
--agreeableness 0.7 \
--neuroticism 0.3 \
--bias-strength 0.7
hindsight bank stats <bank_id>
```
#### background
Add or merge background:
### Set Bank Name
```bash
# Set/merge background
hindsight background <agent_id> "I have expertise in distributed systems"
hindsight bank name <bank_id> "My Assistant"
```
### MCP Server
Start the MCP server:
### Set Background
```bash
hindsight mcp-server
hindsight bank background <bank_id> "I am a helpful AI assistant interested in technology"
# With custom configuration
HINDSIGHT_API_URL=http://api.example.com hindsight mcp-server
# Skip automatic personality inference
hindsight bank background <bank_id> "Background text" --no-update-personality
```
## Document Management
```bash
# List documents
hindsight document list <bank_id>
# Get document details
hindsight document get <bank_id> <document_id>
# Delete document and its memories
hindsight document delete <bank_id> <document_id>
```
## Entity Management
```bash
# List entities
hindsight entity list <bank_id>
# Get entity details
hindsight entity get <bank_id> <entity_id>
# Regenerate entity observations
hindsight entity regenerate <bank_id> <entity_id>
```
## Output Formats
### Pretty (Default)
Human-readable formatted output:
```bash
hindsight search <agent_id> "query"
# Pretty (default)
hindsight memory recall <bank_id> "query"
# JSON
hindsight memory recall <bank_id> "query" -o json
# YAML
hindsight memory recall <bank_id> "query" -o yaml
```
### JSON
Machine-readable JSON output:
```bash
hindsight search <agent_id> "query" -o json
```
### YAML
YAML formatted output:
```bash
hindsight search <agent_id> "query" -o yaml
```
## Verbose Mode
Add `-v` or `--verbose` for detailed output:
```bash
hindsight search <agent_id> "query" -v
```
Shows:
- Request payload
- Response details
- Timing information
## Global Options
| Flag | Description |
|------|-------------|
| `-v, --verbose` | Verbose output |
| `-v, --verbose` | Show detailed output including request/response |
| `-o, --output <format>` | Output format: pretty, json, yaml |
| `--api-url <url>` | Override API URL |
| `--help` | Show help |
| `--version` | Show version |
## Examples
## Interactive Explorer
### Full Workflow
Launch the TUI explorer for visual navigation:
```bash
# Create a memory bank
curl -X PUT http://localhost:8888/api/memory banks/demo-agent \
-H "Content-Type: application/json" \
-d '{"background": "Demo agent"}'
# Store memories
hindsight put demo-agent "Alice works at Google"
hindsight put demo-agent "Bob is a data scientist"
hindsight put demo-agent "Alice and Bob are colleagues"
# Search
hindsight search demo-agent "Who works with Alice?"
# Think (with opinions)
hindsight think demo-agent "What do you know about the team?"
# Update personality
hindsight set-personality demo-agent \
--openness 0.9 \
--conscientiousness 0.7 \
--extraversion 0.6 \
--agreeableness 0.8 \
--neuroticism 0.2 \
--bias-strength 0.6
# Check profile
hindsight profile demo-agent
hindsight explore
```
## Example Workflow
```bash
# Configure API URL
hindsight configure --api-url http://localhost:8888
# Store some memories
hindsight memory retain demo "Alice works at Google"
hindsight memory retain demo "Bob is a data scientist"
hindsight memory retain demo "Alice and Bob are colleagues"
# Search memories
hindsight memory recall demo "Who works with Alice?"
# Generate a response
hindsight memory reflect demo "What do you know about the team?"
# Check bank profile
hindsight bank profile demo
```

View file

@ -8,7 +8,7 @@ Model Context Protocol server for AI assistants like Claude Desktop.
## Setup
The MCP server is included in the Hindsight API. When running the API with MCP enabled (default), it exposes MCP tools via SSE at `/mcp/sse`.
The MCP server is included in the Hindsight API. When running the API with MCP enabled, it exposes MCP tools at `/mcp/{bank_id}/sse`.
### Claude Desktop Configuration
@ -19,50 +19,64 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
"mcpServers": {
"hindsight": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8888/mcp/sse"]
"args": ["-y", "mcp-remote", "http://localhost:8888/mcp/my-bank-id/sse"]
}
}
}
```
Replace `my-bank-id` with your memory bank ID.
## Available Tools
### hindsight_put
### retain
Store a memory for a user:
Store a memory:
```json
{
"name": "hindsight_put",
"name": "retain",
"arguments": {
"bank_id": "user_12345",
"content": "User prefers Python for data analysis",
"context": "programming_preferences"
"context": "preferences"
}
}
```
### hindsight_search
**Parameters:**
Search memories for a user:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `content` | string | yes | Memory content to store |
| `context` | string | no | Category (default: 'general') |
### recall
Search memories:
```json
{
"name": "hindsight_search",
"name": "recall",
"arguments": {
"bank_id": "user_12345",
"query": "What does the user do for work?"
}
}
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | yes | Natural language search query |
| `max_results` | integer | no | Max results (default: 10) |
## Usage Example
Once configured, Claude can use Hindsight naturally:
**User**: "Remember that I prefer morning meetings"
**Claude**: *Uses hindsight_put*
**Claude**: *Uses retain*
> "I've noted that you prefer morning meetings."
@ -70,16 +84,6 @@ Once configured, Claude can use Hindsight naturally:
**User**: "What do you know about my preferences?"
**Claude**: *Uses hindsight_search*
**Claude**: *Uses recall*
> "Based on our conversations, you prefer morning meetings and like Python for data analysis."
## Per-User Memory
The MCP tools require a `bank_id` for each user:
- Each user must have a unique `bank_id` (user ID, email, session ID)
- Memories are isolated by `bank_id`
- Use consistent `bank_id` values across interactions
See [MCP API Reference](/api-reference/mcp) for full parameter details.

View file

@ -15,133 +15,100 @@ npm install @vectorize-io/hindsight-client
## Quick Start
```typescript
import { OpenAPI, MemoryStorageService, SearchService } from '@vectorize-io/hindsight-client';
import { HindsightClient } from '@vectorize-io/hindsight-client';
// Configure base URL
OpenAPI.BASE = 'http://localhost:8888';
const client = new HindsightClient({ baseUrl: 'http://localhost:8888' });
// Store a memory
await MemoryStorageService.putApiPutPost({
agent_id: 'my-agent',
content: 'Alice works at Google as a software engineer',
});
// Retain a memory
await client.retain('my-agent', 'Alice works at Google');
// Search memories
const results = await SearchService.searchApiSearchPost({
agent_id: 'my-agent',
query: 'What does Alice do?',
});
console.log(results);
```
## Configuration
```typescript
import { OpenAPI } from '@vectorize-io/hindsight-client';
OpenAPI.BASE = 'http://localhost:8888';
OpenAPI.TOKEN = 'your-api-token'; // If authentication is enabled
```
## Memory Operations
### Store Memory
```typescript
import { MemoryStorageService } from '@vectorize-io/hindsight-client';
await MemoryStorageService.putApiPutPost({
agent_id: 'my-agent',
content: 'Alice works at Google as a software engineer',
context: 'career discussion',
event_date: '2024-01-15T10:00:00Z',
});
```
### Store Batch
```typescript
await MemoryStorageService.batchApiMemoriesBatchPost({
agent_id: 'my-agent',
items: [
{ content: 'Alice works at Google', context: 'career' },
{ content: 'Bob is a data scientist', context: 'career' },
],
document_id: 'conversation_001',
});
```
## Search Operations
### Basic Search
```typescript
import { SearchService } from '@vectorize-io/hindsight-client';
const results = await SearchService.searchApiSearchPost({
agent_id: 'my-agent',
query: 'What does Alice do?',
});
for (const r of results.results) {
console.log(`${r.text} (weight: ${r.weight})`);
// Recall memories
const response = await client.recall('my-agent', 'What does Alice do?');
for (const r of response.results) {
console.log(r.text);
}
// Reflect - generate response with personality
const answer = await client.reflect('my-agent', 'Tell me about Alice');
console.log(answer.text);
```
### Advanced Search
## Client Initialization
```typescript
const results = await SearchService.recallApiRecallPost({
bank_id: 'my-agent',
query: 'What does Alice do?',
budget: 'low', // 'low', 'mid', or 'high'
top_k: 10,
});
```
### Search World Facts
```typescript
const worldFacts = await SearchService.worldSearchApiWorldSearchPost({
agent_id: 'my-agent',
query: 'Who works at Google?',
});
```
### Search Opinions
```typescript
const opinions = await SearchService.opinionSearchApiOpinionSearchPost({
agent_id: 'my-agent',
query: 'What do I think about Python?',
});
```
## Reflect (Generate Response)
```typescript
import { ReasoningService } from '@vectorize-io/hindsight-client';
const response = await ReasoningService.reflectApiReflectPost({
bank_id: 'my-agent',
query: 'What should I know about Alice?',
import { HindsightClient } from '@vectorize-io/hindsight-client';
const client = new HindsightClient({
baseUrl: 'http://localhost:8888',
});
```
## Core Operations
### Retain (Store Memory)
```typescript
// Simple
await client.retain('my-agent', 'Alice works at Google');
// With options
await client.retain('my-agent', 'Alice got promoted', {
timestamp: new Date('2024-01-15'),
context: 'career update',
metadata: { source: 'slack' },
async: false, // Set true for background processing
});
```
### Retain Batch
```typescript
await client.retainBatch('my-agent', [
{ content: 'Alice works at Google', context: 'career' },
{ content: 'Bob is a data scientist', context: 'career' },
], {
documentId: 'conversation_001',
async: false,
});
```
### Recall (Search)
```typescript
// Simple - returns RecallResponse
const response = await client.recall('my-agent', 'What does Alice do?');
for (const r of response.results) {
console.log(`${r.text} (type: ${r.type})`);
}
// With options
const response = await client.recall('my-agent', 'What does Alice do?', {
types: ['world', 'opinion'], // Filter by fact type
maxTokens: 4096,
budget: 'high', // 'low', 'mid', or 'high'
trace: true,
});
```
### Reflect (Generate Response)
```typescript
const answer = await client.reflect('my-agent', 'What should I know about Alice?', {
budget: 'low', // 'low', 'mid', or 'high'
context: 'preparing for a meeting',
});
console.log(response.text); // Generated response
console.log(response.based_on); // Memories used
console.log(response.new_opinions); // New opinions formed
console.log(answer.text); // Generated response
console.log(answer.based_on); // Memories used
```
## Memory bank Management
## Bank Management
### Create Memory bank
### Create Bank
```typescript
import { ManagementService } from '@vectorize-io/hindsight-client';
await ManagementService.createAgentApiAgentsAgentIdPut('my-agent', {
await client.createBank('my-agent', {
name: 'Assistant',
background: 'I am a helpful AI assistant',
personality: {
@ -155,55 +122,26 @@ await ManagementService.createAgentApiAgentsAgentIdPut('my-agent', {
});
```
### Get Profile
### Get Bank Profile
```typescript
const profile = await ManagementService.getProfileApiAgentsAgentIdProfileGet('my-agent');
const profile = await client.getBankProfile('my-agent');
console.log(profile.personality);
console.log(profile.background);
```
### List Memory banks
### List Memories
```typescript
const memory banks = await ManagementService.listAgentsApiAgentsGet();
for (const agent of memory banks.memory banks) {
console.log(agent.agent_id);
}
```
### Update Personality
```typescript
await ManagementService.updatePersonalityApiAgentsAgentIdProfilePut('my-agent', {
openness: 0.9,
conscientiousness: 0.7,
const response = await client.listMemories('my-agent', {
type: 'world', // Optional filter
q: 'Alice', // Optional text search
limit: 100,
offset: 0,
});
```
### Merge Background
```typescript
await ManagementService.mergeBackgroundApiAgentsAgentIdBackgroundPost('my-agent', {
background: 'Additional context to merge',
});
```
## Error Handling
```typescript
import { ApiError } from '@vectorize-io/hindsight-client';
try {
await SearchService.searchApiSearchPost({
agent_id: 'unknown-agent',
query: 'test',
});
} catch (error) {
if (error instanceof ApiError) {
console.log(`Error: ${error.message}`);
console.log(`Status: ${error.status}`);
}
for (const memory of response.memories) {
console.log(`${memory.id}: ${memory.text}`);
}
```
@ -213,19 +151,91 @@ The client exports all types for full TypeScript support:
```typescript
import type {
AgentProfile,
SearchResult,
ThinkResponse,
MemoryItem,
PersonalityTraits,
RetainResponse,
RecallResponse,
RecallResult,
ReflectResponse,
BankProfileResponse,
Budget,
} from '@vectorize-io/hindsight-client';
const personality: PersonalityTraits = {
openness: 0.7,
conscientiousness: 0.8,
extraversion: 0.5,
agreeableness: 0.6,
neuroticism: 0.3,
bias_strength: 0.5,
};
// Budget is a union type: 'low' | 'mid' | 'high'
const budget: Budget = 'mid';
```
## Advanced: Low-Level SDK
For advanced use cases, access the auto-generated SDK directly:
```typescript
import { sdk, createClient, createConfig } from '@vectorize-io/hindsight-client';
const client = createClient(createConfig({ baseUrl: 'http://localhost:8888' }));
// Use sdk functions directly
const response = await sdk.recallMemories({
client,
path: { bank_id: 'my-agent' },
body: {
query: 'What does Alice do?',
budget: 'mid',
max_tokens: 4096,
},
});
```
## Error Handling
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
const client = new HindsightClient({ baseUrl: 'http://localhost:8888' });
try {
const response = await client.recall('unknown-agent', 'test');
} catch (error) {
console.error('Error:', error.message);
}
```
## Example: Full Workflow
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
async function main() {
const client = new HindsightClient({ baseUrl: 'http://localhost:8888' });
// Create a bank with personality
await client.createBank('demo', {
name: 'Demo Agent',
background: 'A helpful assistant for demos',
personality: {
openness: 0.8,
conscientiousness: 0.7,
extraversion: 0.6,
agreeableness: 0.8,
neuroticism: 0.2,
bias_strength: 0.5,
},
});
// Store some memories
await client.retain('demo', 'Alice works at Google');
await client.retain('demo', 'Bob is a data scientist at Google');
await client.retain('demo', 'Alice and Bob collaborate on ML projects');
// Search for memories
const searchResults = await client.recall('demo', 'Who works at Google?');
console.log('Search results:');
for (const r of searchResults.results) {
console.log(` - ${r.text}`);
}
// Generate a response
const answer = await client.reflect('demo', 'What do you know about the team?');
console.log('\nReflection:', answer.text);
}
main().catch(console.error);
```

View file

@ -48,17 +48,17 @@ with HindsightServer(
) as server:
client = HindsightClient(base_url=server.url)
# Store a memory
client.put(agent_id="my-agent", content="Alice works at Google")
# Retain a memory
client.retain(bank_id="my-agent", content="Alice works at Google")
# Search memories
results = client.search(agent_id="my-agent", query="What does Alice do?")
# Recall memories
results = client.recall(bank_id="my-agent", query="What does Alice do?")
for r in results:
print(r["text"], r["weight"])
print(r.text)
# Generate response with personality
answer = client.think(agent_id="my-agent", query="Tell me about Alice")
print(answer["text"])
# Reflect - generate response with personality
answer = client.reflect(bank_id="my-agent", query="Tell me about Alice")
print(answer.text)
```
</TabItem>
@ -69,17 +69,17 @@ from hindsight_client import Hindsight
client = Hindsight(base_url="http://localhost:8888")
# Store a memory
client.put(agent_id="my-agent", content="Alice works at Google")
# Retain a memory
client.retain(bank_id="my-agent", content="Alice works at Google")
# Search memories
results = client.search(agent_id="my-agent", query="What does Alice do?")
# Recall memories
results = client.recall(bank_id="my-agent", query="What does Alice do?")
for r in results:
print(r["text"], r["weight"])
print(r.text)
# Generate response with personality
answer = client.think(agent_id="my-agent", query="Tell me about Alice")
print(answer["text"])
# Reflect - generate response with personality
answer = client.reflect(bank_id="my-agent", query="Tell me about Alice")
print(answer.text)
```
</TabItem>
@ -96,101 +96,112 @@ client = Hindsight(
)
```
## Memory Operations
## Core Operations
### Store Single Memory
### Retain (Store Memory)
```python
client.store(
agent_id="my-agent",
# Simple
client.retain(
bank_id="my-agent",
content="Alice works at Google as a software engineer",
context="career discussion", # Optional context
event_date="2024-01-15T10:00:00Z", # Optional event date
)
# With options
from datetime import datetime
client.retain(
bank_id="my-agent",
content="Alice got promoted",
context="career update",
timestamp=datetime(2024, 1, 15),
document_id="conversation_001",
metadata={"source": "slack"},
)
```
### Store Batch
### Retain Batch
```python
client.store_batch(
agent_id="my-agent",
client.retain_batch(
bank_id="my-agent",
items=[
{"content": "Alice works at Google", "context": "career"},
{"content": "Bob is a data scientist", "context": "career"},
],
document_id="conversation_001", # Optional grouping
document_id="conversation_001",
retain_async=False, # Set True for background processing
)
```
## Search Operations
### Basic Search
### Recall (Search)
```python
results = client.search(
agent_id="my-agent",
# Simple - returns list of RecallResult
results = client.recall(
bank_id="my-agent",
query="What does Alice do?",
)
for r in results:
print(f"{r['text']} (weight: {r['weight']})")
```
print(f"{r.text} (type: {r.type})")
### Advanced Search
```python
results = client.search_memories(
agent_id="my-agent",
# With options
results = client.recall(
bank_id="my-agent",
query="What does Alice do?",
fact_type=["world", "agent"], # Filter by type
max_tokens=4096, # Token budget for results
top_k=10, # Max results
types=["world", "opinion"], # Filter by fact type
max_tokens=4096,
budget="high", # low, mid, or high
)
```
### Search by Fact Type
### Recall with Full Response
```python
# Search only world facts
world_facts = client.search_memories(
agent_id="my-agent",
query="Who works at Google?",
fact_type=["world"],
# Returns RecallResponse with entities and trace info
response = client.recall_memories(
bank_id="my-agent",
query="What does Alice do?",
types=["world", "bank"],
budget="mid",
max_tokens=4096,
trace=True,
include_entities=True,
max_entity_tokens=500,
)
# Search only opinions
opinions = client.search_memories(
agent_id="my-agent",
query="What do I think about Python?",
fact_type=["opinion"],
)
print(f"Found {len(response.results)} memories")
for r in response.results:
print(f" - {r.text}")
# Access entities
if response.entities:
for entity in response.entities:
print(f"Entity: {entity.name}")
```
## Think (Generate Response)
Generate personality-aware responses using retrieved memories:
### Reflect (Generate Response)
```python
from hindsight_api.engine.memory_engine import Budget
answer = client.reflect(
bank_id="my-agent",
query="What should I know about Alice?",
budget=Budget.LOW, # Budget level for retrieval
budget="low", # low, mid, or high
context="preparing for a meeting",
)
print(answer["text"]) # Generated response
print(answer["based_on"]) # Memories used
print(answer["new_opinions"]) # New opinions formed
print(answer.text) # Generated response
print(answer.based_on) # Memories used
```
## Memory bank Management
## Bank Management
### Create Memory bank
### Create Bank
```python
client.create_agent(
agent_id="my-agent",
client.create_bank(
bank_id="my-agent",
name="Assistant",
background="I am a helpful AI assistant",
personality={
@ -204,69 +215,72 @@ client.create_agent(
)
```
### Get Profile
### List Memories
```python
profile = client.get_profile(agent_id="my-agent")
print(profile["personality"])
print(profile["background"])
```
### List Memory banks
```python
memory banks = client.list_agents()
for agent in memory banks:
print(agent["agent_id"])
```
### Update Personality
```python
client.update_personality(
agent_id="my-agent",
openness=0.9,
conscientiousness=0.7,
response = client.list_memories(
bank_id="my-agent",
type="world", # Optional: filter by type
search_query="Alice", # Optional: text search
limit=100,
offset=0,
)
```
### Update Background
```python
client.update_background(
agent_id="my-agent",
background="Additional context to merge with existing background",
)
```
## Error Handling
```python
from hindsight_client import Hindsight, HindsightError
client = Hindsight(base_url="http://localhost:8888")
try:
results = client.search(agent_id="unknown", query="test")
except HindsightError as e:
print(f"Error: {e.message}")
print(f"Status: {e.status}")
for memory in response.memories:
print(f"{memory.id}: {memory.text}")
```
## Async Support
All methods have async versions prefixed with `a`:
```python
import asyncio
from hindsight_client import AsyncHindsight
from hindsight_client import Hindsight
async def main():
client = AsyncHindsight(base_url="http://localhost:8888")
client = Hindsight(base_url="http://localhost:8888")
# All methods have async versions
await client.store(agent_id="my-agent", content="Hello world")
results = await client.search(agent_id="my-agent", query="Hello")
# Async retain
await client.aretain(bank_id="my-agent", content="Hello world")
print(results)
# Async recall
results = await client.arecall(bank_id="my-agent", query="Hello")
for r in results:
print(r.text)
# Async reflect
answer = await client.areflect(bank_id="my-agent", query="What did I say?")
print(answer.text)
client.close()
asyncio.run(main())
```
## Response Types
The client exports response types for type hints:
```python
from hindsight_client import (
Hindsight,
RetainResponse,
RecallResponse,
RecallResult,
ReflectResponse,
BankProfileResponse,
PersonalityTraits,
)
```
## Context Manager
```python
from hindsight_client import Hindsight
with Hindsight(base_url="http://localhost:8888") as client:
client.retain(bank_id="my-agent", content="Hello")
results = client.recall(bank_id="my-agent", query="Hello")
# Client automatically closed
```