* fix(db): respect vector extension config in per-bank index migration
Migration d5e6f7a8b9c0 hardcoded HNSW when creating per-bank partial
vector indexes, ignoring HINDSIGHT_API_VECTOR_EXTENSION. This caused
banks migrated from pre-v0.4.18 to get HNSW indexes even when
pgvectorscale (DiskANN) or vchord was configured.
- Fix the original migration to read the vector extension config
- Add migration a4b5c6d7e8f9 to detect and recreate mismatched indexes
(skipped entirely when extension is pgvector, since those are correct)
* chore: regenerate openapi.json for v0.4.22 version bump
Add a new "Constellation" memory visualization as the default view in the
control plane, powered by @chenglou/pretext for DOM-free text layout on canvas.
- Canvas-rendered zoomable/pannable memory map with spatial label deconfliction
- Nodes colored by link-count heat gradient (Hindsight brand teal→cyan→blue)
- Star-like rendering with varied size/opacity based on connectivity
- Hover shows rich tooltip with full memory metadata (text, entities, tags, dates)
- Hover highlights connected nodes and their links, dims the rest
- Click to select and view memory details in the side panel
- Fullscreen mode toggle
- Link type legend and heat gradient legend on the HUD
Also optimizes the graph API endpoint:
- Entity query now filters by visible unit IDs (was doing full table scan)
- Links query caps at 10k edges sorted by weight (was returning 500k+ uncapped)
- Replaced expensive DISTINCT ON with LEAST/GREATEST sort with simple ORDER BY
* fix(deps): address critical and high severity security vulnerabilities
Bump vulnerable dependencies to patched versions across the monorepo:
Python (critical/high):
- fastmcp >=2.14.0 → >=3.2.0 (SSRF, path traversal, OAuth confused deputy, command injection)
- langchain-core >=1.2.11 → >=1.2.22 (path traversal in legacy load_prompt)
Python (low):
- cryptography >=46.0.5 → >=46.0.6 (incomplete DNS name constraint enforcement)
- pygments: add >=2.20.0 pin (ReDoS via GUID regex)
Node.js:
- serialize-javascript ^7.0.3 → ^7.0.5 (CPU exhaustion DoS)
- handlebars: add >=4.7.9 override (JS injection via AST type confusion)
- path-to-regexp: add >=0.1.13 override (ReDoS via route params)
- brace-expansion: add version range override (process hang/memory exhaustion)
Also adds type: ignore comments for FastMCP 2.x private attribute access that
ty now flags since FastMCP 3.x removed _tool_manager (guarded by try/except
and hasattr at runtime).
Regenerated all lock files across API, integrations, and tests.
* fix(deps): add ajv v8 scoped overrides for schema-utils and ajv-keywords
The global ajv ^6.14.0 override caused schema-utils and ajv-keywords to
receive ajv v6, but they require ajv v8 (for dist/compile/codegen). Add
scoped overrides to ensure these packages get ajv v8 while the global
override remains for packages that need v6.
* fix(tests): remove stateless_http from FastMCP() constructor calls
FastMCP 3.x no longer accepts stateless_http in the constructor. The
tests call tools directly without HTTP transport, so the parameter is
not needed.
* fix: update MCP tests for FastMCP 3.x _tool_manager removal
FastMCP 3.x removed _tool_manager. Tests now use
_local_provider._components for sync tool dict access and
mcp.list_tools() for async filtered tool listing.
* fix: resolve docusaurus build failures (ajv overrides + missing blog date)
- Remove global ajv ^6.14.0 override and scoped ajv-keywords/schema-utils
overrides that caused webpack compilation errors manifesting as
"Cannot read properties of undefined (reading 'date')" during SSR
and "these parameters are deprecated" warnings. Natural version
resolution (v6.12.6+ for v6 consumers, v8+ for v8 consumers) already
satisfies the security fix (>= 6.12.3).
- Add missing date frontmatter to learning-capabilities blog post.
* chore: regenerate openapi spec and docs skill
When a mental model has tags, refresh_mental_model hardcoded
tags_match="all_strict", causing empty results when most memories
are untagged. Add configurable tags_match and tag_groups fields
to MentalModelTrigger so users can control refresh filtering.
- Add tags_match (any/all/any_strict/all_strict) to override default
- Add tag_groups for compound boolean tag expressions during refresh
- Default behavior unchanged (all_strict when tags present)
- Update both refresh paths (task-based and direct)
- Add UI controls in Create/Update mental model dialogs
- Regenerate OpenAPI spec and client SDKs
CI failures are unrelated to this PR:
- test_mental_models_dimension_change_empty_table: database OID error (infrastructure flake)
- test_reflect_searches_mental_models_when_available: LLM-dependent assertion (flaky)
When using Azure AI Foundry Cohere rerank endpoints, the Cohere SDK
incorrectly appends /v1/rerank to the base_url, but Azure endpoints
already include the full path (e.g., /models/.../invoke). This causes
double-pathing and 404 errors.
This commit modifies CohereCrossEncoder to detect when base_url is
provided and use httpx directly for custom endpoints, while keeping
the native Cohere SDK for standard API usage. The Azure Cohere API
response format is compatible with the native format.
Fixes#783
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Enable passing arbitrary extra_body parameters to OpenAI-compatible API
calls via a JSON-encoded env var. This supports custom model servers
(e.g. vLLM) that need parameters like chat_template_kwargs to control
thinking mode.
Co-authored-by: EMIRHAN GAZI <emirhan.gazi+tcell@turkcell.com.tr>
* feat: add optional LiteLLM SDK embedding output dimensions
Allow configuring an optional output dimension for litellm-sdk embeddings and pass it through only when set, while preserving default behavior.
Made-with: Cursor
* test: assert wrapped init error for invalid dimensions
Add a LiteLLM SDK embeddings test that verifies invalid OpenAI dimensions fail during initialize() and preserve provider error details in the wrapped RuntimeError.
Made-with: Cursor
* feat: expose document_metadata in API and control plane
Add document_metadata (sourced from retain_params.metadata) to both
list and get document endpoints. Display it in the control plane
documents table and detail panel. Drop the unused metadata column
from the documents table (was always stored as empty {}).
* fix: code review fixes for document_metadata feature
- Remove unnecessary `import json as _json` (json already imported at module level)
- Simplify redundant truthiness checks in retain_params parsing
- Regenerate OpenAPI spec and client SDKs (Python, TypeScript, Go)
- Add tests for document_metadata in get_document and list_documents
* feat(ui): improve documents table and detail panel
- Relative timestamps with full date on hover
- Remove context column from table
- Metadata shown as k=v badges (blue, like tags)
- Size in bytes instead of chars
- Document IDs wrap instead of truncating
- Detail panel wider (560px)
- Retain params: context, event_date, metadata badges
Add middleware that detects unknown query params and JSON body fields,
logs a server-side warning, and returns an X-Ignored-Params response
header listing the ignored parameters. This surfaces silent parameter
ignoring (e.g. tag=source:slack on /memories/list) without breaking
forward compatibility between client and server versions.
Closes#792
* fix(engine): classify first-person agent experiences as 'experience' fact type
The extraction prompt defined "assistant" too narrowly as only "interactions
with assistant (requests, recommendations)", causing the LLM to classify
first-person agent actions (code changes, debugging, discoveries) as "world".
Broadened the fact_type definition in the prompt and Pydantic model descriptions
to cover all first-person actions, experiences, and observations by the speaker.
* style: fix line length in fact_extraction.py
Document the new configurable base URL for the ZeroEntropy reranker
provider added in #766. Also fix a type error where RERANK_URL was
renamed to rerank_url but one usage was missed.
* feat(api): add duration_ms to audit log entries
Server-computed duration in milliseconds (started_at → ended_at) on
the list audit logs endpoint. Null when ended_at is not set.
Closes#749
* feat(api): add duration_ms to audit log entries and type audit endpoints
- Add server-computed duration_ms (started_at → ended_at) to audit log
list response. Null when ended_at is not set.
- Add typed Pydantic response models for both audit log endpoints
(list and stats) so they appear in the OpenAPI spec.
- Regenerate OpenAPI spec and all client SDKs.
Closes#749
* chore: regenerate docs skill after audit log response models
* fix(mcp): handle Claude Code GET probe and make stateless_http configurable (#751)
Claude Code v2.1.84+ sends a GET to /mcp/ before POST initialize,
which fails with 405 (stateless) or 400 (stateful). Intercept
sessionless GET requests in MCPMiddleware and return 200 OK so the
client proceeds to POST initialize.
Also make stateless_http configurable via HINDSIGHT_API_MCP_STATELESS
(default: false/stateful) instead of hardcoding true.
Closes#751
* docs: add HINDSIGHT_API_MCP_STATELESS to configuration reference
create_bank_hnsw_indexes() hardcoded USING hnsw regardless of the configured
vector extension, causing "column cannot have more than 2000 dimensions for
hnsw index" when using pgvectorscale or vchord with high-dimensional embeddings.
Now reads get_config().vector_extension and uses the appropriate index type:
- pgvector → USING hnsw
- pgvectorscale → USING diskann
- vchord → USING vchordrq
Closes#738
Verbose mode was the only extraction mode that skipped injecting the
retain_mission FOCUS section into its prompt template. Users who set a
retain_mission got no filtering when using verbose mode.
A 429 usage_limit_reached response during verify_connection() caused the
server to refuse to start entirely. Quota exhaustion is not a configuration
error — the server should start and serve retain/recall requests normally,
it just can't make LLM calls until the quota resets.
Co-authored-by: Marco Rutsch <marco@rutimka.de>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add optional filter_mcp_tools() method to OperationValidatorExtension.
Called during tools/list after bank-level mcp_enabled_tools filtering.
Extensions can override to hide MCP tools per-user-per-bank based on
access policies. Default returns all tools unchanged.
- Add filter_mcp_tools to OperationValidatorExtension with default pass-through
- Wire into _get_enabled_tools in _apply_bank_tool_filtering
- Move _ALL_TOOLS to mcp_tools.py to avoid circular import (re-exported from mcp.py)
- Fail-open: if filter raises, log warning and return unfiltered tools
- Enforce ceiling: validator can narrow but never expand beyond bank config
- Add 8 tests: default, filtering, empty set, integration, composition,
can't-add-tools, exception fail-open, no-validator passthrough
* fix: parse query params from base_url in OpenAI embeddings client
The OpenAI-compatible LLM provider already parses query parameters
(e.g. ?api-version=xxx for Azure OpenAI) from the base_url and passes
them as default_query to the OpenAI client. However, the OpenAI
embeddings provider did not do this, causing Azure OpenAI embeddings
to fail with 404 errors at runtime.
This applies the same URL parsing logic from the LLM provider to the
embeddings provider, enabling Azure OpenAI embeddings to work correctly.
* ci: add workflow to build fork Docker image
* ci: add slim image build (no local models)
* ci: remove fork build workflow per review request
---------
Co-authored-by: Antoine Khater <ak@ptgroup.eu>
* fix(claude-code): implement tool_choice support for forced tool calls
The call_with_tools() method now properly handles the tool_choice parameter
to force specific tool calls. Previously, the parameter was accepted but ignored,
causing the reflect agent to fail when trying to force specific tools on each
iteration.
Fixes#732
Changes:
- When tool_choice forces a specific function: filter allowed_tools to only
that tool (with mcp prefix) and add a strong system prompt instruction
- When tool_choice is 'required': add instruction that model must call at
least one tool
- When tool_choice is 'none': clear allowed_tools and mcp_servers to disable
all tools
- When tool_choice is 'auto' (default): no change (existing behavior)
This matches the approach used in the OpenAI provider while adapting to the
Claude Agent SDK's lack of native tool_choice parameter by using allowed_tools
filtering and system prompt instructions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: fix ruff formatting in alembic migration
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add max_observations_per_scope bank config
Adds a configurable limit on the number of observations per tag scope.
When the limit is reached, consolidation only updates/deletes existing
observations — no new ones are created. Enforcement is done via a
constrained Pydantic response model (max_length on creates list) so the
LLM structurally cannot exceed the limit, plus prompt guidance.
- Config: HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE (-1 = unlimited)
- Reorder action execution: deletes → updates → creates
- Dynamic _ConsolidationBatchResponse with max_length constraint
- Prompt CAPACITY CONSTRAINT section when near/at limit
- Observations with no tags skip the limit entirely
- Control plane UI field + docs
* fix: strengthen max_observations tests with mock LLM + defensive truncation
- Rewrite integration tests to use MockLLM with deterministic responses
(one observation per fact) instead of relying on real LLM behavior
- Add defensive truncation in _consolidate_batch_with_llm as belt-and-
suspenders — catches LLM providers that ignore JSON schema max_length
- Tests now assert exact counts, not just upper bounds
The stats endpoint JOINs memory_links to memory_units just to filter
by bank_id. With 8.2M+ links per bank this takes 18+ seconds, and
the control plane polls every 10s — perpetually blocking the server.
Add bank_id column directly to memory_links so the query can filter
on ml.bank_id instead of mu.bank_id, letting Postgres push the filter
down before the JOIN.
- Migration: add bank_id TEXT NOT NULL, backfill from memory_units
- All 4 INSERT paths (temporal, semantic, entity, causal) now write bank_id
- Stats query filters on ml.bank_id instead of mu.bank_id
* fix(migrations): use HINDSIGHT_API_MIGRATION_DATABASE_URL when set
Session-level advisory locks are broken when the database URL goes
through PgBouncer in transaction mode: the backend connection is
returned to the pool on COMMIT, orphaning the lock, so multiple pods
can simultaneously run migrations for the same schema.
When HINDSIGHT_API_MIGRATION_DATABASE_URL is set, use it for both
the advisory lock connection and the Alembic run. Callers should
point this at the direct PostgreSQL endpoint (bypassing the pooler)
so the session-level lock is held for the full migration duration.
* refactor(migrations): move MIGRATION_DATABASE_URL to standard config
Wire HINDSIGHT_API_MIGRATION_DATABASE_URL through HindsightConfig
instead of reading os.getenv() directly in migrations.py. Add the
field to the dataclass, from_env(), log_config(), all call sites,
.env.example, and the configuration docs page.
* fix: update test mocks for migration_database_url kwarg and regenerate docs skill
---------
Co-authored-by: Nicolò Boschi <boschi1997@gmail.com>
* fix: silence noisy google_genai.models INFO logging
The google-genai SDK logs "AFC is enabled with max remote calls: 10"
at INFO level on every initialization. Set its logger to WARNING.
* fix: regenerate docs skill in release-integration script
The release script generates changelog/SDK pages but never re-ran
generate-docs-skill.sh, causing CI to fail with out-of-sync skill
files after every integration release. Now it regenerates the skill
and includes the output in the release commit.
Also adds the missing ag2 skill files from the latest release.
* fix(migration): use IF EXISTS when dropping chunk FK constraint
The migration unconditionally dropped memory_units_chunk_fkey, but
depending on the order in which migrations were applied the constraint
may not exist. Use raw SQL with IF EXISTS so the drop is safe regardless.
* fix(migration): make chunk FK add idempotent with DO block
The previous fix only handled the DROP side with IF EXISTS. The ADD side
could still fail with DuplicateObject when the FK already existed on a
schema that was provisioned after the base migration ran.
Wrap the ADD CONSTRAINT in a DO block to catch duplicate_object and
continue, making the migration fully idempotent in both directions.
Port fixes from #461 (claude_code_llm) to codex_llm:
- Replace json.dumps(result) with result.model_dump_json() for Pydantic models to fix TypeError during consolidation
- Wrap record_llm_call tracing block in try/except so logging failures never propagate to retry handler
Co-authored-by: Marco Rutsch <marco@rutimka.de>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add audit log for feature usage tracking
Add full auditability for all mutating and core API operations across
HTTP, MCP, and system (worker) transports. Audit entries record raw
request/response as JSONB, timing (started_at/ended_at), action, and
transport type.
Backend:
- New audit_log table with JSONB columns for expandability without
future migrations (merge migration of 3 existing heads)
- AuditLogger with fire-and-forget writes via asyncio.create_task
- @audited decorator on 28 HTTP route handlers
- MCP tool audit wrapping for 16 auditable tools
- Worker task execution wrapped with audit_context
- List endpoint with action, transport, date range filters + pagination
- Stats endpoint with per-day counts for charting
- Configurable retention sweep (concurrent-safe DELETE)
Config (env-only, static):
- HINDSIGHT_API_AUDIT_LOG_ENABLED (default: false)
- HINDSIGHT_API_AUDIT_LOG_ACTIONS (comma-separated allowlist, empty=all)
- HINDSIGHT_API_AUDIT_LOG_RETENTION_DAYS (default: -1, keep forever)
Control Plane:
- New "Audit Logs" tab on bank configuration page
- Line chart showing request volume (today/7d/30d) with action filter
- Filterable table with action, transport, date range filters
- Paginated list with detail dialog showing raw request/response JSON
Tests:
- 13 tests covering list, filters, pagination, stats, disabled mode,
action allowlist, and ordering
* fix: split 3-way merge migration into two 2-way merges
Alembic doesn't support 3-parent merge migrations. Split into a no-op
merge of 2 heads (b1c2d3e4f5g6) followed by the audit_log table
migration merging the third head.
* fix: correct merge migration to merge actual 2 heads
The original analysis incorrectly identified 3 heads. There were only 2
(a3b4c5d6e7f8 and c8e5f2a3b4d1). Remove the unnecessary intermediate
merge migration and fix the audit_log migration to merge these 2 heads.
* fix: use 'heads' instead of 'head' in migration runner
Alembic's upgrade('head') fails when multiple heads exist (e.g. from
namespace package overlaps between hindsight-api and hindsight-api-slim).
Using 'heads' (plural) handles this gracefully by upgrading all branches.
* chore: regenerate OpenAPI spec with audit log endpoints
* chore: regenerate TypeScript client and docs skill OpenAPI spec
Python and Go clients still need regeneration (requires Docker).
* chore: regenerate all client SDKs (Python, Go, TypeScript)
Adds generated audit log API clients for Python (audit_api.py),
Go (api_audit.go), and TypeScript client type updates.
- Add 'ark' and 'volcano' as valid LLM providers (both are aliases for Volcano Engine)
- Set default model to 'doubao-pro-32k' for both providers
- Add them to OpenAICompatibleLLM provider list
- Exclude from json_object response format support
Co-authored-by: yishun.eason <yishun.eason@bytedance.com>
* docs(python-client): improve pydoc strings for async-first usage and low-level API access
- Class docstring now clearly documents async-first pattern: a* methods
preferred, sync wrappers for scripts/REPLs only
- Every sync method docstring points to its async counterpart
- Every async method docstring says "preferred"
- Expose 10 low-level API properties (documents, entities, operations,
webhooks, monitoring, etc.) so agents/users can discover the full API
surface without guessing at _-prefixed internals
- Add missing API parameters: tag_groups (recall/reflect), fact_types,
exclude_mental_models, exclude_mental_model_ids (reflect),
observation_scopes/strategy (retain items), background (create_bank)
- Fix areflect missing include_facts param that sync reflect already had
- Sync recall/reflect now delegate to async counterparts (no logic duplication)
* style(retain): format long function call arguments one-per-line
* feat(retain): delta retain — skip LLM re-extraction for unchanged chunks on upsert
When upserting a document (same document_id), instead of deleting all
facts and re-extracting from scratch, compare chunk content hashes
and only process changed/new chunks. Unchanged chunks keep their
existing facts, entities, and links.
- Add content_hash column to chunks table (migration b3c4d5e6f7a8)
- Add chunk delta comparison functions in chunk_storage.py
- Add delta_mode to fact_storage.handle_document_tracking (skip full delete)
- Add update_memory_units_tags for propagating tag changes to existing facts
- Refactor orchestrator into _try_delta_retain and _full_retain paths
- Automatic fallback to full retain for pre-migration data or all-changed scenarios
- Fix ty type error in metrics.py (resource module import on Windows)
- 16 new tests covering entities, links, tags, metadata, edge cases
* refactor(retain): deduplicate delta and full retain paths
Extract shared _insert_facts_and_links() and _extract_and_embed()
functions used by both the full retain and delta retain paths.
Remove delta_mode flag from handle_document_tracking — delta path
uses dedicated upsert_document_metadata() instead.
* chore: regenerate clients, openapi spec, and lockfile
* chore: regenerate docs skill
* docs: add supported platforms section and Windows installation guide
Adds a platform compatibility table (Linux, macOS, Windows) and a
dedicated Windows setup section with step-by-step instructions for
installing PostgreSQL + pgvector and running Hindsight natively.
Follows up on #699 which added Windows native support.
Also fixes a ty type-check error in metrics.py for the conditional
resource module import.
* chore: sync generated clients and lock file after #699
Regenerate client SDKs to pick up ValidationError model changes
and update uv.lock with platform-specific uvloop/winloop deps.
* docs: update Windows section — pg0 now supports Windows
pg0 v0.12.0 added Windows support, so embedded DB works everywhere.
Restructure Windows section to show simple install-and-run first,
with external PostgreSQL as an optional alternative.
* chore: sync generated docs skill and openapi references
FastAPI generates the ValidationError schema with only loc, msg, and
type, but Pydantic v2 actually returns input, ctx, and url as well.
Generated clients with strict JSON decoding (Go's DisallowUnknownFields)
cannot parse real 422 responses — the actual validation message gets
replaced by a confusing JSON decoding error.
- Patch the OpenAPI schema in create_app() to add input, ctx, url
- Regenerate spec and Go client
* feat: Windows native support — run Hindsight without Docker on Windows
Four compatibility fixes that allow Hindsight to run natively on Windows
with an external PostgreSQL + pgvector installation:
1. **pyproject.toml**: Conditional event loop dependency
- `winloop` on Windows (sys_platform == 'win32')
- `uvloop` on Linux/macOS (sys_platform != 'win32')
2. **main.py**: winloop integration via `winloop.install()`
- Patches asyncio event loop policy globally before uvicorn starts
- uvicorn sees "asyncio" but runs winloop underneath (same perf as uvloop)
- Falls back to default asyncio if winloop unavailable
3. **metrics.py**: Guard `resource` module import
- `resource` is Unix-only (getrusage, getrlimit)
- Conditional import with None fallback
- Skip process metrics collection on Windows
4. **fact_storage.py**: Cross-platform strftime
- `%-d` (no-padding day) is glibc-only, fails on Windows
- Replaced with `%d` + `.replace(" 0", " ")` for same output
## Windows Setup Guide
### Prerequisites
- Python 3.11+
- PostgreSQL 17 with pgvector extension
- Ollama (for local embeddings) or external embedding provider
### Install PostgreSQL + pgvector on Windows
```bash
winget install PostgreSQL.PostgreSQL.17
# Build pgvector from source (requires Visual Studio Build Tools)
git clone https://github.com/pgvector/pgvector.git
# In x64 Native Tools Command Prompt:
set PGROOT=C:\Program Files\PostgreSQL\17
nmake /F Makefile.win
nmake /F Makefile.win install
# Enable extension
psql -U postgres -d hindsight -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
### Install and Run Hindsight
```bash
pip install -e ".[embedded-db]"
# Set environment variables
set HINDSIGHT_API_LLM_PROVIDER=openai
set HINDSIGHT_API_LLM_API_KEY=your-api-key
set HINDSIGHT_API_LLM_BASE_URL=https://your-llm-endpoint/v1
set HINDSIGHT_API_LLM_MODEL=your-model
set HINDSIGHT_API_DATABASE_URL=postgresql://postgres@localhost:5432/hindsight
set HINDSIGHT_API_EMBEDDING_PROVIDER=ollama
set HINDSIGHT_API_PORT=8889
hindsight-api
```
Data persists in PostgreSQL on your local disk — survives reboots,
updates, and anything that would wipe a Docker volume.
Tested on Windows 11 with PostgreSQL 17.9, pgvector 0.8.2,
Python 3.11, RTX 5080 (CUDA embeddings + reranking).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle strftime ValueError on Windows in fact_storage
The strftime call on occurred_start/occurred_end can raise ValueError
on Windows when the datetime object has unexpected format properties.
Wrap in try/except to gracefully skip date signal rather than crash
the entire retain batch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: control plane UI fixes for recall and data view
- Sanitize NaN cross-encoder scores to 0.0 in reranking pipeline
(Pydantic serializes NaN as JSON null, breaking UI score display)
- Add null-coalesce for score in search debug view to prevent crash
- Switch data view text filter from debounced onChange to Enter key
(avoids slow ILIKE queries on every keystroke for large banks)
- Show loading spinner in search icon during filter requests
- Preserve search/tag filters when clicking "Load more"
* chore: sync generated files after rebase
Adds a proper 'none' provider option so users can run Hindsight as a
chunk store with semantic search but without any LLM dependency, replacing
the hacky workaround of setting provider to 'mock'.
When HINDSIGHT_API_LLM_PROVIDER=none:
- Retain automatically uses chunks mode (no fact extraction)
- Recall works normally (semantic search, BM25, graph retrieval)
- Reflect returns HTTP 400 with clear error message
- Consolidation/observations are disabled
- Mental model refresh returns HTTP 400
- No API key required
* feat(reflect): make source facts in search_observations configurable
The recent fix (#669) hardcoded include_source_facts=False in
search_observations to prevent context overflow. This makes it
configurable via HINDSIGHT_API_REFLECT_SOURCE_FACTS_MAX_TOKENS
(env/tenant/bank), defaulting to -1 (disabled).
- -1: source facts disabled (current behavior, default)
- 0: source facts enabled with no token limit
- >0: source facts enabled with a token budget
* docs: add reflect_source_facts_max_tokens to configuration reference
* fix: update configurable fields count in tests and regenerate docs skill
* feat: add LiteLLM LLM provider for Bedrock and 100+ providers
Add a new `litellm` LLM provider that uses the LiteLLM SDK for chat
completions and tool calling, enabling AWS Bedrock and 100+ other
providers for Hindsight's core engine (retain, recall, reflect).
- New LiteLLMLLM provider in engine/providers/litellm_llm.py
- Registered in factory, valid providers list, and no-api-key set
- Refactored API key validation to use requires_api_key() helper
- Added boto3 dependency for Bedrock auth
- Updated docs: configuration, models, monitoring, providers grid
* feat: add bedrock as first-class LLM provider alias
Add `bedrock` as a dedicated provider name that auto-prepends the
`bedrock/` prefix to model names and delegates to LiteLLMLLM under
the hood. This makes Bedrock support more discoverable — users set
`HINDSIGHT_API_LLM_PROVIDER=bedrock` with plain Bedrock model IDs.
* test: add Bedrock to CI provider tests
- Add bedrock/us.amazon.nova-lite-v1:0 to MODEL_MATRIX in test_llm_provider.py
- Add AWS credential check in should_skip_provider()
- Pass AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION_NAME secrets to test-api job
- Update default bedrock model to amazon.nova-2-lite-v1:0
* fix: regenerate docs skill files and bump memory test timeout
- Regenerate skills/hindsight-docs references after docs changes
- Bump test_llm_provider_memory_operations timeout to 600s for slower
providers like Bedrock via LiteLLM
* test: skip bedrock lite models in memory operations test
Nova Lite has a 10K output token limit which is too low for fact
extraction (requires 64K). The api_methods test (completion, tools,
structured output) already validates the provider works correctly.
* test: use Nova Pro for bedrock CI tests to cover full memory pipeline
Nova Lite only supports 10K output tokens, too low for fact extraction.
Switch to Nova Pro which supports the full 64K output needed for
retain/reflect operations. This ensures bedrock is tested on all
Hindsight functionalities, not just basic API methods.
* test: switch bedrock CI to Nova 2 Lite (supports 64K output tokens)
Nova v1 models (Pro, Lite) have a 10K output token limit which is
too low for fact extraction. Nova 2 Lite supports 64K+ output tokens,
enabling full memory pipeline testing (retain + reflect).
MCP tool bridges sometimes serialize JSON arrays as strings during
transport, e.g. '["a", "b"]' arrives as the literal string '["a", "b"]'
instead of a native JSON array. This causes Pydantic to reject the
input with a validation error.
Add defensive coercion at two layers:
1. HTTP API (http.py): Pydantic field_validator on MemoryItem.tags
with mode="before" that parses JSON strings back into lists.
2. MCP tools (mcp_tools.py): Same coercion in build_content_dict
before tags reach the Pydantic model.
A plain non-JSON string is wrapped in a single-element list.
Correctly-formatted input is passed through unchanged.
Co-authored-by: Philipp <philipp@Philipps-MacBook-Pro.local>
Expose the named retain strategy on the MCP retain tool, matching the
HTTP API's per-item strategy support. This allows MCP clients (Claude
Code, Claude Desktop, etc.) to specify extraction behavior per memory:
strategy: "exact" → verbatim storage, no LLM processing
strategy: "verbose" → detailed extraction
strategy: "concise" → default compressed extraction
Strategies are defined in bank config under retain_strategies.
Unknown strategy names are logged and ignored (bank default applies).
Changes:
- Add strategy param to both retain function signatures (with/without bank_id)
- Add strategy to build_content_dict
- Strategy is set in the content dict, which the engine already handles per-item
Co-authored-by: Philipp <philipp@Philipps-MacBook-Pro.local>
* fix: return metadata in recall responses (#674)
Metadata stored during retain was never retrieved during recall.
Add metadata to all SQL SELECT queries, the RetrievalResult dataclass,
ScoredResult.to_dict(), and MemoryFact construction in the recall pipeline.
* test: add metadata round-trip test for retain→recall
Replace placeholder metadata test with one that actually passes
metadata via retain_batch_async and asserts it is returned on recall.
* fix: parse metadata JSON string from database in MemoryFact
asyncpg may return JSONB columns as strings. Add a field_validator
to MemoryFact.metadata to handle JSON string deserialization.
* feat(openclaw): remove hardcoded default models, rely on Hindsight API defaults
* feat(claude-code): remove hardcoded default models, rely on Hindsight API defaults
* feat(claude-code,docs): remove hardcoded default models from claude-code integration and docs
* feat: use claude-haiku-4-5 as default Anthropic model
search_observations in the reflect agent hardcoded include_source_facts=True
with max_source_facts_tokens=-1 (unlimited). For banks with many observations
backed by thousands of facts, a single tool call could produce 300K+ tokens,
exceeding the default 100K context budget and causing forced synthesis with
an empty 'Retrieved Data' section.
The reflect agent synthesizes from observations, not raw backing facts.
Disable source facts to keep payloads proportional to observation count
(~6K vs ~310K in the reporter's case).
The consolidation path already has configurable source fact limits (PR #509,
v0.4.17). The reflect path was not updated.
Fixes#668
Co-authored-by: Kagura Chen <daniyuu19@sjtu.edu.cn>