diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index d87de954..749b8e1a 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -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 diff --git a/hindsight-api/hindsight_api/api/__init__.py b/hindsight-api/hindsight_api/api/__init__.py index d35c311a..6cac4164 100644 --- a/hindsight-api/hindsight_api/api/__init__.py +++ b/hindsight-api/hindsight_api/api/__init__.py @@ -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]") diff --git a/hindsight-api/hindsight_api/api/mcp.py b/hindsight-api/hindsight_api/api/mcp.py index 3b1ebac5..5ad2db58 100644 --- a/hindsight-api/hindsight_api/api/mcp.py +++ b/hindsight-api/hindsight_api/api/mcp.py @@ -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) diff --git a/hindsight-api/hindsight_api/web/server.py b/hindsight-api/hindsight_api/web/server.py index 78c40d70..fc559691 100644 --- a/hindsight-api/hindsight_api/web/server.py +++ b/hindsight-api/hindsight_api/web/server.py @@ -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"), diff --git a/hindsight-docs/docs/api-reference/mcp.md b/hindsight-docs/docs/api-reference/mcp.md index 2e4f9d0e..4c9d777c 100644 --- a/hindsight-docs/docs/api-reference/mcp.md +++ b/hindsight-docs/docs/api-reference/mcp.md @@ -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 -``` diff --git a/hindsight-docs/docs/developer/models.md b/hindsight-docs/docs/developer/models.md index 81bdca89..9788f406 100644 --- a/hindsight-docs/docs/developer/models.md +++ b/hindsight-docs/docs/developer/models.md @@ -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. diff --git a/hindsight-docs/docs/sdks/cli.md b/hindsight-docs/docs/sdks/cli.md index d75b974e..798dc56b 100644 --- a/hindsight-docs/docs/sdks/cli.md +++ b/hindsight-docs/docs/sdks/cli.md @@ -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 "Alice works at Google as a software engineer" +hindsight memory retain "Alice works at Google as a software engineer" # With context -hindsight put "Bob loves hiking" --context "hobby discussion" +hindsight memory retain "Bob loves hiking" --context "hobby discussion" -# With event date -hindsight put "Meeting with Carol" --date "2024-01-15" +# Queue for background processing +hindsight memory retain "Meeting notes" --async ``` -#### put-files +### Retain Files -Store file contents as memories: +Bulk import from files: ```bash -# Store a single file -hindsight put-files notes.txt +# Single file +hindsight memory retain-files notes.txt -# Store multiple files -hindsight put-files file1.txt file2.md file3.json +# Directory (recursive by default) +hindsight memory retain-files ./documents/ # With context -hindsight put-files meeting-notes.txt --context "team meeting" +hindsight memory retain-files meeting-notes.txt --context "team meeting" + +# Background processing +hindsight memory retain-files ./data/ --async ``` -#### search +### Recall (Search) -Search memories: +Search memories using semantic similarity: ```bash -hindsight search "What does Alice do?" +hindsight memory recall "What does Alice do?" # With options -hindsight search "hiking recommendations" --budget 100 --top-k 5 +hindsight memory recall "hiking recommendations" \ + --budget high \ + --max-tokens 8192 -# Verbose output -hindsight search "query" -v +# Filter by fact type +hindsight memory recall "query" --fact-type world,opinion + +# Show trace information +hindsight memory recall "query" --trace ``` -#### think +### Reflect (Generate Response) -Generate a response using memories and opinions: +Generate a response using memories and bank personality: ```bash -hindsight think "What do you know about Alice?" +hindsight memory reflect "What do you know about Alice?" -# Verbose mode shows reasoning -hindsight think "Should I recommend Python or Java?" -v +# With additional context +hindsight memory reflect "Should I learn Python?" --context "career advice" + +# Higher budget for complex questions +hindsight memory reflect "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 +hindsight bank profile ``` -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 \ - --openness 0.8 \ - --conscientiousness 0.6 \ - --extraversion 0.5 \ - --agreeableness 0.7 \ - --neuroticism 0.3 \ - --bias-strength 0.7 +hindsight bank stats ``` -#### background - -Add or merge background: +### Set Bank Name ```bash -# Set/merge background -hindsight background "I have expertise in distributed systems" +hindsight bank name "My Assistant" ``` -### MCP Server - -Start the MCP server: +### Set Background ```bash -hindsight mcp-server +hindsight bank background "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 "Background text" --no-update-personality +``` + +## Document Management + +```bash +# List documents +hindsight document list + +# Get document details +hindsight document get + +# Delete document and its memories +hindsight document delete +``` + +## Entity Management + +```bash +# List entities +hindsight entity list + +# Get entity details +hindsight entity get + +# Regenerate entity observations +hindsight entity regenerate ``` ## Output Formats -### Pretty (Default) - -Human-readable formatted output: - ```bash -hindsight search "query" +# Pretty (default) +hindsight memory recall "query" + +# JSON +hindsight memory recall "query" -o json + +# YAML +hindsight memory recall "query" -o yaml ``` -### JSON - -Machine-readable JSON output: - -```bash -hindsight search "query" -o json -``` - -### YAML - -YAML formatted output: - -```bash -hindsight search "query" -o yaml -``` - -## Verbose Mode - -Add `-v` or `--verbose` for detailed output: - -```bash -hindsight search "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 ` | Output format: pretty, json, yaml | -| `--api-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 ``` diff --git a/hindsight-docs/docs/sdks/mcp.md b/hindsight-docs/docs/sdks/mcp.md index 2139be20..377ab27c 100644 --- a/hindsight-docs/docs/sdks/mcp.md +++ b/hindsight-docs/docs/sdks/mcp.md @@ -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. diff --git a/hindsight-docs/docs/sdks/nodejs.md b/hindsight-docs/docs/sdks/nodejs.md index fc5d36f8..c320a48e 100644 --- a/hindsight-docs/docs/sdks/nodejs.md +++ b/hindsight-docs/docs/sdks/nodejs.md @@ -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); ``` diff --git a/hindsight-docs/docs/sdks/python.md b/hindsight-docs/docs/sdks/python.md index c76cc880..499e4415 100644 --- a/hindsight-docs/docs/sdks/python.md +++ b/hindsight-docs/docs/sdks/python.md @@ -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) ``` @@ -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) ``` @@ -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 +```