Compare commits

...
20 Commits
Author SHA1 Message Date
Nicolò Boschi efbecb6423 fix: create report.pdf in working directory for retain.sh file upload examples 2026-02-20 15:40:35 +01:00
Nicolò Boschi 4b5b580410 fix: add include_facts to reflect client, fix retain.sh temp files, fix main-methods based_on access 2026-02-20 15:18:11 +01:00
Nicolò Boschi f8eb0c84c2 doc: improve api explanation 2026-02-20 15:12:22 +01:00
Nicolò Boschi 23165c244c doc: improve api explanation 2026-02-20 14:37:11 +01:00
Nicolò Boschi 7074893f70 doc: improve api explanation 2026-02-20 14:36:53 +01:00
Anton EvseevandClaude Opus 4.6 3f9eb27cd7 feat(openclaw): add autoRecall toggle and excludeProviders schema (#413)
Add `autoRecall` config option (default: true) to allow disabling
automatic memory recall injection when the host agent has its own
dedicated recall tool. This is backward compatible — existing
deployments continue auto-recalling as before.

Also add the existing `excludeProviders` field to the plugin.json
configSchema so it appears in the UI and docs.

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-20 09:31:46 +01:00
Nicolò Boschi 13c82bab60 fix: set hindsight-crewai version to 0.4.13 (#412) 2026-02-20 09:31:22 +01:00
Nicolò Boschi 4f431b4ace doc: 0.4.13 changelog (#411) 2026-02-19 20:48:50 +01:00
Nicolò Boschi 2993fdd2f9 Release v0.4.13
- Update version to 0.4.13 in all components
- Regenerate OpenAPI spec and client SDKs
- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm, hindsight-embed
- Python client: hindsight-clients/python
- TypeScript client: hindsight-clients/typescript
- Rust CLI: hindsight-cli
- Control Plane: hindsight-control-plane
- OpenClaw integration: hindsight-integrations/openclaw
- AI SDK integration: hindsight-integrations/ai-sdk
- Helm chart
- Sync documentation to version-0.4
2026-02-19 18:46:07 +01:00
Nicolò Boschi 325b5cc141 feat: switch default model to gpt-4o-mini (#410) 2026-02-19 18:43:52 +01:00
Nicolò Boschi 0758827d39 fix: npx hindsight-control-plane fails (#408) 2026-02-19 18:23:13 +01:00
Nicolò Boschi ea8163c56d fix(mcp): unify hindsight-mcp-local and server mcp (#407)
* fix(mcp): stateless param not supported anymore

* fixes

* fix: npx hindsight-control-plane fails
2026-02-19 17:57:17 +01:00
Nicolò Boschi ac73948706 fix: docker startup fails with named docker volumes (#405) 2026-02-19 16:42:27 +01:00
Nicolò Boschi 5569d4adba feat: include source facts in observation recall (#404)
* feat: include source facts in observation recall

* feat: include source facts in observation recall

* feat: include source facts in observation recall

* feat: include source facts in observation recall

* fix(cli): add missing source_facts field to IncludeOptions initializer
2026-02-19 14:54:19 +01:00
Nicolò Boschi e785b05831 fix(mcp): stateless param not supported anymore (#406) 2026-02-19 13:42:54 +01:00
Nicolò Boschi 58c4d65778 fix: reranker crashes on provider error (#403)
* fix: reranker crashes on provider error

* fix: reranker crashes on provider error
2026-02-19 11:38:37 +01:00
Derek Bouius c3ef1555bf fix: reduce temporal ordering offset from 10s to 10ms per fact (#402)
The 10-second offset per fact caused significant timestamp drift when
ingesting many items — e.g. 600 facts would shift the last fact by
~100 minutes from its actual event time. This broke timeline views
and made occurred_start/mentioned_at unreliable for temporal queries.

Reducing to 10ms preserves fact ordering while keeping timestamps
within ~8 seconds of the original values even for large batches.
2026-02-19 10:48:18 +01:00
Nicolò Boschi dcaa9f14ab fix: clients don't respect timeout setting (#400) 2026-02-19 10:47:47 +01:00
BenandClaude Opus 4.6 41db2960c5 feat: add CrewAI integration for persistent crew memory (#319)
* feat: add CrewAI integration for persistent crew memory

Implements a CrewAI ExternalMemory storage backend that maps CrewAI's
Storage interface (save/search/reset) to Hindsight's retain/recall/delete
APIs, giving crews long-term memory with fact extraction, entity tracking,
and temporal awareness across runs.

Key features:
- HindsightStorage: drop-in Storage backend for CrewAI ExternalMemory
- HindsightReflectTool: BaseTool exposing Hindsight's reflect API
- Per-agent memory banks with customizable bank resolver
- Async compatibility layer for CrewAI's threading model
- 35 unit tests, docs site page, example script

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: move CrewAI example to hindsight-cookbook

Move research_crew.py example from hindsight-integrations/crewai/examples/
to the cookbook repo and update the integration README to link there instead.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* ci: add GitHub Actions test job for CrewAI integration

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* ci: add uv.lock for frozen installs in CI

The test-crewai-integration CI job uses `uv sync --frozen` which
requires a committed lock file.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-18 17:05:52 +01:00
Nicolò Boschi f78278ea89 fix: document not tracked if has 0 extracted facts (#399)
* fix: document not tracked if has 0 extracted facts

* fix: document not tracked if has 0 extracted facts
2026-02-18 17:01:57 +01:00
291 changed files with 8927 additions and 2208 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# Supported providers: openai, groq, ollama, gemini, anthropic, lmstudio, vertexai
HINDSIGHT_API_LLM_PROVIDER=openai
HINDSIGHT_API_LLM_API_KEY=your-api-key-here
HINDSIGHT_API_LLM_MODEL=o3-mini
HINDSIGHT_API_LLM_MODEL=gpt-4o-mini
HINDSIGHT_API_LLM_BASE_URL=https://api.openai.com/v1
# Example: Anthropic Claude configuration
+15 -1
View File
@@ -46,6 +46,10 @@ jobs:
working-directory: ./hindsight-embed
run: uv build --out-dir dist
- name: Build hindsight-crewai
working-directory: ./hindsight-integrations/crewai
run: uv build --out-dir dist
# Publish in order (client and api first, then hindsight-all which depends on them)
- name: Publish hindsight-client to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
@@ -77,6 +81,12 @@ jobs:
packages-dir: ./hindsight-embed/dist
skip-existing: true
- name: Publish hindsight-crewai to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: ./hindsight-integrations/crewai/dist
skip-existing: true
# Upload artifacts for GitHub release
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -88,6 +98,7 @@ jobs:
hindsight/dist/*
hindsight-integrations/litellm/dist/*
hindsight-embed/dist/*
hindsight-integrations/crewai/dist/*
retention-days: 1
release-typescript-client:
@@ -268,11 +279,14 @@ jobs:
- name: Build
run: npm run build --workspace=hindsight-control-plane
- name: Verify standalone build
run: test -f hindsight-control-plane/standalone/server.js || (echo 'standalone/server.js missing - build failed' && exit 1)
- name: Publish to npm
working-directory: ./hindsight-control-plane
run: |
set +e
OUTPUT=$(npm publish --access public 2>&1)
OUTPUT=$(npm publish --access public --ignore-scripts 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
if [ $EXIT_CODE -ne 0 ]; then
+71 -41
View File
@@ -171,9 +171,9 @@ jobs:
test-rust-cli:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -343,7 +343,9 @@ jobs:
- name: Smoke test - verify container starts
if: matrix.variant == 'slim'
env:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_EMBEDDINGS_PROVIDER: openai
HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_RERANKER_PROVIDER: cohere
@@ -353,14 +355,13 @@ jobs:
test-api:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -414,9 +415,9 @@ jobs:
test-python-client:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
@@ -490,9 +491,9 @@ jobs:
test-typescript-client:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
@@ -571,9 +572,9 @@ jobs:
test-rust-client:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
@@ -651,9 +652,9 @@ jobs:
test-go-client:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
@@ -729,9 +730,9 @@ jobs:
test-openclaw-integration:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
HINDSIGHT_EMBED_PACKAGE_PATH: ${{ github.workspace }}/hindsight-embed
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -830,9 +831,9 @@ jobs:
test-integration:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -918,6 +919,35 @@ jobs:
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-crewai-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"
- name: Build crewai integration
working-directory: ./hindsight-integrations/crewai
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/crewai
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/crewai
run: uv run pytest tests -v
test-litellm-integration:
runs-on: ubuntu-latest
@@ -950,9 +980,9 @@ jobs:
test-embed:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
# Prefer CPU-only PyTorch in CI
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -994,13 +1024,13 @@ jobs:
test-hindsight-all:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
# For test_server_integration.py compatibility
HINDSIGHT_LLM_PROVIDER: groq
HINDSIGHT_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_LLM_PROVIDER: openai
HINDSIGHT_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_LLM_MODEL: gpt-4o-mini
# Prefer CPU-only PyTorch in CI
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -1043,9 +1073,9 @@ jobs:
runs-on: ubuntu-latest
needs: test-rust-cli
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
@@ -1135,9 +1165,9 @@ jobs:
test-upgrade:
runs-on: ubuntu-latest
env:
HINDSIGHT_API_LLM_PROVIDER: groq
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
HINDSIGHT_API_LLM_PROVIDER: openai
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_LLM_MODEL: gpt-4o-mini
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
+1 -1
View File
@@ -317,7 +317,7 @@ npm install
Required env vars:
- `HINDSIGHT_API_LLM_PROVIDER`: openai, anthropic, gemini, groq, ollama, lmstudio
- `HINDSIGHT_API_LLM_API_KEY`: Your API key
- `HINDSIGHT_API_LLM_MODEL`: Model name (e.g., o3-mini, claude-sonnet-4-20250514)
- `HINDSIGHT_API_LLM_MODEL`: Model name (e.g., gpt-4o-mini, claude-sonnet-4-20250514)
Optional (uses local models by default):
- `HINDSIGHT_API_EMBEDDINGS_PROVIDER`: local (default) or tei
+10
View File
@@ -170,6 +170,11 @@ RUN chown -R hindsight:hindsight /app
USER hindsight
# Create pg0 data directory as hindsight user so that Docker seeds new named
# volumes with correct ownership (UID 1000) on first use, avoiding the
# "Permission denied" error when mounting a fresh root-owned volume.
RUN mkdir -p /home/hindsight/.pg0
ENV PATH="/app/api/.venv/bin:${PATH}"
# Pre-download tiktoken encoding (ALWAYS - required for token counting even in air-gapped envs)
@@ -321,6 +326,11 @@ RUN chown -R hindsight:hindsight /app
USER hindsight
# Create pg0 data directory as hindsight user so that Docker seeds new named
# volumes with correct ownership (UID 1000) on first use, avoiding the
# "Permission denied" error when mounting a fresh root-owned volume.
RUN mkdir -p /home/hindsight/.pg0
ENV PATH="/app/api/.venv/bin:${PATH}"
# Pre-download tiktoken encoding (ALWAYS - required for token counting even in air-gapped envs)
+10 -10
View File
@@ -13,9 +13,9 @@
# 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)
# HINDSIGHT_API_LLM_API_KEY - Required for API/standalone images (LLM verification)
# HINDSIGHT_API_LLM_PROVIDER - LLM provider (default: openai)
# HINDSIGHT_API_LLM_MODEL - LLM model (default: gpt-4o-mini)
# HINDSIGHT_API_EMBEDDINGS_PROVIDER - Embeddings provider (optional, for slim images: openai, cohere, tei)
# HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY - OpenAI API key for embeddings (optional)
# HINDSIGHT_API_RERANKER_PROVIDER - Reranker provider (optional, for slim images: cohere, tei)
@@ -34,7 +34,7 @@
# ./docker/test-image.sh hindsight-control-plane:test cp-only
#
# # Test slim image with external providers
# export GROQ_API_KEY=gsk_xxx
# export HINDSIGHT_API_LLM_API_KEY=sk_xxx
# export HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
# export HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=sk-xxx
# export HINDSIGHT_API_RERANKER_PROVIDER=cohere
@@ -60,8 +60,8 @@ 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}"
LLM_PROVIDER="${HINDSIGHT_API_LLM_PROVIDER:-openai}"
LLM_MODEL="${HINDSIGHT_API_LLM_MODEL:-gpt-4o-mini}"
# Validate arguments
if [ -z "$IMAGE" ]; then
@@ -88,9 +88,9 @@ else
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"
if [ "$NEEDS_LLM" = true ] && [ -z "${HINDSIGHT_API_LLM_API_KEY:-}" ]; then
echo -e "${RED}Error: HINDSIGHT_API_LLM_API_KEY environment variable is required for API/standalone images${NC}"
echo "Set it with: export HINDSIGHT_API_LLM_API_KEY=your-api-key"
exit 2
fi
@@ -123,7 +123,7 @@ else
# Build docker run command with required and optional env vars
DOCKER_CMD="docker run -d --name $CONTAINER_NAME"
DOCKER_CMD="$DOCKER_CMD -e HINDSIGHT_API_LLM_PROVIDER=$LLM_PROVIDER"
DOCKER_CMD="$DOCKER_CMD -e HINDSIGHT_API_LLM_API_KEY=${GROQ_API_KEY}"
DOCKER_CMD="$DOCKER_CMD -e HINDSIGHT_API_LLM_API_KEY=${HINDSIGHT_API_LLM_API_KEY}"
DOCKER_CMD="$DOCKER_CMD -e HINDSIGHT_API_LLM_MODEL=$LLM_MODEL"
# Add optional embeddings provider config
+5 -9
View File
@@ -6,24 +6,17 @@
# It expects API keys to be set in environment variables.
#
# Usage:
# export GROQ_API_KEY=gsk_xxx
# export OPENAI_API_KEY=sk-xxx
# export COHERE_API_KEY=xxx
# ./docker/test-slim-local.sh
#
# Or inline:
# GROQ_API_KEY=gsk_xxx OPENAI_API_KEY=sk_xxx COHERE_API_KEY=xxx ./docker/test-slim-local.sh
# OPENAI_API_KEY=sk_xxx COHERE_API_KEY=xxx ./docker/test-slim-local.sh
#
set -euo pipefail
# Check for required API keys
if [ -z "${GROQ_API_KEY:-}" ]; then
echo "❌ Error: GROQ_API_KEY environment variable is required"
echo "Set it with: export GROQ_API_KEY=gsk_xxx"
exit 1
fi
if [ -z "${OPENAI_API_KEY:-}" ]; then
echo "❌ Error: OPENAI_API_KEY environment variable is required"
echo "Set it with: export OPENAI_API_KEY=sk-xxx"
@@ -41,7 +34,10 @@ IMAGE="${1:-hindsight-slim:test}"
echo "Testing image: $IMAGE"
echo ""
# Set up external providers
# Set up LLM and external providers
export HINDSIGHT_API_LLM_PROVIDER=openai
export HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY
export HINDSIGHT_API_LLM_MODEL=gpt-4o-mini
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
export HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=$OPENAI_API_KEY
export HINDSIGHT_API_RERANKER_PROVIDER=cohere
+2 -2
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: hindsight
description: Hindsight helm chart
type: application
version: 0.4.12
appVersion: "0.4.12"
version: 0.4.13
appVersion: "0.4.13"
keywords:
- ai
- memory
+1 -1
View File
@@ -46,4 +46,4 @@ __all__ = [
"RemoteTEICrossEncoder",
"LLMConfig",
]
__version__ = "0.4.12"
__version__ = "0.4.13"
+38 -7
View File
@@ -74,7 +74,7 @@ from hindsight_api.config import get_config
from hindsight_api.engine.db_utils import acquire_with_retry
from hindsight_api.engine.memory_engine import Budget, _get_tiktoken_encoding, fq_table
from hindsight_api.engine.reflect.observations import Observation
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES, TokenUsage
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES, MemoryFact, TokenUsage
from hindsight_api.engine.search.tags import TagsMatch
from hindsight_api.extensions import HttpExtension, OperationValidationError, load_extension
from hindsight_api.metrics import create_metrics_collector, get_metrics_collector, initialize_metrics
@@ -97,6 +97,12 @@ class ChunkIncludeOptions(BaseModel):
max_tokens: int = Field(default=8192, description="Maximum tokens for chunks (chunks may be truncated)")
class SourceFactsIncludeOptions(BaseModel):
"""Options for including source facts for observation-type results."""
max_tokens: int = Field(default=4096, description="Maximum tokens for source facts")
class IncludeOptions(BaseModel):
"""Options for including additional data in recall results."""
@@ -107,6 +113,10 @@ class IncludeOptions(BaseModel):
chunks: ChunkIncludeOptions | None = Field(
default=None, description="Include raw chunks. Set to {} to enable, null to disable (default: disabled)."
)
source_facts: SourceFactsIncludeOptions | None = Field(
default=None,
description="Include source facts for observation-type results. Set to {} to enable, null to disable (default: disabled).",
)
class RecallRequest(BaseModel):
@@ -189,6 +199,9 @@ class RecallResult(BaseModel):
metadata: dict[str, str] | None = None # User-defined metadata
chunk_id: str | None = None # Chunk this fact was extracted from
tags: list[str] | None = None # Visibility scope tags
source_fact_ids: list[str] | None = (
None # IDs of source facts (observation type only, when source_facts is enabled)
)
class EntityObservationResponse(BaseModel):
@@ -340,6 +353,9 @@ class RecallResponse(BaseModel):
default=None, description="Entity states for entities mentioned in results"
)
chunks: dict[str, ChunkData] | None = Field(default=None, description="Chunks for facts, keyed by chunk_id")
source_facts: dict[str, RecallResult] | None = Field(
default=None, description="Source facts for observation-type results, keyed by fact ID"
)
class EntityInput(BaseModel):
@@ -413,7 +429,6 @@ class RetainRequest(BaseModel):
},
],
"async": False,
"document_tags": ["user_a", "user_b"],
}
}
)
@@ -426,7 +441,8 @@ class RetainRequest(BaseModel):
)
document_tags: list[str] | None = Field(
default=None,
description="Tags applied to all items in this request. These are merged with any item-level tags.",
description="Deprecated. Use item-level tags instead.",
deprecated=True,
)
@@ -1959,6 +1975,10 @@ def _register_routes(app: FastAPI):
include_chunks = request.include.chunks is not None
max_chunk_tokens = request.include.chunks.max_tokens if include_chunks else 8192
# Determine source facts inclusion settings
include_source_facts = request.include.source_facts is not None
max_source_facts_tokens = request.include.source_facts.max_tokens if include_source_facts else 4096
pre_recall = time.time() - handler_start
# Run recall with tracing (record metrics)
with metrics.record_operation(
@@ -1977,14 +1997,16 @@ def _register_routes(app: FastAPI):
max_entity_tokens=max_entity_tokens,
include_chunks=include_chunks,
max_chunk_tokens=max_chunk_tokens,
include_source_facts=include_source_facts,
max_source_facts_tokens=max_source_facts_tokens,
request_context=request_context,
tags=request.tags,
tags_match=request.tags_match,
)
# Convert core MemoryFact objects to API RecallResult objects (excluding internal metrics)
recall_results = [
RecallResult(
def _fact_to_result(fact: "MemoryFact") -> RecallResult:
return RecallResult(
id=fact.id,
text=fact.text,
type=fact.fact_type,
@@ -1996,9 +2018,10 @@ def _register_routes(app: FastAPI):
document_id=fact.document_id,
chunk_id=fact.chunk_id,
tags=fact.tags,
source_fact_ids=fact.source_fact_ids,
)
for fact in core_result.results
]
recall_results = [_fact_to_result(fact) for fact in core_result.results]
# Convert chunks from engine to HTTP API format
chunks_response = None
@@ -2026,11 +2049,19 @@ def _register_routes(app: FastAPI):
],
)
# Convert source facts dict to API format
source_facts_response = None
if core_result.source_facts:
source_facts_response = {
fact_id: _fact_to_result(fact) for fact_id, fact in core_result.source_facts.items()
}
response = RecallResponse(
results=recall_results,
trace=core_result.trace,
entities=entities_response,
chunks=chunks_response,
source_facts=source_facts_response,
)
handler_duration = time.time() - handler_start
+6 -7
View File
@@ -78,10 +78,9 @@ def create_mcp_server(memory: MemoryEngine, multi_bank: bool = True) -> FastMCP:
If False, only expose bank-scoped tools without bank_id parameters.
Returns:
Configured FastMCP server instance with stateless_http enabled
Configured FastMCP server instance
"""
# Use stateless_http=True for Claude Code compatibility
mcp = FastMCP("hindsight-mcp-server", stateless_http=True)
mcp = FastMCP("hindsight-mcp-server")
# Configure and register tools using shared module
config = MCPToolsConfig(
@@ -211,9 +210,9 @@ class MCPMiddleware:
else:
# Create servers internally (for direct construction / tests)
self.multi_bank_server = create_mcp_server(memory, multi_bank=True)
self.multi_bank_app = self.multi_bank_server.http_app(path="/")
self.multi_bank_app = self.multi_bank_server.http_app(path="/", stateless_http=True)
self.single_bank_server = create_mcp_server(memory, multi_bank=False)
self.single_bank_app = self.single_bank_server.http_app(path="/")
self.single_bank_app = self.single_bank_server.http_app(path="/", stateless_http=True)
def _get_header(self, scope: dict, name: str) -> str | None:
"""Extract a header value from ASGI scope."""
@@ -379,9 +378,9 @@ def create_mcp_servers(memory: MemoryEngine):
Tuple of (multi_bank_server, single_bank_server, multi_bank_app, single_bank_app)
"""
multi_bank_server = create_mcp_server(memory, multi_bank=True)
multi_bank_app = multi_bank_server.http_app(path="/")
multi_bank_app = multi_bank_server.http_app(path="/", stateless_http=True)
single_bank_server = create_mcp_server(memory, multi_bank=False)
single_bank_app = single_bank_server.http_app(path="/")
single_bank_app = single_bank_server.http_app(path="/", stateless_http=True)
return multi_bank_server, single_bank_server, multi_bank_app, single_bank_app
+2 -5
View File
@@ -233,8 +233,6 @@ ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
ENV_MPFP_TOP_K_NEIGHBORS = "HINDSIGHT_API_MPFP_TOP_K_NEIGHBORS"
ENV_RECALL_MAX_CONCURRENT = "HINDSIGHT_API_RECALL_MAX_CONCURRENT"
ENV_RECALL_CONNECTION_BUDGET = "HINDSIGHT_API_RECALL_CONNECTION_BUDGET"
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
ENV_MENTAL_MODEL_REFRESH_CONCURRENCY = "HINDSIGHT_API_MENTAL_MODEL_REFRESH_CONCURRENCY"
# OpenTelemetry tracing configuration
@@ -316,7 +314,7 @@ DEFAULT_LLM_PROVIDER = "openai"
# Provider-specific default models
PROVIDER_DEFAULT_MODELS = {
"openai": "o3-mini",
"openai": "gpt-4o-mini",
"anthropic": "claude-haiku-4-5-20251001",
"gemini": "gemini-2.5-flash",
"groq": "openai/gpt-oss-120b",
@@ -327,7 +325,7 @@ PROVIDER_DEFAULT_MODELS = {
"claude-code": "claude-sonnet-4-5-20250929",
"mock": "mock-model",
}
DEFAULT_LLM_MODEL = "o3-mini" # Fallback if provider not in table
DEFAULT_LLM_MODEL = "gpt-4o-mini" # Fallback if provider not in table
DEFAULT_LLM_MAX_CONCURRENT = 32
DEFAULT_LLM_MAX_RETRIES = 10 # Max retry attempts for LLM API calls
DEFAULT_LLM_INITIAL_BACKOFF = 1.0 # Initial backoff in seconds for retry exponential backoff
@@ -389,7 +387,6 @@ DEFAULT_GRAPH_RETRIEVER = "link_expansion" # Options: "link_expansion", "mpfp",
DEFAULT_MPFP_TOP_K_NEIGHBORS = 20 # Fan-out limit per node in MPFP graph traversal
DEFAULT_RECALL_MAX_CONCURRENT = 32 # Max concurrent recall operations per worker
DEFAULT_RECALL_CONNECTION_BUDGET = 4 # Max concurrent DB connections per recall operation
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
DEFAULT_MENTAL_MODEL_REFRESH_CONCURRENCY = 8 # Max concurrent mental model refreshes
# Retain settings
@@ -2020,6 +2020,8 @@ class MemoryEngine(MemoryEngineInterface):
max_entity_tokens: int = 500,
include_chunks: bool = False,
max_chunk_tokens: int = 8192,
include_source_facts: bool = False,
max_source_facts_tokens: int = 4096,
request_context: "RequestContext",
tags: list[str] | None = None,
tags_match: TagsMatch = "any",
@@ -2159,6 +2161,8 @@ class MemoryEngine(MemoryEngineInterface):
tags_match=tags_match,
connection_budget=_connection_budget,
quiet=_quiet,
include_source_facts=include_source_facts,
max_source_facts_tokens=max_source_facts_tokens,
)
break # Success - exit retry loop
except Exception as e:
@@ -2283,6 +2287,8 @@ class MemoryEngine(MemoryEngineInterface):
tags_match: TagsMatch = "any",
connection_budget: int | None = None,
quiet: bool = False,
include_source_facts: bool = False,
max_source_facts_tokens: int = 4096,
) -> RecallResultModel:
"""
Search implementation with modular retrieval and reranking.
@@ -2628,6 +2634,8 @@ class MemoryEngine(MemoryEngineInterface):
rerank_span.set_attribute("hindsight.bank_id", bank_id)
rerank_span.set_attribute("hindsight.candidates_count", len(merged_candidates))
scored_results: list = []
pre_filtered_count = 0
try:
# Ensure reranker is initialized (for lazy initialization mode)
await reranker_instance.ensure_initialized()
@@ -2635,7 +2643,6 @@ class MemoryEngine(MemoryEngineInterface):
# Pre-filter candidates to reduce reranking cost (RRF already provides good ranking)
# This is especially important for remote rerankers with network latency
reranker_max_candidates = get_config().reranker_max_candidates
pre_filtered_count = 0
if len(merged_candidates) > reranker_max_candidates:
# Sort by RRF score and take top candidates
merged_candidates.sort(key=lambda mc: mc.rrf_score, reverse=True)
@@ -2878,6 +2885,74 @@ class MemoryEngine(MemoryEngineInterface):
)
top_results_dicts.append(result_dict)
# Fetch source facts for observation-type results (mirrors chunks pattern)
source_fact_ids_by_obs: dict[str, list[str]] = {} # obs_id -> [source_id, ...]
source_facts_dict: dict[str, MemoryFact] | None = None
if include_source_facts:
observation_ids = [uuid.UUID(sr.id) for sr in top_scored if sr.retrieval.fact_type == "observation"]
if observation_ids:
async with acquire_with_retry(pool) as sf_conn:
# Fetch source_memory_ids for all observation results
obs_rows = await sf_conn.fetch(
f"""
SELECT id, source_memory_ids
FROM {fq_table("memory_units")}
WHERE id = ANY($1::uuid[]) AND fact_type = 'observation'
""",
observation_ids,
)
# Collect unique source IDs in order of first appearance
seen_source_ids: set[str] = set()
source_ids_ordered: list[str] = []
for obs_row in obs_rows:
obs_id = str(obs_row["id"])
sids = [str(s) for s in (obs_row["source_memory_ids"] or [])]
source_fact_ids_by_obs[obs_id] = sids
for sid in sids:
if sid not in seen_source_ids:
source_ids_ordered.append(sid)
seen_source_ids.add(sid)
# Fetch source fact content up to token budget
if source_ids_ordered:
import uuid as uuid_module
source_rows = await sf_conn.fetch(
f"""
SELECT id, text, fact_type, context, occurred_start, occurred_end,
mentioned_at, document_id, chunk_id, tags
FROM {fq_table("memory_units")}
WHERE id = ANY($1::uuid[])
""",
[uuid_module.UUID(sid) for sid in source_ids_ordered],
)
source_row_by_id = {str(r["id"]): r for r in source_rows}
encoding = _get_tiktoken_encoding()
source_facts_dict = {}
total_source_tokens = 0
for sid in source_ids_ordered:
if sid not in source_row_by_id:
continue
r = source_row_by_id[sid]
fact_tokens = len(encoding.encode(r["text"]))
if total_source_tokens + fact_tokens > max_source_facts_tokens:
break
source_facts_dict[sid] = MemoryFact(
id=sid,
text=r["text"],
fact_type=r["fact_type"],
context=r["context"],
occurred_start=r["occurred_start"].isoformat() if r["occurred_start"] else None,
occurred_end=r["occurred_end"].isoformat() if r["occurred_end"] else None,
mentioned_at=r["mentioned_at"].isoformat() if r["mentioned_at"] else None,
document_id=r["document_id"],
chunk_id=str(r["chunk_id"]) if r["chunk_id"] else None,
tags=r["tags"] or None,
)
total_source_tokens += fact_tokens
# Get entities for each fact if include_entities is requested
fact_entity_map = {} # unit_id -> list of (entity_id, entity_name)
if include_entities and top_scored:
@@ -2923,6 +2998,7 @@ class MemoryEngine(MemoryEngineInterface):
document_id=result_dict.get("document_id"),
chunk_id=result_dict.get("chunk_id"),
tags=result_dict.get("tags"),
source_fact_ids=source_fact_ids_by_obs.get(result_id) if include_source_facts else None,
)
)
@@ -2976,7 +3052,13 @@ class MemoryEngine(MemoryEngineInterface):
if not quiet:
logger.info("\n" + "\n".join(log_buffer))
return RecallResultModel(results=memory_facts, trace=trace_dict, entities=entities_dict, chunks=chunks_dict)
return RecallResultModel(
results=memory_facts,
trace=trace_dict,
entities=entities_dict,
chunks=chunks_dict,
source_facts=source_facts_dict,
)
except Exception as e:
log_buffer.append(f"[RECALL {recall_id}] ERROR after {time.time() - recall_start:.3f}s: {str(e)}")
@@ -159,6 +159,10 @@ class MemoryFact(BaseModel):
None, description="ID of the chunk this fact was extracted from (format: bank_id_document_id_chunk_index)"
)
tags: list[str] | None = Field(None, description="Visibility scope tags associated with this fact")
source_fact_ids: list[str] | None = Field(
None,
description="IDs of source facts this observation was derived from (observation type only, when source_facts is enabled)",
)
class ChunkInfo(BaseModel):
@@ -226,6 +230,9 @@ class RecallResult(BaseModel):
chunks: dict[str, ChunkInfo] | None = Field(
None, description="Chunks for facts, keyed by '{document_id}_{chunk_index}'"
)
source_facts: dict[str, MemoryFact] | None = Field(
None, description="Source facts for observation-type results, keyed by fact ID"
)
class ReflectResult(BaseModel):
@@ -1274,8 +1274,8 @@ from .types import ExtractedFact as ExtractedFactType
logger = logging.getLogger(__name__)
# Each fact gets 10 seconds offset to preserve ordering within a document
SECONDS_PER_FACT = 10
# Each fact gets 10ms offset to preserve ordering within a document
SECONDS_PER_FACT = 0.01
async def extract_facts_from_contents_batch_api(
@@ -156,13 +156,22 @@ async def retain_batch(
)
if not extracted_facts:
# Still need to create document if document_id was provided
# Still need to create document if document_id was provided or chunks exist
from collections import defaultdict
docs_tracked = 0
async with acquire_with_retry(pool) as conn:
async with conn.transaction():
await fact_storage.ensure_bank_exists(conn, bank_id)
# Handle document tracking even with no facts
# Group contents by document_id (consistent with normal path)
contents_by_doc_early = defaultdict(list)
for idx, content_dict in enumerate(contents_dicts):
doc_id = content_dict.get("document_id")
contents_by_doc_early[doc_id].append((idx, content_dict))
if document_id:
# Legacy: single document_id parameter
combined_content = "\n".join([c.get("content", "") for c in contents_dicts])
# Collect tags from all content items and merge with document_tags
all_tags = set(document_tags or [])
@@ -187,45 +196,57 @@ async def retain_batch(
await fact_storage.handle_document_tracking(
conn, bank_id, document_id, combined_content, is_first_batch, retain_params, merged_tags
)
docs_tracked += 1
else:
# Check for per-item document_ids
from collections import defaultdict
# Handle per-item document_ids and/or chunks (mirrors normal path logic)
has_any_doc_ids = any(item.get("document_id") for item in contents_dicts)
contents_by_doc = defaultdict(list)
for idx, content_dict in enumerate(contents_dicts):
doc_id = content_dict.get("document_id")
if doc_id:
contents_by_doc[doc_id].append((idx, content_dict))
if has_any_doc_ids or chunks:
for original_doc_id, doc_contents in contents_by_doc_early.items():
should_create_doc = (original_doc_id is not None) or chunks
if not should_create_doc:
continue
for doc_id, doc_contents in contents_by_doc.items():
combined_content = "\n".join([c.get("content", "") for _, c in doc_contents])
# Collect tags from all content items for this document and merge with document_tags
all_tags = set(document_tags or [])
for _, item in doc_contents:
item_tags = item.get("tags", []) or []
all_tags.update(item_tags)
merged_tags = list(all_tags)
actual_doc_id = original_doc_id
if actual_doc_id is None:
# No document_id but have chunks - generate one
actual_doc_id = str(uuid.uuid4())
retain_params = {}
if doc_contents:
first_item = doc_contents[0][1]
if first_item.get("context"):
retain_params["context"] = first_item["context"]
if first_item.get("event_date"):
retain_params["event_date"] = (
first_item["event_date"].isoformat()
if hasattr(first_item["event_date"], "isoformat")
else str(first_item["event_date"])
)
if first_item.get("metadata"):
retain_params["metadata"] = first_item["metadata"]
await fact_storage.handle_document_tracking(
conn, bank_id, doc_id, combined_content, is_first_batch, retain_params, merged_tags
)
combined_content = "\n".join([c.get("content", "") for _, c in doc_contents])
all_tags = set(document_tags or [])
for _, item in doc_contents:
item_tags = item.get("tags", []) or []
all_tags.update(item_tags)
merged_tags = list(all_tags)
retain_params = {}
if doc_contents:
first_item = doc_contents[0][1]
if first_item.get("context"):
retain_params["context"] = first_item["context"]
if first_item.get("event_date"):
retain_params["event_date"] = (
first_item["event_date"].isoformat()
if hasattr(first_item["event_date"], "isoformat")
else str(first_item["event_date"])
)
if first_item.get("metadata"):
retain_params["metadata"] = first_item["metadata"]
await fact_storage.handle_document_tracking(
conn,
bank_id,
actual_doc_id,
combined_content,
is_first_batch,
retain_params,
merged_tags,
)
docs_tracked += 1
total_time = time.time() - start_time
doc_status = f"{docs_tracked} document(s) tracked" if docs_tracked > 0 else "no document tracked"
logger.info(
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (document tracked, no facts)"
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s ({doc_status}, no facts)"
)
return [[] for _ in contents], usage
+16 -134
View File
@@ -1,8 +1,14 @@
"""
Local MCP server for use with Claude Code (stdio transport).
Local MCP server entry point for use with Claude Code (HTTP transport).
This runs a fully local Hindsight instance with embedded PostgreSQL (pg0).
No external database or server required.
This is a thin wrapper around the main hindsight-api server that pre-configures
sensible defaults for local use (embedded PostgreSQL via pg0, warning log level).
The full API runs on localhost:8888. Configure Claude Code's MCP settings:
claude mcp add --transport http hindsight http://localhost:8888/mcp/
Or pinned to a specific bank (single-bank mode):
claude mcp add --transport http hindsight http://localhost:8888/mcp/default/
Run with:
hindsight-local-mcp
@@ -10,148 +16,24 @@ Run with:
Or with uvx:
uvx hindsight-api@latest hindsight-local-mcp
Configure in Claude Code's MCP settings:
{
"mcpServers": {
"hindsight": {
"command": "uvx",
"args": ["hindsight-api@latest", "hindsight-local-mcp"],
"env": {
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
}
}
}
}
Environment variables:
HINDSIGHT_API_LLM_API_KEY: Required. API key for LLM provider.
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_DATABASE_URL: Optional. Override database URL (default: pg0://hindsight-mcp).
"""
import logging
import os
import sys
from mcp.server.fastmcp import FastMCP
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,
)
from hindsight_api.mcp_tools import MCPToolsConfig, register_mcp_tools
# 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()
_log_level_map = {
"critical": logging.CRITICAL,
"error": logging.ERROR,
"warning": logging.WARNING,
"info": logging.INFO,
"debug": logging.DEBUG,
}
logging.basicConfig(
level=_log_level_map.get(_log_level_str, logging.WARNING),
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
stream=sys.stderr, # MCP uses stdout for protocol, logs go to stderr
)
logger = logging.getLogger(__name__)
def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
"""
Create a stdio MCP server with retain/recall tools.
def main() -> None:
"""Start the Hindsight API server with local defaults."""
# Set local defaults (only if not already configured by the user)
os.environ.setdefault("HINDSIGHT_API_DATABASE_URL", "pg0://hindsight-mcp")
Args:
bank_id: The memory bank ID to use for all operations.
memory: Optional MemoryEngine instance. If not provided, creates one with pg0.
from hindsight_api.main import main as api_main
Returns:
Configured FastMCP server instance.
"""
# Import here to avoid slow startup if just checking --help
from hindsight_api import MemoryEngine
# Create memory engine with pg0 embedded database if not provided
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")
# Configure and register tools using shared module
config = MCPToolsConfig(
bank_id_resolver=lambda: bank_id,
include_bank_id_param=False, # Local MCP uses fixed bank_id
tools={"retain", "recall"}, # Local MCP only has retain and recall
retain_description=retain_description,
recall_description=recall_description,
retain_fire_and_forget=True, # Local MCP uses fire-and-forget pattern
)
register_mcp_tools(mcp, memory, config)
return mcp
async def _initialize_and_run(bank_id: str):
"""Initialize memory and run the MCP server."""
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"
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
await memory.initialize()
# Create and run the server
mcp = create_local_mcp_server(bank_id, memory=memory)
await mcp.run_stdio_async()
def main():
"""Main entry point for the stdio MCP server."""
import asyncio
from hindsight_api.config import ENV_LLM_API_KEY, get_config
# Check for required environment variables
config = get_config()
if not config.llm_api_key:
print(f"Error: {ENV_LLM_API_KEY} environment variable is required", file=sys.stderr)
print("Set it in your MCP configuration or shell environment", file=sys.stderr)
sys.exit(1)
# 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
# Run the async initialization and server
asyncio.run(_initialize_and_run(bank_id))
api_main()
if __name__ == "__main__":
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "hindsight-api"
version = "0.4.12"
version = "0.4.13"
description = "Hindsight: Agent Memory That Works Like Human Memory"
readme = "README.md"
requires-python = ">=3.11"
+54 -1
View File
@@ -2,9 +2,13 @@
Tests for document tracking and upsert functionality.
"""
import logging
import pytest
from datetime import datetime, timezone
from unittest.mock import patch
import pytest
from hindsight_api import RequestContext
from hindsight_api.engine.response_models import TokenUsage
@pytest.mark.asyncio
@@ -311,3 +315,52 @@ async def test_document_persisted_with_zero_facts_async_submit(memory, request_c
finally:
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_document_stored_without_chunks_when_zero_facts(memory_no_llm_verify, request_context):
"""
Regression test: when 0 facts are extracted from chunked content, the document row
must be stored but no chunk rows should be written.
"""
bank_id = f"test_zero_facts_no_chunks_{datetime.now(timezone.utc).timestamp()}"
document_id = "doc-zero-facts-chunked"
# Content large enough to exceed default retain_chunk_size (3000 chars) so chunking is triggered
content = "Alice works at Google. " * 200 # ~4600 chars
async def mock_llm_zero_facts(*args, **kwargs):
response = {"facts": []}
if kwargs.get("return_usage", False):
return response, TokenUsage(input_tokens=10, output_tokens=2)
return response
try:
with patch("hindsight_api.engine.llm_wrapper.LLMProvider.call", new=mock_llm_zero_facts):
units = await memory_no_llm_verify.retain_async(
bank_id=bank_id,
content=content,
document_id=document_id,
request_context=request_context,
)
assert units == [], "Should return no memory units when LLM extracts zero facts"
# Document row must exist
doc = await memory_no_llm_verify.get_document(document_id, bank_id, request_context=request_context)
assert doc is not None, "Document row must be stored even when zero facts are extracted"
assert doc["id"] == document_id
assert doc["memory_unit_count"] == 0
# No chunk rows should be stored
pool = await memory_no_llm_verify._get_pool()
async with pool.acquire() as conn:
chunk_count = await conn.fetchval(
"SELECT COUNT(*) FROM chunks WHERE document_id = $1 AND bank_id = $2",
document_id,
bank_id,
)
assert chunk_count == 0, "No chunk rows should be stored when zero facts are extracted"
finally:
await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context)
-212
View File
@@ -1,212 +0,0 @@
"""Test local MCP server."""
import asyncio
import pytest
from unittest.mock import AsyncMock, MagicMock
@pytest.fixture
def mock_memory():
"""Create a mock MemoryEngine."""
memory = MagicMock()
memory._initialized = True
memory.retain_batch_async = AsyncMock()
memory.recall_async = AsyncMock(return_value=MagicMock(results=[]))
return memory
@pytest.mark.asyncio
async def test_local_mcp_server_retain(mock_memory):
"""Test that retain tool fires async and returns immediately."""
from hindsight_api.mcp_local import create_local_mcp_server
bank_id = "test-bank"
mcp_server = create_local_mcp_server(bank_id, memory=mock_memory)
# Get the tools
tools = mcp_server._tool_manager._tools
assert "retain" in tools
# Call retain
retain_tool = tools["retain"]
result = await retain_tool.fn(content="test content", context="test_context")
# Returns immediately with accepted status
assert result["status"] == "accepted"
# Wait for background task to complete
await asyncio.sleep(0.1)
# Verify the memory was called correctly
mock_memory.retain_batch_async.assert_called_once()
call_kwargs = mock_memory.retain_batch_async.call_args.kwargs
assert call_kwargs["bank_id"] == "test-bank"
assert call_kwargs["contents"] == [{"content": "test content", "context": "test_context"}]
@pytest.mark.asyncio
async def test_local_mcp_server_recall(mock_memory):
"""Test that recall tool calls memory.recall_async with correct params."""
from hindsight_api.mcp_local import create_local_mcp_server
from hindsight_api.engine.memory_engine import Budget
# Mock recall_async to return a proper pydantic model
mock_result = MagicMock()
mock_result.model_dump.return_value = {"results": []}
mock_memory.recall_async = AsyncMock(return_value=mock_result)
bank_id = "test-bank"
mcp_server = create_local_mcp_server(bank_id, memory=mock_memory)
# Get the tools
tools = mcp_server._tool_manager._tools
assert "recall" in tools
# Call recall
recall_tool = tools["recall"]
result = await recall_tool.fn(query="test query", max_tokens=2048)
# Result is a dict
assert isinstance(result, dict)
# Verify the memory was called correctly
mock_memory.recall_async.assert_called_once()
call_kwargs = mock_memory.recall_async.call_args.kwargs
assert call_kwargs["bank_id"] == "test-bank"
assert call_kwargs["query"] == "test query"
assert call_kwargs["max_tokens"] == 2048
assert call_kwargs["budget"] == Budget.HIGH
@pytest.mark.asyncio
async def test_local_mcp_server_retain_with_default_context(mock_memory):
"""Test that retain uses default context when not provided."""
from hindsight_api.mcp_local import create_local_mcp_server
bank_id = "test-bank"
mcp_server = create_local_mcp_server(bank_id, memory=mock_memory)
tools = mcp_server._tool_manager._tools
retain_tool = tools["retain"]
# Call retain without context
await retain_tool.fn(content="test content")
# Wait for background task
await asyncio.sleep(0.1)
call_kwargs = mock_memory.retain_batch_async.call_args.kwargs
assert call_kwargs["contents"] == [{"content": "test content", "context": "general"}]
@pytest.mark.asyncio
async def test_local_mcp_server_retain_error_handling(mock_memory):
"""Test that retain errors are logged but don't affect response."""
from hindsight_api.mcp_local import create_local_mcp_server
mock_memory.retain_batch_async = AsyncMock(side_effect=Exception("Test error"))
mcp_server = create_local_mcp_server("test-bank", memory=mock_memory)
tools = mcp_server._tool_manager._tools
retain_tool = tools["retain"]
# Retain returns immediately with accepted status (fire and forget)
result = await retain_tool.fn(content="test content")
assert result["status"] == "accepted"
# Wait for background task to complete (and log error)
await asyncio.sleep(0.1)
@pytest.mark.asyncio
async def test_local_mcp_server_recall_error_handling(mock_memory):
"""Test that recall handles errors gracefully."""
from hindsight_api.mcp_local import create_local_mcp_server
mock_memory.recall_async = AsyncMock(side_effect=Exception("Test error"))
mcp_server = create_local_mcp_server("test-bank", memory=mock_memory)
tools = mcp_server._tool_manager._tools
recall_tool = tools["recall"]
result = await recall_tool.fn(query="test query")
# Result is a dict with error
assert isinstance(result, dict)
assert "error" in result
assert result["results"] == []
@pytest.mark.asyncio
async def test_local_mcp_server_recall_with_defaults(mock_memory):
"""Test that recall uses default max_tokens and HIGH budget."""
from hindsight_api.mcp_local import create_local_mcp_server
from hindsight_api.engine.memory_engine import Budget
mock_result = MagicMock()
mock_result.model_dump.return_value = {"results": []}
mock_memory.recall_async = AsyncMock(return_value=mock_result)
mcp_server = create_local_mcp_server("test-bank", memory=mock_memory)
tools = mcp_server._tool_manager._tools
recall_tool = tools["recall"]
# Call with defaults
await recall_tool.fn(query="test query")
call_kwargs = mock_memory.recall_async.call_args.kwargs
assert call_kwargs["max_tokens"] == 4096
assert call_kwargs["budget"] == Budget.HIGH
@pytest.mark.asyncio
async def test_local_mcp_server_retain_with_timestamp(mock_memory):
"""Test that retain passes timestamp as event_date."""
from datetime import datetime, timezone
from hindsight_api.mcp_local import create_local_mcp_server
mcp_server = create_local_mcp_server("test-bank", memory=mock_memory)
tools = mcp_server._tool_manager._tools
retain_tool = tools["retain"]
# Call retain with timestamp
result = await retain_tool.fn(
content="test content", context="test_context", timestamp="2024-01-15T10:30:00Z"
)
assert result["status"] == "accepted"
# Wait for background task
await asyncio.sleep(0.1)
call_kwargs = mock_memory.retain_batch_async.call_args.kwargs
contents = call_kwargs["contents"]
assert len(contents) == 1
assert contents[0]["content"] == "test content"
assert contents[0]["context"] == "test_context"
assert "event_date" in contents[0]
assert contents[0]["event_date"] == datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc)
@pytest.mark.asyncio
async def test_local_mcp_server_retain_with_invalid_timestamp(mock_memory):
"""Test that retain rejects invalid timestamp format."""
from hindsight_api.mcp_local import create_local_mcp_server
mcp_server = create_local_mcp_server("test-bank", memory=mock_memory)
tools = mcp_server._tool_manager._tools
retain_tool = tools["retain"]
# Call retain with invalid timestamp
result = await retain_tool.fn(content="test content", timestamp="not-a-date")
assert result["status"] == "error"
assert "Invalid timestamp format" in result["message"]
# Verify retain_batch_async was NOT called
mock_memory.retain_batch_async.assert_not_called()
@@ -93,7 +93,7 @@ def test_per_operation_provider_default_model():
config = HindsightConfig.from_env()
# Global LLM should use OpenAI default
assert config.llm_model == "o3-mini", f"Expected o3-mini, got {config.llm_model}"
assert config.llm_model == "gpt-4o-mini", f"Expected gpt-4o-mini, got {config.llm_model}"
# Retain should use Anthropic default
assert (
@@ -0,0 +1,71 @@
"""
Regression test for UnboundLocalError in recall when the reranker raises.
Before the fix, `scored_results` and `pre_filtered_count` were only assigned
inside the `try` block, but referenced in the `finally` block. If
`reranker_instance.rerank()` (or `ensure_initialized()`) raised, the `finally`
block crashed with `UnboundLocalError` instead of propagating the original
exception.
Fix: initialise both variables to safe defaults before the try/finally block.
"""
from datetime import datetime, timezone
from unittest.mock import AsyncMock, patch
import pytest
@pytest.mark.asyncio
async def test_recall_reranker_error_does_not_raise_unbound_local(memory, request_context):
"""Recall must propagate the reranker's exception, not an UnboundLocalError."""
bank_id = f"test_reranker_err_{datetime.now(timezone.utc).timestamp()}"
try:
await memory.retain_async(
bank_id=bank_id,
content="Paris is the capital of France",
request_context=request_context,
)
# Simulate a reranker failure (e.g. Cohere API error on empty/small candidate set)
rerank_mock = AsyncMock(side_effect=RuntimeError("reranker API error"))
memory._cross_encoder_reranker._initialized = True # skip ensure_initialized
with patch.object(memory._cross_encoder_reranker, "rerank", rerank_mock):
with pytest.raises(Exception, match="reranker API error"):
await memory.recall_async(
bank_id=bank_id,
query="capital of France",
request_context=request_context,
)
finally:
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_recall_reranker_init_error_does_not_raise_unbound_local(memory, request_context):
"""Same regression when ensure_initialized() raises (before pre_filtered_count is set)."""
bank_id = f"test_reranker_init_err_{datetime.now(timezone.utc).timestamp()}"
try:
await memory.retain_async(
bank_id=bank_id,
content="Paris is the capital of France",
request_context=request_context,
)
init_mock = AsyncMock(side_effect=RuntimeError("reranker init failed"))
memory._cross_encoder_reranker._initialized = False
with patch.object(memory._cross_encoder_reranker, "ensure_initialized", init_mock):
with pytest.raises(Exception, match="reranker init failed"):
await memory.recall_async(
bank_id=bank_id,
query="capital of France",
request_context=request_context,
)
finally:
await memory.delete_bank(bank_id, request_context=request_context)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "hindsight-cli"
version = "0.4.12"
version = "0.4.13"
edition = "2021"
authors = ["Hindsight Team"]
description = "A beautiful CLI for Hindsight - semantic memory system"
+1
View File
@@ -266,6 +266,7 @@ pub fn recall(
max_tokens: chunk_max_tokens,
}),
entities: None,
source_facts: None,
})
} else {
None
+21 -4
View File
@@ -7,7 +7,7 @@ info:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
title: Hindsight HTTP API
version: 0.4.12
version: 0.4.13
servers:
- url: /
paths:
@@ -3226,6 +3226,8 @@ components:
$ref: '#/components/schemas/EntityIncludeOptions'
chunks:
$ref: '#/components/schemas/ChunkIncludeOptions'
source_facts:
$ref: '#/components/schemas/SourceFactsIncludeOptions'
title: IncludeOptions
ListDocumentsResponse:
description: Response model for list documents endpoint.
@@ -3724,6 +3726,10 @@ components:
additionalProperties:
$ref: '#/components/schemas/ChunkData'
nullable: true
source_facts:
additionalProperties:
$ref: '#/components/schemas/RecallResult'
nullable: true
required:
- results
title: RecallResponse
@@ -3789,6 +3795,11 @@ components:
type: string
nullable: true
type: array
source_fact_ids:
items:
type: string
nullable: true
type: array
required:
- id
- text
@@ -4083,9 +4094,6 @@ components:
description: Request model for retain endpoint.
example:
async: false
document_tags:
- user_a
- user_b
items:
- content: Alice works at Google
context: work
@@ -4148,6 +4156,15 @@ components:
- items_count
- success
title: RetainResponse
SourceFactsIncludeOptions:
description: Options for including source facts for observation-type results.
properties:
max_tokens:
default: 4096
description: Maximum tokens for source facts
title: Max Tokens
type: integer
title: SourceFactsIncludeOptions
TagItem:
description: Single tag with usage count.
properties:
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+2 -2
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -41,7 +41,7 @@ var (
queryDescape = strings.NewReplacer( "%5B", "[", "%5D", "]" )
)
// APIClient manages communication with the Hindsight HTTP API API v0.4.12
// APIClient manages communication with the Hindsight HTTP API API v0.4.13
// In most cases there should be only one, shared, APIClient.
type APIClient struct {
cfg *Configuration
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+24
View File
@@ -1,7 +1,14 @@
package hindsight
import (
"net/http"
"time"
)
// NewAPIClientWithToken creates a new API client configured with a base URL and API token.
// The token is sent as a Bearer token in the Authorization header for all requests.
// Note: this uses http.DefaultClient which has no timeout. Use NewAPIClientWithTimeout
// to set a request timeout.
//
// Example:
//
@@ -15,3 +22,20 @@ func NewAPIClientWithToken(baseURL, token string) *APIClient {
cfg.AddDefaultHeader("Authorization", "Bearer "+token)
return NewAPIClient(cfg)
}
// NewAPIClientWithTimeout creates a new API client configured with a base URL, API token,
// and a request timeout. Use 0 for no timeout.
//
// Example:
//
// client := hindsight.NewAPIClientWithTimeout("https://api.example.com", "your-api-token", 30*time.Second)
// resp, _, err := client.MemoryAPI.RetainMemories(ctx, bankID).RetainRequest(req).Execute()
func NewAPIClientWithTimeout(baseURL, token string, timeout time.Duration) *APIClient {
cfg := NewConfiguration()
cfg.Servers = ServerConfigurations{
{URL: baseURL},
}
cfg.AddDefaultHeader("Authorization", "Bearer "+token)
cfg.HTTPClient = &http.Client{Timeout: timeout}
return NewAPIClient(cfg)
}
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+47 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -21,6 +21,7 @@ var _ MappedNullable = &IncludeOptions{}
type IncludeOptions struct {
Entities NullableEntityIncludeOptions `json:"entities,omitempty"`
Chunks NullableChunkIncludeOptions `json:"chunks,omitempty"`
SourceFacts NullableSourceFactsIncludeOptions `json:"source_facts,omitempty"`
}
// NewIncludeOptions instantiates a new IncludeOptions object
@@ -124,6 +125,48 @@ func (o *IncludeOptions) UnsetChunks() {
o.Chunks.Unset()
}
// GetSourceFacts returns the SourceFacts field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *IncludeOptions) GetSourceFacts() SourceFactsIncludeOptions {
if o == nil || IsNil(o.SourceFacts.Get()) {
var ret SourceFactsIncludeOptions
return ret
}
return *o.SourceFacts.Get()
}
// GetSourceFactsOk returns a tuple with the SourceFacts field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *IncludeOptions) GetSourceFactsOk() (*SourceFactsIncludeOptions, bool) {
if o == nil {
return nil, false
}
return o.SourceFacts.Get(), o.SourceFacts.IsSet()
}
// HasSourceFacts returns a boolean if a field has been set.
func (o *IncludeOptions) HasSourceFacts() bool {
if o != nil && o.SourceFacts.IsSet() {
return true
}
return false
}
// SetSourceFacts gets a reference to the given NullableSourceFactsIncludeOptions and assigns it to the SourceFacts field.
func (o *IncludeOptions) SetSourceFacts(v SourceFactsIncludeOptions) {
o.SourceFacts.Set(&v)
}
// SetSourceFactsNil sets the value for SourceFacts to be an explicit nil
func (o *IncludeOptions) SetSourceFactsNil() {
o.SourceFacts.Set(nil)
}
// UnsetSourceFacts ensures that no value is present for SourceFacts, not even an explicit nil
func (o *IncludeOptions) UnsetSourceFacts() {
o.SourceFacts.Unset()
}
func (o IncludeOptions) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -140,6 +183,9 @@ func (o IncludeOptions) ToMap() (map[string]interface{}, error) {
if o.Chunks.IsSet() {
toSerialize["chunks"] = o.Chunks.Get()
}
if o.SourceFacts.IsSet() {
toSerialize["source_facts"] = o.SourceFacts.Get()
}
return toSerialize, nil
}
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+38 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -25,6 +25,7 @@ type RecallResponse struct {
Trace map[string]interface{} `json:"trace,omitempty"`
Entities map[string]EntityStateResponse `json:"entities,omitempty"`
Chunks map[string]ChunkData `json:"chunks,omitempty"`
SourceFacts map[string]RecallResult `json:"source_facts,omitempty"`
}
type _RecallResponse RecallResponse
@@ -170,6 +171,39 @@ func (o *RecallResponse) SetChunks(v map[string]ChunkData) {
o.Chunks = v
}
// GetSourceFacts returns the SourceFacts field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *RecallResponse) GetSourceFacts() map[string]RecallResult {
if o == nil {
var ret map[string]RecallResult
return ret
}
return o.SourceFacts
}
// GetSourceFactsOk returns a tuple with the SourceFacts field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *RecallResponse) GetSourceFactsOk() (map[string]RecallResult, bool) {
if o == nil || IsNil(o.SourceFacts) {
return map[string]RecallResult{}, false
}
return o.SourceFacts, true
}
// HasSourceFacts returns a boolean if a field has been set.
func (o *RecallResponse) HasSourceFacts() bool {
if o != nil && !IsNil(o.SourceFacts) {
return true
}
return false
}
// SetSourceFacts gets a reference to the given map[string]RecallResult and assigns it to the SourceFacts field.
func (o *RecallResponse) SetSourceFacts(v map[string]RecallResult) {
o.SourceFacts = v
}
func (o RecallResponse) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -190,6 +224,9 @@ func (o RecallResponse) ToMap() (map[string]interface{}, error) {
if o.Chunks != nil {
toSerialize["chunks"] = o.Chunks
}
if o.SourceFacts != nil {
toSerialize["source_facts"] = o.SourceFacts
}
return toSerialize, nil
}
+38 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -33,6 +33,7 @@ type RecallResult struct {
Metadata map[string]string `json:"metadata,omitempty"`
ChunkId NullableString `json:"chunk_id,omitempty"`
Tags []string `json:"tags,omitempty"`
SourceFactIds []string `json:"source_fact_ids,omitempty"`
}
type _RecallResult RecallResult
@@ -497,6 +498,39 @@ func (o *RecallResult) SetTags(v []string) {
o.Tags = v
}
// GetSourceFactIds returns the SourceFactIds field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *RecallResult) GetSourceFactIds() []string {
if o == nil {
var ret []string
return ret
}
return o.SourceFactIds
}
// GetSourceFactIdsOk returns a tuple with the SourceFactIds field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *RecallResult) GetSourceFactIdsOk() ([]string, bool) {
if o == nil || IsNil(o.SourceFactIds) {
return nil, false
}
return o.SourceFactIds, true
}
// HasSourceFactIds returns a boolean if a field has been set.
func (o *RecallResult) HasSourceFactIds() bool {
if o != nil && !IsNil(o.SourceFactIds) {
return true
}
return false
}
// SetSourceFactIds gets a reference to the given []string and assigns it to the SourceFactIds field.
func (o *RecallResult) SetSourceFactIds(v []string) {
o.SourceFactIds = v
}
func (o RecallResult) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -539,6 +573,9 @@ func (o RecallResult) ToMap() (map[string]interface{}, error) {
if o.Tags != nil {
toSerialize["tags"] = o.Tags
}
if o.SourceFactIds != nil {
toSerialize["source_fact_ids"] = o.SourceFactIds
}
return toSerialize, nil
}
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+1 -1
View File
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.12
API version: 0.4.13
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

Some files were not shown because too many files have changed in this diff Show More