Compare commits

..
11 Commits
Author SHA1 Message Date
DK09876 e521914f0f Fix example scripts: remove non-existent API attributes
- recall.py: remove .weight, fix entities iteration (dict not list)
- retain.mjs: remove result.async check
2025-12-17 12:14:43 -07:00
DK09876 7cb469ff75 Fix opinions.py: use actual API attributes instead of non-existent ones 2025-12-17 11:40:55 -07:00
DK09876 9118e7b4cb Fix main-methods.py: RecallResult and ReflectFact don't have weight attribute 2025-12-17 11:18:09 -07:00
DK09876 841a66f375 Fix async API client usage in documents.py example 2025-12-17 11:11:46 -07:00
DK09876 eb06adb2be Add documentation code validation CI job
- Use uv sync + uv run pattern (matches existing CI)
- Add requests to test dependencies for cleanup scripts
2025-12-17 10:48:38 -07:00
DK09876 3913788fd8 Fix: run cd in subshell so install runs from repo root 2025-12-17 10:40:10 -07:00
DK09876 6ea02eb023 Fix: use explicit shell expansion for wheel install 2025-12-17 10:35:01 -07:00
DK09876 55154384f6 Fix wheel path - uv build outputs to repo root dist/ 2025-12-17 10:24:52 -07:00
DK09876 19e4e2d635 Fix CI issue 2025-12-17 10:20:15 -07:00
DK09876 8f2396f04a Fix wheel glob expansion in test-doc-examples CI job 2025-12-17 10:15:13 -07:00
DK09876 bffc0ee0d0 Add documentation code validation system
- Create runnable example scripts in examples/api/ (19 files)
- Add CodeSnippet component for extracting marked sections
- Add raw-loader dependency for importing source files
- Create sample retain-new.mdx showing new approach
- Add README documenting coverage and gaps
2025-12-17 10:06:53 -07:00
39 changed files with 5571 additions and 1223 deletions
+5 -105
View File
@@ -102,18 +102,7 @@ jobs:
- name: Publish to npm
working-directory: ./hindsight-clients/typescript
run: |
set +e
OUTPUT=$(npm publish --access public 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
if [ $EXIT_CODE -ne 0 ]; then
if echo "$OUTPUT" | grep -q "cannot publish over"; then
echo "Package version already published, skipping..."
exit 0
fi
exit $EXIT_CODE
fi
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -128,65 +117,6 @@ jobs:
path: hindsight-clients/typescript/*.tgz
retention-days: 1
release-control-plane:
runs-on: ubuntu-latest
environment: npm
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Build TypeScript client (dependency)
run: npm run build --workspace=hindsight-clients/typescript
- name: Fix platform-specific native modules
run: |
# npm ci installs from lockfile which may have wrong platform binaries
# Delete hoisted native modules and reinstall for current platform
rm -rf node_modules/lightningcss node_modules/@tailwindcss
npm install lightningcss @tailwindcss/postcss @tailwindcss/node
- name: Build
run: npm run build --workspace=hindsight-control-plane
- name: Publish to npm
working-directory: ./hindsight-control-plane
run: |
set +e
OUTPUT=$(npm publish --access public 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
if [ $EXIT_CODE -ne 0 ]; then
if echo "$OUTPUT" | grep -q "cannot publish over"; then
echo "Package version already published, skipping..."
exit 0
fi
exit $EXIT_CODE
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Pack for GitHub release
working-directory: ./hindsight-control-plane
run: npm pack
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: control-plane
path: hindsight-control-plane/*.tgz
retention-days: 1
release-rust-cli:
runs-on: ${{ matrix.os }}
strategy:
@@ -251,7 +181,7 @@ jobs:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: true
tool-cache: false
android: true
dotnet: true
haskell: true
@@ -276,7 +206,7 @@ jobs:
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Extract metadata for release tags
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
@@ -287,29 +217,7 @@ jobs:
type=semver,pattern={{major}},value=${{ steps.get_version.outputs.VERSION }}
type=raw,value=latest
# TODO: Re-enable smoke test when disk space issue is resolved
# # Step 1: Build for local testing (single platform, no push)
# # This creates an identical image to what will be released, just for one platform
# - name: Build image for testing
# uses: docker/build-push-action@v6
# with:
# context: .
# file: docker/standalone/Dockerfile
# target: ${{ matrix.target }}
# push: false
# load: true
# tags: ${{ matrix.image_name }}:test
# cache-from: type=gha
# cache-to: type=gha,mode=max
# # Step 2: Test the image before pushing anything
# - name: Smoke test - verify container starts
# env:
# GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
# run: ./scripts/docker-smoke-test.sh "${{ matrix.image_name }}:test" "${{ matrix.target }}"
# Build multi-platform and push to release tags
- name: Build and push release images
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
@@ -355,7 +263,7 @@ jobs:
create-github-release:
runs-on: ubuntu-latest
needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
needs: [release-python-packages, release-typescript-client, release-rust-cli, release-docker-images, release-helm-chart]
permissions:
contents: write
@@ -378,12 +286,6 @@ jobs:
name: typescript-client
path: ./artifacts/typescript-client
- name: Download Control Plane
uses: actions/download-artifact@v4
with:
name: control-plane
path: ./artifacts/control-plane
- name: Download Rust CLI (Linux)
uses: actions/download-artifact@v4
with:
@@ -418,8 +320,6 @@ jobs:
cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true
# TypeScript client
cp artifacts/typescript-client/*.tgz release-assets/ || true
# Control Plane
cp artifacts/control-plane/*.tgz release-assets/ || true
# Rust CLI binaries
cp artifacts/rust-cli-linux/hindsight-linux-amd64 release-assets/ || true
cp artifacts/rust-cli-darwin-amd64/hindsight-darwin-amd64 release-assets/ || true
+1 -60
View File
@@ -80,58 +80,6 @@ jobs:
- name: Build TypeScript client
run: npm run build --workspace=hindsight-clients/typescript
build-control-plane:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install SDK dependencies
run: npm ci --workspace=hindsight-clients/typescript
- name: Build SDK
run: npm run build --workspace=hindsight-clients/typescript
# Install control plane deps and fix hoisted lightningcss binary
# lightningcss gets hoisted to root node_modules, so we need to reinstall it there
- name: Install Control Plane dependencies
run: |
npm install --workspace=hindsight-control-plane
rm -rf node_modules/lightningcss node_modules/@tailwindcss
npm install lightningcss @tailwindcss/postcss @tailwindcss/node
- name: Build Control Plane
run: npm run build --workspace=hindsight-control-plane
- name: Verify standalone build
run: |
test -f hindsight-control-plane/standalone/server.js || exit 1
test -d hindsight-control-plane/standalone/node_modules || exit 1
node hindsight-control-plane/bin/cli.js --help
- name: Smoke test - verify server starts
run: |
cd hindsight-control-plane
node bin/cli.js --port 9999 &
SERVER_PID=$!
sleep 5
if curl -sf http://localhost:9999 > /dev/null 2>&1; then
echo "Server started successfully"
kill $SERVER_PID 2>/dev/null || true
exit 0
else
echo "Server failed to respond"
kill $SERVER_PID 2>/dev/null || true
exit 1
fi
build-docs:
runs-on: ubuntu-latest
@@ -205,7 +153,7 @@ jobs:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: true
tool-cache: false
android: true
dotnet: true
haskell: true
@@ -223,13 +171,6 @@ jobs:
file: docker/standalone/Dockerfile
target: ${{ matrix.target }}
push: false
load: false
# TODO: Re-enable smoke test when disk space issue is resolved
# - name: Smoke test - verify container starts
# env:
# GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
# run: ./scripts/docker-smoke-test.sh "hindsight-${{ matrix.name }}:test" "${{ matrix.target }}"
test-api:
runs-on: ubuntu-latest
+21 -43
View File
@@ -72,48 +72,30 @@ FROM node:20-slim AS cp-builder
ARG INCLUDE_CP
RUN if [ "$INCLUDE_CP" != "true" ]; then echo "Skipping CP build" && exit 0; fi
# Create directory structure matching the monorepo layout
# This is required because build:standalone script expects .next/standalone/memory-poc/hindsight-control-plane
WORKDIR /app/memory-poc/hindsight-control-plane
WORKDIR /app
# Copy built SDK
COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
# Install Control Plane dependencies
# Only copy package.json (not package-lock.json) to ensure npm installs
# correct platform-specific native bindings for lightningcss/tailwindcss
COPY hindsight-control-plane/package.json ./
# Remove the file: dependency on SDK (we'll copy it directly later)
RUN sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
RUN npm install
# Copy Control Plane source (excluding node_modules via .dockerignore)
COPY hindsight-control-plane/ ./
# Remove package-lock.json to avoid conflicts with installed native bindings
# Also remove the file: dependency from package.json (restored by COPY above)
RUN rm -f package-lock.json && sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
RUN rm -f package-lock.json
# Copy built SDK directly into node_modules (more reliable than npm link in Docker)
COPY --from=sdk-builder /app/hindsight-clients/typescript ./node_modules/@vectorize-io/hindsight-client
# Link SDK (temporary for build)
RUN cd /app/sdk && npm link && cd /app && npm link @vectorize-io/hindsight-client
# Build Control Plane - run next build first, then custom standalone copy
# (The build:standalone script expects a specific path structure that differs in Docker)
RUN npm exec -- next build
# Build Control Plane
RUN npm run build
# Create standalone directory structure manually
# Note: Must exclude node_modules from find to avoid wrong server.js from next/dist/experimental/testmode/
# Note: Must explicitly copy .next since glob * doesn't match hidden directories
RUN STANDALONE_ROOT=$(find .next/standalone -path '*/node_modules' -prune -o -name 'server.js' -print | head -1 | xargs dirname) && \
mkdir -p standalone && \
cp -r "$STANDALONE_ROOT"/* standalone/ && \
cp -r "$STANDALONE_ROOT"/.next standalone/.next && \
# Copy node_modules if separate from app dir (monorepo structure)
if [ -d ".next/standalone/node_modules" ] && [ "$STANDALONE_ROOT" != ".next/standalone" ]; then \
cp -r .next/standalone/node_modules standalone/node_modules; \
fi && \
cp -r .next/static standalone/.next/static && \
mkdir -p standalone/public && \
cp -r public/* standalone/public/ 2>/dev/null || true && \
# Verify required files exist
test -f standalone/server.js || (echo "ERROR: server.js missing!" && exit 1) && \
test -f standalone/.next/BUILD_ID || (echo "ERROR: BUILD_ID missing!" && exit 1)
# Create public directory if it doesn't exist
RUN mkdir -p public
# =============================================================================
# Stage: Final Image - API Only
@@ -122,16 +104,14 @@ FROM python:3.11-slim AS api-only
WORKDIR /app
# Install pg0 dependencies (procps provides 'kill' command needed by pg0)
# Note: libicu version varies by Debian version - try common versions in order
# Install pg0 dependencies
RUN apt-get update && apt-get install -y \
curl \
procps \
libxml2 \
libssl3 \
libgssapi-krb5-2 \
libossp-uuid16 \
&& (apt-get install -y libicu72 2>/dev/null || apt-get install -y libicu74 2>/dev/null || apt-get install -y libicu76 2>/dev/null || true) \
&& apt-get install -y libicu72 || apt-get install -y libicu74 || apt-get install -y libicu* \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir uv
@@ -191,9 +171,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
# Copy Control Plane standalone build
WORKDIR /app/control-plane
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
COPY --from=cp-builder /app/.next/standalone ./
COPY --from=cp-builder /app/.next/static ./.next/static
COPY --from=cp-builder /app/public ./public
WORKDIR /app
@@ -220,16 +200,14 @@ FROM python:3.11-slim AS standalone
WORKDIR /app
# Install Node.js, curl, uv, and pg0 dependencies (procps provides 'kill' command needed by pg0)
# Note: libicu version varies by Debian version - try common versions in order
# Install Node.js, curl, uv, and pg0 dependencies
RUN apt-get update && apt-get install -y \
curl \
procps \
libxml2 \
libssl3 \
libgssapi-krb5-2 \
libossp-uuid16 \
&& (apt-get install -y libicu72 2>/dev/null || apt-get install -y libicu74 2>/dev/null || apt-get install -y libicu76 2>/dev/null || true) \
&& apt-get install -y libicu72 || apt-get install -y libicu74 || apt-get install -y libicu* \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/* \
@@ -246,9 +224,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
# Copy Control Plane standalone build
WORKDIR /app/control-plane
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
COPY --from=cp-builder /app/.next/standalone ./
COPY --from=cp-builder /app/.next/static ./.next/static
COPY --from=cp-builder /app/public ./public
WORKDIR /app
+2 -3
View File
@@ -23,8 +23,7 @@ PIDS=()
# Start API if enabled
if [ "$ENABLE_API" = "true" ]; then
cd /app/api
# Run API directly - Python's PYTHONUNBUFFERED=1 handles output buffering
hindsight-api &
hindsight-api 2>&1 | sed -u 's/^/[api] /' &
API_PID=$!
PIDS+=($API_PID)
@@ -43,7 +42,7 @@ fi
if [ "$ENABLE_CP" = "true" ]; then
echo "🎛️ Starting Control Plane..."
cd /app/control-plane
PORT=9999 node server.js &
PORT=9999 node server.js 2>&1 | grep -v -E "^[[:space:]]*(▲|✓|-|$)" | sed -u 's/^/[control-plane] /' &
CP_PID=$!
PIDS+=($CP_PID)
else
+2 -2
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: hindsight
description: Hindsight helm chart
type: application
version: 0.1.11
appVersion: "0.1.11"
version: 0.1.8
appVersion: "0.1.8"
keywords:
- ai
- memory
+1 -24
View File
@@ -31,7 +31,6 @@ ENV_LOG_LEVEL = "HINDSIGHT_API_LOG_LEVEL"
ENV_MCP_ENABLED = "HINDSIGHT_API_MCP_ENABLED"
ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
# Default values
DEFAULT_DATABASE_URL = "pg0"
@@ -51,26 +50,6 @@ DEFAULT_MCP_ENABLED = True
DEFAULT_GRAPH_RETRIEVER = "bfs" # Options: "bfs", "mpfp"
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
# Default MCP tool descriptions (can be customized via env vars)
DEFAULT_MCP_RETAIN_DESCRIPTION = """Store important information to long-term memory.
Use this tool PROACTIVELY whenever the user shares:
- 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"""
DEFAULT_MCP_RECALL_DESCRIPTION = """Search memories to provide personalized, context-aware responses.
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"""
# Required embedding dimension for database schema
EMBEDDING_DIMENSION = 384
@@ -163,9 +142,7 @@ class HindsightConfig:
def configure_logging(self) -> None:
"""Configure Python logging based on the log level."""
logging.basicConfig(
level=self.get_python_log_level(),
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
force=True, # Override any existing configuration
level=self.get_python_log_level(), format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
)
def log_config(self) -> None:
@@ -112,7 +112,7 @@ class LLMProvider:
)
await self.call(
messages=[{"role": "user", "content": "Say 'ok'"}],
max_completion_tokens=100,
max_completion_tokens=10,
max_retries=2,
initial_backoff=0.5,
max_backoff=2.0,
@@ -172,7 +172,7 @@ class LLMProvider:
# Check if model supports reasoning parameter (o1, o3, gpt-5 families)
model_lower = self.model.lower()
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3", "deepseek"])
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3"])
# For GPT-4 and GPT-4.1 models, cap max_completion_tokens to 32000
# For GPT-4o models, cap to 16384
@@ -194,7 +194,7 @@ class LLMProvider:
call_params["temperature"] = temperature
# Set reasoning_effort for reasoning models (OpenAI gpt-5, o1, o3)
if is_reasoning_model:
if is_reasoning_model and self.provider == "openai":
call_params["reasoning_effort"] = self.reasoning_effort
# Provider-specific parameters
@@ -203,6 +203,7 @@ class LLMProvider:
extra_body = {"service_tier": "auto"}
# Only add reasoning parameters for reasoning models
if is_reasoning_model:
extra_body["reasoning_effort"] = self.reasoning_effort
extra_body["include_reasoning"] = False
call_params["extra_body"] = extra_body
@@ -107,10 +107,6 @@ async def retain_batch(
)
if not extracted_facts:
total_time = time.time() - start_time
logger.info(
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (nothing to store)"
)
return [[] for _ in contents]
# Apply fact_type_override if provided
-2
View File
@@ -127,10 +127,8 @@ def main():
port=args.port,
log_level=args.log_level,
mcp_enabled=config.mcp_enabled,
graph_retriever=config.graph_retriever,
)
config.configure_logging()
config.log_config()
# Register cleanup handlers
atexit.register(_cleanup)
+28 -31
View File
@@ -28,15 +28,7 @@ Environment variables:
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "warning").
HINDSIGHT_API_MCP_INSTRUCTIONS: Optional. Additional instructions appended to both retain and recall tools.
Example custom instructions (these are ADDED to the default behavior):
To also store assistant actions:
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, including tool calls, code written, and decisions made."
To also store conversation summaries:
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store summaries of important conversations and their outcomes."
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "info").
"""
import logging
@@ -44,19 +36,14 @@ import os
import sys
from mcp.server.fastmcp import FastMCP
from mcp.types import Icon
from hindsight_api.config import (
DEFAULT_MCP_LOCAL_BANK_ID,
DEFAULT_MCP_RECALL_DESCRIPTION,
DEFAULT_MCP_RETAIN_DESCRIPTION,
ENV_MCP_INSTRUCTIONS,
ENV_MCP_LOCAL_BANK_ID,
)
# Configure logging - default to warning to avoid polluting stderr during MCP init
# MCP clients interpret stderr output as errors, so we suppress INFO logs by default
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "warning").lower()
# Configure logging - default to info
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
_log_level_map = {
"critical": logging.CRITICAL,
"error": logging.ERROR,
@@ -92,21 +79,22 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
if memory is None:
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
# Get custom instructions from environment variable (appended to both tools)
extra_instructions = os.environ.get(ENV_MCP_INSTRUCTIONS, "")
retain_description = DEFAULT_MCP_RETAIN_DESCRIPTION
recall_description = DEFAULT_MCP_RECALL_DESCRIPTION
if extra_instructions:
retain_description = f"{DEFAULT_MCP_RETAIN_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
recall_description = f"{DEFAULT_MCP_RECALL_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
mcp = FastMCP("hindsight")
@mcp.tool(description=retain_description)
@mcp.tool()
async def retain(content: str, context: str = "general") -> dict:
"""
Store important information to long-term memory.
Use this tool PROACTIVELY whenever the user shares:
- 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
Args:
content: The fact/memory to store (be specific and include relevant details)
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
@@ -123,9 +111,17 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
asyncio.create_task(_retain())
return {"status": "accepted", "message": "Memory storage initiated"}
@mcp.tool(description=recall_description)
@mcp.tool()
async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
"""
Search memories to provide personalized, context-aware responses.
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:
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
max_tokens: Maximum tokens to return in results (default: 4096)
@@ -157,9 +153,10 @@ async def _initialize_and_run(bank_id: str):
from hindsight_api import MemoryEngine
# Create and initialize memory engine with pg0 embedded database
# Note: We avoid printing to stderr during init as MCP clients show it as "errors"
print("Initializing memory engine...", file=sys.stderr)
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
await memory.initialize()
print("Memory engine initialized.", file=sys.stderr)
# Create and run the server
mcp = create_local_mcp_server(bank_id, memory=memory)
@@ -182,8 +179,8 @@ def main():
# Get bank ID from environment, default to "mcp"
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
# Note: We don't print to stderr as MCP clients display it as "error output"
# Use HINDSIGHT_API_LOG_LEVEL=debug for verbose startup logging
# Print startup message to stderr (stdout is reserved for MCP protocol)
print(f"Hindsight MCP server starting (bank_id={bank_id})...", file=sys.stderr)
# Run the async initialization and server
asyncio.run(_initialize_and_run(bank_id))
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "hindsight-api"
version = "0.1.11"
version = "0.1.8"
description = "Hindsight: Agent Memory That Works Like Human Memory"
readme = "README.md"
requires-python = ">=3.11"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "hindsight-cli"
version = "0.1.11"
version = "0.1.8"
edition = "2021"
authors = ["Hindsight Team"]
description = "A beautiful CLI for Hindsight - semantic memory system"
-56
View File
@@ -91,9 +91,6 @@ enum Commands {
#[command(alias = "tui")]
Explore,
/// Launch the web-based control plane UI
Ui,
/// Configure the CLI (API URL, etc.)
#[command(after_help = "Configuration priority:\n 1. Environment variable (HINDSIGHT_API_URL) - highest priority\n 2. Config file (~/.hindsight/config)\n 3. Default (http://localhost:8888)")]
Configure {
@@ -376,11 +373,6 @@ fn run() -> Result<()> {
return handle_configure(api_url, output_format);
}
// Handle ui command - needs config but not API client
if let Commands::Ui = cli.command {
return handle_ui(output_format);
}
// Load configuration
let config = Config::from_env().unwrap_or_else(|e| {
ui::print_error(&format!("Configuration error: {}", e));
@@ -398,7 +390,6 @@ fn run() -> Result<()> {
// Execute command and handle errors
let result: Result<()> = match cli.command {
Commands::Configure { .. } => unreachable!(), // Handled above
Commands::Ui => unreachable!(), // Handled above
Commands::Explore => commands::explore::run(&client),
Commands::Bank(bank_cmd) => match bank_cmd {
BankCommands::List => commands::bank::list(&client, verbose, output_format),
@@ -530,50 +521,3 @@ fn handle_configure(api_url: Option<String>, output_format: OutputFormat) -> Res
Ok(())
}
fn handle_ui(output_format: OutputFormat) -> Result<()> {
use std::process::Command;
// Load configuration to get the API URL
let config = Config::load().unwrap_or_else(|e| {
ui::print_error(&format!("Configuration error: {}", e));
errors::print_config_help();
std::process::exit(1);
});
let api_url = config.api_url();
if output_format == OutputFormat::Pretty {
ui::print_info("Launching Hindsight Control Plane UI...");
println!();
println!(" API URL: {}", api_url);
println!();
}
// Run npx @vectorize-io/hindsight-control-plane --api-url {api_url}
let status = Command::new("npx")
.arg("@vectorize-io/hindsight-control-plane")
.arg("--api-url")
.arg(api_url)
.status();
match status {
Ok(exit_status) => {
if !exit_status.success() {
if let Some(code) = exit_status.code() {
std::process::exit(code);
} else {
std::process::exit(1);
}
}
}
Err(e) => {
ui::print_error(&format!("Failed to launch control plane UI: {}", e));
ui::print_info("Make sure you have Node.js and npm installed.");
ui::print_info("You can also install the control plane globally: npm install -g @vectorize-io/hindsight-control-plane");
std::process::exit(1);
}
}
Ok(())
}
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "hindsight-client"
version = "0.1.11"
version = "0.1.8"
description = "Python client for Hindsight - Semantic memory system with personality-driven thinking"
authors = [
{name = "Hindsight Team"}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@vectorize-io/hindsight-client",
"version": "0.1.11",
"version": "0.1.8",
"description": "TypeScript client for Hindsight - Semantic memory system with personality-driven thinking",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
-1
View File
@@ -14,7 +14,6 @@
# production
/build
/standalone
# misc
.DS_Store
-86
View File
@@ -1,86 +0,0 @@
#!/usr/bin/env node
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const args = process.argv.slice(2);
// Parse command line arguments
let port = process.env.PORT || 9999;
let hostname = process.env.HOSTNAME || '0.0.0.0';
let apiUrl = process.env.HINDSIGHT_CP_DATAPLANE_API_URL;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--port' || args[i] === '-p') {
port = args[++i];
} else if (args[i] === '--hostname' || args[i] === '-H') {
hostname = args[++i];
} else if (args[i] === '--api-url' || args[i] === '-a') {
apiUrl = args[++i];
} else if (args[i] === '--help' || args[i] === '-h') {
console.log(`
Hindsight Control Plane
Usage: hindsight-control-plane [options]
Options:
-p, --port <port> Port to listen on (default: 9999, env: PORT)
-H, --hostname <host> Hostname to bind to (default: 0.0.0.0, env: HOSTNAME)
-a, --api-url <url> Hindsight API URL (env: HINDSIGHT_CP_DATAPLANE_API_URL)
-h, --help Show this help message
Environment Variables:
PORT Port to listen on
HOSTNAME Hostname to bind to
HINDSIGHT_CP_DATAPLANE_API_URL URL of the Hindsight API server
`);
process.exit(0);
}
}
// Find the standalone server
const standaloneDir = path.join(__dirname, '..', 'standalone');
const serverPath = path.join(standaloneDir, 'server.js');
if (!fs.existsSync(serverPath)) {
console.error('Error: Standalone server not found at', serverPath);
console.error('This package may not have been built correctly.');
process.exit(1);
}
// Set up environment
const env = {
...process.env,
PORT: String(port),
HOSTNAME: hostname,
};
if (apiUrl) {
env.HINDSIGHT_CP_DATAPLANE_API_URL = apiUrl;
}
console.log(`Starting Hindsight Control Plane on http://${hostname}:${port}`);
if (apiUrl) {
console.log(`API URL: ${apiUrl}`);
}
// Run the standalone server
const server = spawn('node', [serverPath], {
cwd: standaloneDir,
env,
stdio: 'inherit',
});
server.on('error', (err) => {
console.error('Failed to start server:', err.message);
process.exit(1);
});
server.on('close', (code) => {
process.exit(code || 0);
});
// Handle signals
process.on('SIGTERM', () => server.kill('SIGTERM'));
process.on('SIGINT', () => server.kill('SIGINT'));
-7
View File
@@ -1,14 +1,7 @@
import type { NextConfig } from "next";
import path from "path";
const nextConfig: NextConfig = {
output: 'standalone',
// Disable request logging in production
logging: false,
// Set the monorepo root explicitly to avoid detecting wrong lockfiles in parent directories
turbopack: {
root: path.resolve(__dirname, '..'),
},
};
export default nextConfig;
+8 -17
View File
@@ -1,26 +1,17 @@
{
"name": "@vectorize-io/hindsight-control-plane",
"version": "0.1.11",
"description": "Control plane for Hindsight - Semantic memory system",
"bin": {
"hindsight-control-plane": "./bin/cli.js"
},
"files": [
"bin",
"standalone",
"public"
],
"name": "hindsight-control-plane",
"version": "0.1.8",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && npm run build:standalone",
"build:standalone": "rm -rf standalone && STANDALONE_ROOT=$(find .next/standalone -path '*/node_modules' -prune -o -name 'server.js' -print | head -1 | xargs dirname) && cp -r \"$STANDALONE_ROOT\" standalone && cp -r .next/standalone/node_modules standalone/node_modules && mkdir -p standalone/.next && cp -r .next/static standalone/.next/static && mkdir -p standalone/public && cp -r public/* standalone/public/ 2>/dev/null || true",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepublishOnly": "npm run build"
"lint": "next lint"
},
"keywords": ["hindsight", "memory", "semantic", "ai"],
"keywords": [],
"author": "Hindsight Team",
"license": "ISC",
"description": "Control plane for Hindsight - Semantic memory system",
"dependencies": {
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
@@ -36,6 +27,7 @@
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -58,7 +50,6 @@
"typescript": "^5.9.3"
},
"devDependencies": {
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"eslint-plugin-react": "^7.37.5",
@@ -1,35 +0,0 @@
import { NextResponse } from "next/server";
import { sdk, lowLevelClient } from "@/lib/hindsight-client";
export async function GET() {
const status: {
status: string;
service: string;
dataplane?: {
status: string;
url: string;
error?: string;
};
} = {
status: "ok",
service: "hindsight-control-plane",
};
// Check dataplane connectivity
const dataplaneUrl = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888";
try {
await sdk.listBanks({ client: lowLevelClient });
status.dataplane = {
status: "connected",
url: dataplaneUrl,
};
} catch (error) {
status.dataplane = {
status: "disconnected",
url: dataplaneUrl,
error: error instanceof Error ? error.message : String(error),
};
}
return NextResponse.json(status, { status: 200 });
}
@@ -7,6 +7,17 @@ export async function POST(request: NextRequest) {
const bankId = body.bank_id || body.agent_id || "default";
const { query, types, fact_type, max_tokens, trace, budget, include, query_timestamp } = body;
console.log("[Recall API] Request:", {
bankId,
query,
types: types || fact_type,
max_tokens,
trace,
budget,
query_timestamp,
});
console.log("[Recall API] Include options:", JSON.stringify(include, null, 2));
const response = await sdk.recallMemories({
client: lowLevelClient,
path: { bank_id: bankId },
@@ -26,6 +37,18 @@ export async function POST(request: NextRequest) {
throw new Error(`API returned no data: ${JSON.stringify(response.error || "Unknown error")}`);
}
console.log("[Recall API] Response structure:", {
hasResults: !!response.data?.results,
resultsCount: response.data?.results?.length,
hasTrace: !!response.data?.trace,
hasEntities: !!response.data?.entities,
entitiesType: typeof response.data?.entities,
entitiesKeys: response.data?.entities ? Object.keys(response.data.entities) : null,
hasChunks: !!response.data?.chunks,
chunksType: typeof response.data?.chunks,
chunksKeys: response.data?.chunks ? Object.keys(response.data.chunks) : null,
});
// Return a clean JSON object by spreading the response
// This ensures any non-serializable properties are excluded
const jsonResponse = {
@@ -102,6 +102,12 @@ export function DataView({ factType }: DataViewProps) {
bank_id: currentBank,
type: factType,
});
console.log("Loaded graph data:", {
total_units: graphData.total_units,
nodes: graphData.nodes?.length,
edges: graphData.edges?.length,
table_rows: graphData.table_rows?.length,
});
setData(graphData);
} catch (error) {
console.error("Error loading data:", error);
@@ -185,6 +191,10 @@ export function DataView({ factType }: DataViewProps) {
otherTypes[type] = (otherTypes[type] || 0) + 1;
}
});
console.log("Graph link stats:", { semantic, temporal, entity, causal, total });
if (Object.keys(otherTypes).length > 0) {
console.log("Other link types:", otherTypes);
}
return { semantic, temporal, entity, causal, total, otherTypes };
}, [graph2DData]);
@@ -214,11 +214,9 @@ def build_changelog_markdown(
# Build markdown
lines = [f"## [{version}]({release_url})", ""]
has_entries = False
for cat_key in ["breaking", "feature", "improvement", "bugfix", "other"]:
cat_name, cat_entries = categories[cat_key]
if cat_entries:
has_entries = True
lines.append(f"**{cat_name}**")
lines.append("")
for entry in cat_entries:
@@ -226,10 +224,6 @@ def build_changelog_markdown(
lines.append(f"- {entry.summary} ([`{entry.commit_id}`]({commit_url}))")
lines.append("")
if not has_entries:
lines.append("*This release contains internal maintenance and infrastructure changes only.*")
lines.append("")
return "\n".join(lines)
@@ -242,8 +236,6 @@ sidebar_position: 1
# Changelog
This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted.
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
"""
return header, ""
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "hindsight-dev"
version = "0.1.11"
version = "0.1.8"
description = "Development utilities for Hindsight"
requires-python = ">=3.11"
dependencies = [
-36
View File
@@ -4,44 +4,8 @@ sidebar_position: 1
# Changelog
This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted.
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
## [0.1.11](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.11)
**Bug Fixes**
- Fixed the standalone Docker image and control plane standalone build process so standalone deployments build correctly. ([`2948cb6`](https://github.com/vectorize-io/hindsight/commit/2948cb6))
## [0.1.10](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.10)
*This release contains internal maintenance and infrastructure changes only.*
## [0.1.9](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.9)
**Features**
- Simplified local MCP installation and added a standalone UI option for easier setup. ([`1c6acc3`](https://github.com/vectorize-io/hindsight/commit/1c6acc3))
**Bug Fixes**
- Fixed the standalone Docker image so it builds and starts reliably. ([`b52eb90`](https://github.com/vectorize-io/hindsight/commit/b52eb90))
- Improved Docker runtime reliability by adding required system utilities (procps). ([`ae80876`](https://github.com/vectorize-io/hindsight/commit/ae80876))
## [0.1.8](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.8)
**Bug Fixes**
- Fix bank list responses when a bank has no name. ([`04f01ab`](https://github.com/vectorize-io/hindsight/commit/04f01ab))
- Fix failures when retaining memories asynchronously. ([`63f5138`](https://github.com/vectorize-io/hindsight/commit/63f5138))
- Fix a race condition in the bank selector when switching banks. ([`e468a4e`](https://github.com/vectorize-io/hindsight/commit/e468a4e))
## [0.1.7](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.7)
*This release contains internal maintenance and infrastructure changes only.*
## [0.1.6](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.6)
**Features**
@@ -53,7 +53,7 @@ export HINDSIGHT_API_LLM_MODEL=gemini-2.0-flash
# Ollama (local, no API key)
export HINDSIGHT_API_LLM_PROVIDER=ollama
export HINDSIGHT_API_LLM_BASE_URL=http://localhost:11434/v1
export HINDSIGHT_API_LLM_MODEL=gpt-oss-20b
export HINDSIGHT_API_LLM_MODEL=llama3.1
# OpenAI-compatible endpoint
export HINDSIGHT_API_LLM_PROVIDER=openai
@@ -133,38 +133,6 @@ hindsight-api --workers 4 # Multiple worker processes
hindsight-api --log-level debug # Verbose logging
```
### Control Plane
The Control Plane (Web UI) can be run standalone using npx:
```bash
npx @vectorize-io/hindsight-control-plane --api-url http://localhost:8888
```
This connects to your running API server and provides a visual interface for managing memory banks, exploring entities, and testing queries.
#### Options
| Option | Environment Variable | Default | Description |
|--------|---------------------|---------|-------------|
| `-p, --port` | `PORT` | 9999 | Port to listen on |
| `-H, --hostname` | `HOSTNAME` | 0.0.0.0 | Hostname to bind to |
| `-a, --api-url` | `HINDSIGHT_CP_DATAPLANE_API_URL` | http://localhost:8888 | Hindsight API URL |
#### Examples
```bash
# Run on custom port
npx @vectorize-io/hindsight-control-plane --port 9999 --api-url http://localhost:8888
# Using environment variables
export HINDSIGHT_CP_DATAPLANE_API_URL=http://api.example.com
npx @vectorize-io/hindsight-control-plane
# Production deployment
PORT=80 HINDSIGHT_CP_DATAPLANE_API_URL=https://api.hindsight.io npx @vectorize-io/hindsight-control-plane
```
---
## Next Steps
+2 -8
View File
@@ -18,13 +18,7 @@ All local models (embedding, cross-encoder) are automatically downloaded from Hu
Used for fact extraction, entity resolution, opinion generation, and answer synthesis.
**Supported providers:** OpenAI, Gemini, Groq, Ollama, and **any OpenAI-compatible API**
:::tip OpenAI-Compatible Providers
Hindsight works with any provider that exposes an OpenAI-compatible API (e.g., Azure OpenAI). Simply set `HINDSIGHT_API_LLM_PROVIDER=openai` and configure `HINDSIGHT_API_LLM_BASE_URL` to point to your provider's endpoint.
See [Configuration](./configuration#llm-provider) for setup examples.
:::
**Supported providers:** OpenAI, Gemini, Groq, Ollama
### Tested Models
@@ -70,7 +64,7 @@ export HINDSIGHT_API_LLM_MODEL=gemini-2.0-flash
# Ollama (local)
export HINDSIGHT_API_LLM_PROVIDER=ollama
export HINDSIGHT_API_LLM_BASE_URL=http://localhost:11434/v1
export HINDSIGHT_API_LLM_MODEL=gpt-oss-20b
export HINDSIGHT_API_LLM_MODEL=llama3.1
```
**Note:** The LLM is the primary bottleneck for retain operations. See [Performance](./performance) for optimization strategies.
+5 -3
View File
@@ -25,9 +25,11 @@ Web UI for managing and exploring your memory banks:
- View ingestion history and operations
- Test recall queries interactively
The Control Plane connects to the API service and provides a visual interface for development and debugging.
```
hindsight-control-plane # Default port: 9999
```
For bare metal deployments, you can run the Control Plane standalone using npx. See [Installation - Bare Metal](./installation#control-plane) for details.
The Control Plane connects to the API service and provides a visual interface for development and debugging.
## Deployment Options
@@ -37,4 +39,4 @@ For bare metal deployments, you can run the Control Plane standalone using npx.
| **Helm / Kubernetes** | Separate pods | Production, scaling |
| **Bare metal** | Run independently | Custom deployments |
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling. For bare metal, you can run the API via pip and the Control Plane via npx.
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling.
-19
View File
@@ -177,25 +177,6 @@ hindsight memory recall <bank_id> "query" -o yaml
| `--help` | Show help |
| `--version` | Show version |
## Control Plane UI
Launch the web-based Control Plane UI directly from the CLI:
```bash
hindsight ui
```
This runs the Control Plane locally on port 9999 using the API URL from your configuration. The UI provides:
- **Memory bank management** — Browse and manage all your banks
- **Entity explorer** — Visualize the knowledge graph
- **Query testing** — Interactive recall and reflect testing
- **Operation history** — View ingestion and processing logs
:::tip
The UI command requires Node.js to be installed. It automatically downloads and runs the `@vectorize-io/hindsight-control-plane` package via npx.
:::
## Interactive Explorer
Launch the TUI explorer for visual navigation of your memory banks:
@@ -7,35 +7,28 @@ sidebar_position: 2
Hindsight provides a fully local MCP server that runs entirely on your machine with an embedded PostgreSQL database. No external server or database setup required.
This is ideal for:
- **Personal use with Claude Desktop** — Give Claude long-term memory across conversations
- **Personal use with Claude Code** — Give Claude long-term memory across conversations
- **Development and testing** — Quick setup without infrastructure
- **Privacy-focused setups** — All data stays on your machine
## Quick Install
## Quick Start
### With uvx (recommended)
```bash
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
--app claude-desktop \
--set HINDSIGHT_API_LLM_API_KEY=sk-...
uvx --from hindsight-api hindsight-local-mcp
```
This script will:
1. Install [uv](https://docs.astral.sh/uv/) if not already installed
2. Configure Claude Desktop to use the Hindsight MCP server
3. Set the provided environment variables in the MCP configuration
### With pip
:::info Other MCP Applications
The quick install script currently supports Claude Desktop only. For other MCP-compatible applications (Cursor, Cline, etc.), follow the [Manual Configuration](#manual-configuration) steps below.
:::
```bash
pip install hindsight-api
hindsight-local-mcp
```
## Manual Configuration
## Claude Code Configuration
Add the following to your MCP client's configuration. For Claude Desktop:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
For other MCP clients, refer to their documentation for the configuration file location.
Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
```json
{
@@ -44,7 +37,7 @@ For other MCP clients, refer to their documentation for the configuration file l
"command": "uvx",
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
"env": {
"HINDSIGHT_API_LLM_API_KEY": "sk-..."
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
}
}
}
@@ -62,7 +55,7 @@ By default, memories are stored in a bank called `mcp`. To use a different bank:
"command": "uvx",
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
"env": {
"HINDSIGHT_API_LLM_API_KEY": "sk-...",
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key",
"HINDSIGHT_API_MCP_LOCAL_BANK_ID": "my-personal-memory"
}
}
@@ -79,20 +72,6 @@ All standard [Hindsight configuration variables](/developer/configuration) are s
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `HINDSIGHT_API_MCP_LOCAL_BANK_ID` | No | `mcp` | Memory bank ID to use |
| `HINDSIGHT_API_MCP_INSTRUCTIONS` | No | - | Additional instructions appended to both `retain` and `recall` tools |
### Customizing Tool Behavior
You can customize what gets stored by adding instructions to the tools. Re-run the install script with the additional `--set` flag:
```bash
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
--app claude-desktop \
--set HINDSIGHT_API_LLM_API_KEY=sk-... \
--set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, code you write, and files you modify."
```
These instructions are appended to the default tool descriptions, guiding Claude on when and how to use the memory tools.
## Available Tools
-301
View File
@@ -1,301 +0,0 @@
#!/bin/bash
#
# Install Hindsight MCP server for Claude Desktop
#
# Usage:
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=YOUR_KEY
#
# Options:
# --app Required. Target application (currently only: claude-desktop)
# --set ENV=VALUE Set environment variable (can be repeated)
#
# Examples:
# # With OpenAI
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
# --app claude-desktop \
# --set HINDSIGHT_API_LLM_API_KEY=sk-...
#
# # With Ollama (local LLM)
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
# --app claude-desktop \
# --set HINDSIGHT_API_LLM_PROVIDER=ollama \
# --set HINDSIGHT_API_LLM_MODEL=llama3.2
#
# # With custom memory instructions
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
# --app claude-desktop \
# --set HINDSIGHT_API_LLM_API_KEY=sk-... \
# --set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take and code you write."
#
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_info() {
echo -e "${BLUE}${NC} $1"
}
print_success() {
echo -e "${GREEN}✓${NC} $1"
}
print_error() {
echo -e "${RED}✗${NC} $1"
exit 1
}
print_warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
print_banner() {
echo ""
# ANSI logo
echo -e " \033[38;2;9;127;184m▄\033[0m\033[48;2;8;130;178m\033[38;2;5;133;186m▄\033[0m \033[48;2;10;143;160m\033[38;2;10;143;165m▄\033[0m\033[38;2;7;140;156m▄\033[0m "
echo -e " \033[38;2;8;125;192m▄\033[0m \033[38;2;3;132;191m▀\033[0m\033[38;2;2;133;192m▄\033[0m \033[38;2;3;132;180m▄\033[0m\033[38;2;1;137;184m▄\033[0m\033[38;2;3;133;174m▄\033[0m \033[38;2;3;142;176m▄\033[0m\033[38;2;4;142;169m▀\033[0m \033[38;2;10;144;164m▄\033[0m "
echo -e "\033[38;2;6;121;195m▀\033[0m\033[38;2;5;128;203m▀\033[0m\033[48;2;5;124;195m\033[38;2;3;125;200m▄\033[0m\033[38;2;2;126;196m▄\033[0m\033[48;2;3;128;188m\033[38;2;1;131;196m▄\033[0m\033[48;2;0;152;219m\033[38;2;2;131;191m▄\033[0m\033[38;2;1;141;196m▀\033[0m\033[38;2;1;135;183m▀\033[0m\033[38;2;1;148;198m▀\033[0m\033[48;2;1;156;202m\033[38;2;2;135;180m▄\033[0m\033[48;2;4;134;169m\033[38;2;1;137;177m▄\033[0m\033[38;2;3;138;173m▄\033[0m\033[48;2;6;137;165m\033[38;2;2;140;170m▄\033[0m\033[38;2;7;144;169m▀\033[0m\033[38;2;7;139;158m▀\033[0m"
echo -e " \033[48;2;2;128;202m\033[38;2;2;124;201m▄\033[0m\033[48;2;1;130;201m\033[38;2;0;135;212m▄\033[0m\033[38;2;2;128;196m▄\033[0m \033[48;2;2;142;204m\033[38;2;7;138;199m▄\033[0m \033[38;2;1;135;186m▄\033[0m\033[48;2;1;142;186m\033[38;2;2;144;194m▄\033[0m\033[48;2;3;138;176m\033[38;2;2;134;176m▄\033[0m "
echo -e " \033[48;2;8;118;200m\033[38;2;8;121;209m▄\033[0m\033[38;2;3;121;203m▀\033[0m \033[38;2;3;122;192m▀\033[0m\033[38;2;1;138;216m▀\033[0m\033[48;2;0;138;210m\033[38;2;3;128;198m▄\033[0m\033[48;2;0;126;188m\033[38;2;2;131;198m▄\033[0m\033[48;2;0;142;205m\033[38;2;3;132;193m▄\033[0m\033[38;2;1;140;196m▀\033[0m \033[38;2;4;134;175m▀\033[0m\033[48;2;13;135;167m\033[38;2;8;136;174m▄\033[0m "
echo ""
echo -e " ${BLUE}HINDSIGHT MCP INSTALLER${NC}"
echo ""
}
# Parse arguments
APP=""
declare -a ENV_VARS=()
while [[ $# -gt 0 ]]; do
case $1 in
--app)
APP="$2"
shift 2
;;
--set)
ENV_VARS+=("$2")
shift 2
;;
-h|--help)
echo "Usage: $0 --app <app> --set ENV=VALUE [--set ENV2=VALUE2 ...]"
echo ""
echo "Options:"
echo " --app Required. Target application (currently only: claude-desktop)"
echo " --set ENV=VALUE Set environment variable (can be repeated)"
echo ""
echo "Examples:"
echo " # With OpenAI"
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=sk-..."
echo ""
echo " # With Ollama (local LLM, no API key needed)"
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_PROVIDER=ollama --set HINDSIGHT_API_LLM_MODEL=llama3.2"
exit 0
;;
*)
print_error "Unknown option: $1. Use --help for usage."
;;
esac
done
# Validate required arguments
if [ -z "$APP" ]; then
print_error "Missing required argument: --app. Use --help for usage."
fi
if [ "$APP" != "claude-desktop" ]; then
print_error "Unsupported app: $APP. Currently only 'claude-desktop' is supported."
fi
# Detect OS
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos" ;;
Linux*) echo "linux" ;;
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
*) echo "unknown" ;;
esac
}
OS=$(detect_os)
# Get Claude Desktop config path based on OS
get_claude_config_path() {
case "$OS" in
macos)
echo "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
;;
linux)
echo "$HOME/.config/Claude/claude_desktop_config.json"
;;
windows)
echo "$APPDATA/Claude/claude_desktop_config.json"
;;
*)
print_error "Unsupported operating system: $OS"
;;
esac
}
# Check if uvx is installed and return its path
find_uvx() {
# Check if in PATH
if command -v uvx &> /dev/null; then
command -v uvx
return 0
fi
# Check common installation paths
local paths=(
"$HOME/.local/bin/uvx"
"$HOME/.cargo/bin/uvx"
"/usr/local/bin/uvx"
)
for path in "${paths[@]}"; do
if [ -f "$path" ]; then
echo "$path"
return 0
fi
done
return 1
}
# Install uv (which includes uvx)
install_uv() {
print_info "Installing uv..."
if [ "$OS" = "windows" ]; then
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
else
curl -LsSf https://astral.sh/uv/install.sh | sh
fi
# Source the env to get uvx in path
if [ -f "$HOME/.local/bin/env" ]; then
source "$HOME/.local/bin/env"
fi
# Find uvx again
if ! UVX_PATH=$(find_uvx); then
print_error "uv installed but uvx not found. Please check your installation."
fi
print_success "uv installed successfully"
}
# Update Claude Desktop config
update_claude_config() {
local config_path="$1"
local uvx_path="$2"
shift 2
local env_vars=("$@")
# Create config directory if it doesn't exist
mkdir -p "$(dirname "$config_path")"
# Check if jq is available (needed for both new and existing configs)
if ! command -v jq &> /dev/null; then
print_warning "jq not found. Installing..."
if [ "$OS" = "macos" ]; then
if command -v brew &> /dev/null; then
brew install jq
else
print_error "Please install jq: brew install jq"
fi
elif [ "$OS" = "linux" ]; then
if command -v apt-get &> /dev/null; then
sudo apt-get install -y jq
elif command -v yum &> /dev/null; then
sudo yum install -y jq
else
print_error "Please install jq manually"
fi
fi
fi
# Build the env object from env_vars array
local env_json="{}"
for env_var in "${env_vars[@]}"; do
local key="${env_var%%=*}"
local value="${env_var#*=}"
env_json=$(echo "$env_json" | jq --arg k "$key" --arg v "$value" '. + {($k): $v}')
done
# Build the hindsight server config
local hindsight_config
hindsight_config=$(jq -n \
--arg uvx "$uvx_path" \
--argjson env "$env_json" \
'{
"command": $uvx,
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
"env": $env
}')
# Check if config file exists and has content
if [ -f "$config_path" ] && [ -s "$config_path" ]; then
print_info "Updating existing Claude Desktop config..."
# Backup existing config
cp "$config_path" "${config_path}.backup"
print_info "Backed up existing config to ${config_path}.backup"
# Add or update hindsight server in existing config
local new_config
new_config=$(jq --argjson hs "$hindsight_config" '.mcpServers.hindsight = $hs' "$config_path")
echo "$new_config" > "$config_path"
else
print_info "Creating new Claude Desktop config..."
# Create new config with hindsight server
local new_config
new_config=$(jq -n --argjson hs "$hindsight_config" '{"mcpServers": {"hindsight": $hs}}')
echo "$new_config" > "$config_path"
fi
print_success "Claude Desktop config updated: $config_path"
}
# Main installation flow
main() {
print_banner
print_info "App: $APP"
if [ ${#ENV_VARS[@]} -gt 0 ]; then
print_info "Environment variables: ${#ENV_VARS[@]} configured"
fi
echo ""
# Step 1: Check/Install uvx
print_info "Checking for uvx..."
if UVX_PATH=$(find_uvx); then
print_success "uvx found at: $UVX_PATH"
else
print_warning "uvx not found. Installing uv..."
install_uv
UVX_PATH=$(find_uvx)
fi
# Step 2: Update Claude Desktop config
CONFIG_PATH=$(get_claude_config_path)
print_info "Configuring Claude Desktop..."
update_claude_config "$CONFIG_PATH" "$UVX_PATH" "${ENV_VARS[@]}"
# Done!
echo ""
print_success "Installation complete!"
echo ""
print_info "Next steps:"
echo " 1. Restart Claude Desktop"
echo " 2. Look for the 'hindsight' tools (retain, recall) in Claude"
echo ""
}
main "$@"
@@ -1,6 +1,6 @@
[project]
name = "hindsight-litellm"
version = "0.1.11"
version = "0.1.8"
description = "Universal LLM memory integration via LiteLLM - works with 100+ providers"
readme = "README.md"
requires-python = ">=3.10"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "hindsight-all"
version = "0.1.11"
version = "0.1.8"
description = "Hindsight: Agent Memory That Works Like Human Memory - All-in-One Bundle"
readme = "README.md"
requires-python = ">=3.11"
+5429 -117
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -2,7 +2,7 @@
set -e
ROOT_DIR="$(git rev-parse --show-toplevel)"
cd "$ROOT_DIR" || exit 1
cd "$ROOT_DIR/hindsight-control-plane" || exit 1
# Check if .env exists in workspace root
if [ ! -f "$ROOT_DIR/.env" ]; then
@@ -30,4 +30,4 @@ fi
export HOSTNAME="${HINDSIGHT_CP_HOSTNAME:-0.0.0.0}"
# Run dev server
npm run dev -w @vectorize-io/hindsight-control-plane
npm run dev -w hindsight-control-plane
-169
View File
@@ -1,169 +0,0 @@
#!/bin/bash
#
# Docker Smoke Test Script
#
# Tests that a Hindsight Docker image starts correctly and becomes healthy.
# Can be run locally or in CI pipelines.
#
# Usage:
# ./scripts/docker-smoke-test.sh <image> [target]
#
# Arguments:
# image - Docker image to test (e.g., hindsight-api:test, ghcr.io/vectorize-io/hindsight:latest)
# target - Optional: 'cp-only' for control plane, otherwise assumes API image (default: api)
#
# Environment variables:
# GROQ_API_KEY - Required for API/standalone images (LLM verification)
# HINDSIGHT_API_LLM_PROVIDER - LLM provider (default: groq)
# HINDSIGHT_API_LLM_MODEL - LLM model (default: llama-3.3-70b-versatile)
# SMOKE_TEST_TIMEOUT - Timeout in seconds (default: 120)
# SMOKE_TEST_CONTAINER_NAME - Container name (default: hindsight-smoke-test)
#
# Examples:
# # Test a locally built image
# ./scripts/docker-smoke-test.sh hindsight-api:test
#
# # Test a released image
# ./scripts/docker-smoke-test.sh ghcr.io/vectorize-io/hindsight:latest
#
# # Test control plane image
# ./scripts/docker-smoke-test.sh hindsight-control-plane:test cp-only
#
# Exit codes:
# 0 - Success (container healthy)
# 1 - Failure (container not healthy within timeout)
# 2 - Invalid arguments
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# Configuration
IMAGE="${1:-}"
TARGET="${2:-api}"
TIMEOUT="${SMOKE_TEST_TIMEOUT:-120}"
CONTAINER_NAME="${SMOKE_TEST_CONTAINER_NAME:-hindsight-smoke-test}"
LLM_PROVIDER="${HINDSIGHT_API_LLM_PROVIDER:-groq}"
LLM_MODEL="${HINDSIGHT_API_LLM_MODEL:-llama-3.3-70b-versatile}"
# Validate arguments
if [ -z "$IMAGE" ]; then
echo -e "${RED}Error: Image argument is required${NC}"
echo ""
echo "Usage: $0 <image> [target]"
echo ""
echo "Examples:"
echo " $0 hindsight-api:test"
echo " $0 ghcr.io/vectorize-io/hindsight:latest"
echo " $0 hindsight-control-plane:test cp-only"
exit 2
fi
# Determine health endpoint based on target
if [ "$TARGET" = "cp-only" ]; then
HEALTH_PORT=9999
HEALTH_PATH="/api/health"
NEEDS_LLM=false
else
HEALTH_PORT=8888
HEALTH_PATH="/health"
NEEDS_LLM=true
fi
# Check for required environment variables
if [ "$NEEDS_LLM" = true ] && [ -z "${GROQ_API_KEY:-}" ]; then
echo -e "${RED}Error: GROQ_API_KEY environment variable is required for API/standalone images${NC}"
echo "Set it with: export GROQ_API_KEY=your-api-key"
exit 2
fi
# Cleanup function
cleanup() {
echo "Cleaning up..."
docker stop "$CONTAINER_NAME" 2>/dev/null || true
docker rm "$CONTAINER_NAME" 2>/dev/null || true
}
# Set trap to cleanup on exit
trap cleanup EXIT
echo -e "${YELLOW}Starting smoke test for: ${IMAGE}${NC}"
echo " Target: $TARGET"
echo " Health endpoint: http://localhost:${HEALTH_PORT}${HEALTH_PATH}"
echo " Timeout: ${TIMEOUT}s"
echo ""
# Remove any existing container with the same name
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
# Start container based on target type
echo "Starting container..."
if [ "$TARGET" = "cp-only" ]; then
docker run -d --name "$CONTAINER_NAME" \
-p "${HEALTH_PORT}:${HEALTH_PORT}" \
"$IMAGE"
else
docker run -d --name "$CONTAINER_NAME" \
-e HINDSIGHT_API_LLM_PROVIDER="$LLM_PROVIDER" \
-e HINDSIGHT_API_LLM_API_KEY="${GROQ_API_KEY}" \
-e HINDSIGHT_API_LLM_MODEL="$LLM_MODEL" \
-p "${HEALTH_PORT}:${HEALTH_PORT}" \
"$IMAGE"
fi
# Wait for health endpoint
echo "Waiting for health endpoint at http://localhost:${HEALTH_PORT}${HEALTH_PATH}..."
start_time=$(date +%s)
for i in $(seq 1 "$TIMEOUT"); do
if curl -sf "http://localhost:${HEALTH_PORT}${HEALTH_PATH}" > /dev/null 2>&1; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo ""
echo -e "${GREEN}Container is healthy after ${duration}s${NC}"
echo ""
echo "=== Health Response ==="
curl -s "http://localhost:${HEALTH_PORT}${HEALTH_PATH}" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:${HEALTH_PORT}${HEALTH_PATH}"
echo ""
echo ""
echo "=== Container Logs (last 50 lines) ==="
docker logs "$CONTAINER_NAME" 2>&1 | tail -50
echo ""
echo -e "${GREEN}Smoke test PASSED${NC}"
exit 0
fi
# Show progress every 10 seconds
if [ $((i % 10)) -eq 0 ]; then
echo " Still waiting... (${i}s)"
fi
# Check if container is still running
if ! docker ps -q -f "name=$CONTAINER_NAME" | grep -q .; then
echo ""
echo -e "${RED}Container exited unexpectedly!${NC}"
echo ""
echo "=== Container Logs ==="
docker logs "$CONTAINER_NAME" 2>&1
echo ""
echo -e "${RED}Smoke test FAILED${NC}"
exit 1
fi
sleep 1
done
# Timeout reached
echo ""
echo -e "${RED}Container failed to become healthy after ${TIMEOUT}s${NC}"
echo ""
echo "=== Container Logs ==="
docker logs "$CONTAINER_NAME" 2>&1
echo ""
echo -e "${RED}Smoke test FAILED${NC}"
exit 1
+2 -2
View File
@@ -65,7 +65,7 @@ fi
print_info "Updating version in all components..."
# Update Python packages
PYTHON_PACKAGES=("hindsight-api" "hindsight-dev" "hindsight" "hindsight-integrations/litellm")
PYTHON_PACKAGES=("hindsight-api" "hindsight-dev" "hindsight-dev/benchmarks" "hindsight" "hindsight-integrations/litellm")
for package in "${PYTHON_PACKAGES[@]}"; do
PYPROJECT_FILE="$package/pyproject.toml"
if [ -f "$PYPROJECT_FILE" ]; then
@@ -148,7 +148,7 @@ git add -A
git commit -m "Release v$VERSION
- Update version to $VERSION in all components
- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm
- Python packages: hindsight-api, hindsight-dev, hindsight-dev/benchmarks, hindsight-all, hindsight-litellm
- Python client: hindsight-clients/python
- TypeScript client: hindsight-clients/typescript
- Rust CLI: hindsight-cli
Generated
+4 -4
View File
@@ -1141,7 +1141,7 @@ wheels = [
[[package]]
name = "hindsight-all"
version = "0.1.11"
version = "0.1.8"
source = { editable = "hindsight" }
dependencies = [
{ name = "hindsight-api" },
@@ -1165,7 +1165,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-api"
version = "0.1.11"
version = "0.1.8"
source = { editable = "hindsight-api" }
dependencies = [
{ name = "alembic" },
@@ -1269,7 +1269,7 @@ dev = [
[[package]]
name = "hindsight-client"
version = "0.1.11"
version = "0.1.8"
source = { editable = "hindsight-clients/python" }
dependencies = [
{ name = "aiohttp" },
@@ -1303,7 +1303,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-dev"
version = "0.1.11"
version = "0.1.8"
source = { editable = "hindsight-dev" }
dependencies = [
{ name = "hindsight-api" },