* feat: add update_mode='append' for retain to concatenate content to existing documents
When retaining with update_mode='append' and a document_id that already exists,
the new content is appended to the existing document text and the full document
is reprocessed. Delta retain automatically skips unchanged chunks, so only the
new content triggers LLM extraction.
- Add update_mode field to MemoryItem (API), RetainContentDict (internal), MCP tools
- Validate that update_mode='append' requires a document_id
- Fetch existing document content and prepend before processing in orchestrator
- Update Python, TypeScript, Go generated clients and top-level client wrappers
- Add tests for append, multiple appends, no-existing-doc, validation, and default replace
* fix: add update_mode field to Rust CLI and client MemoryItem initializers
* chore: regenerate docs skill references for update_mode
* test: add regression tests for #874 and #894
Add tests for None event_date in fact extraction (AttributeError fix)
and for _register_profile skipping .env overwrite with short config keys.
* fix(config): validate entity_labels structure on PATCH (#891)
Config PATCH accepted bare strings in entity_labels values without
validation, causing silent failures at retain time. Now validates
via parse_entity_labels() before writing to DB, and fixes the
BankTemplateConfig type from list[str] to list[dict[str, Any]].
* fix(scripts): handle Python client generator README crash gracefully
The openapi-generator sometimes crashes writing README_onlypackage.mustache.
Allow the failure with || true since all API/model files are generated
before that step, and add a verification check for api_client.py.
* chore: regenerate docs skill openapi.json
* feat(api): add bank template import/export endpoints
Add POST /banks/{bank_id}/import and GET /banks/{bank_id}/export
endpoints for declarative bank setup via JSON manifests.
A template manifest (version 1) can include bank config overrides
and mental model definitions. Import creates or updates mental
models matched by id, applies config as per-bank overrides, and
returns async operation IDs for content generation.
Export dumps a bank's explicit overrides and mental models as a
manifest that can be re-imported into another bank.
Includes control plane UI: bank creation dialog now accepts an
optional template JSON to pre-configure the bank on creation.
* docs: add Template Gallery page and bank templates reference
- Template Gallery (/templates) with search, category filter, manifest
preview modal with copy-to-clipboard
- 5 starter templates: Customer Support, Research Assistant, Personal
Journal, Code Review Buddy, Meeting Notes
- Bank Templates API reference doc (developer/api/bank-templates)
- Sidebar entry under API section
* docs: add Template Gallery links to navbar and sidebar
- Top navbar: "Templates" link between Integrations and Changelog
- Sidebar: "Template Gallery" in Resources section
* fix(docs): remove emoji icons, autofocus search, fix placeholder in template gallery
* docs: rename to Bank Templates, move to Resources sidebar only
* docs: add Bank Templates to Resources navbar dropdown
* feat(api): add directives to bank template import/export
- Add BankTemplateDirective model with name, content, priority, is_active, tags
- Import creates/updates directives matched by name
- Export includes all directives (active and inactive)
- Validation: duplicate names rejected, empty name/content caught
- Tests: 24 tests covering directives create/update, existing vs new
bank import, validation, export with directives, full round-trip
* docs: add directives to bank templates docs and sample templates
* feat(api): add JSON Schema endpoint for bank template validation
- GET /v1/default/bank-template-schema returns the JSON Schema
auto-generated from the Pydantic BankTemplateManifest model
- Static schema file at docs/static/bank-template-schema.json
- Docs updated with schema endpoint, static file link, and
validation examples (Python jsonschema, Node ajv-cli)
* feat(api): live schema validation on import, fix schema endpoint path
- Move schema endpoint to /v1/bank-template-schema (system-level, not per-bank)
- Import endpoint now accepts raw JSON and validates with Pydantic manually,
returning clean 400 errors instead of raw 422s for all validation failures
- All validation (schema + semantic) returns consistent 400 with detailed messages
* docs: add interactive JSON Schema viewer to Bank Templates page
Renders the Pydantic-generated schema as a collapsible property tree
with types, required badges, defaults, and descriptions. The schema
is imported from the static bank-template-schema.json file.
* ui: add template toggle switch and browse link to bank creation dialog
- Replace always-visible textarea with a switch toggle ("Import from template")
- Textarea only shows when switch is on, keeping the dialog clean by default
- Add "Browse templates" link pointing to hindsight.vectorize.io/templates
- Reset template state when switch is toggled off or dialog is cancelled
* ui: add empty state with Add Document CTA to data view
When a bank has 0 memories, the data view (all tabs: constellation,
graph, table, timeline) shows a centered empty state with a CTA
button that opens the Add Document dialog.
* docs: replace templates with Conversation and Coding Agent
Remove generic placeholder templates. Add two practical templates
based on actual integration patterns:
- Conversation: for chat agents (LiteLLM, LangGraph, Pydantic AI,
Vercel AI SDK). Tracks user preferences, open threads.
- Coding Agent: for Claude Code/Codex. Tracks technical decisions,
project context, developer preferences. High literalism.
* docs: rename gallery to Bank Templates Hub, keep API doc as Bank Templates
* docs: register layout-template and file-json icons in navbar and sidebar
* docs: register layout-template icon in DefaultNavbarItem for dropdown items
* docs: show integration icons on template cards
Templates now have an optional `integrations` field referencing
integration IDs from integrations.json. Icons are resolved at render
time and shown in the card header next to the category badge.
* docs: add Personal Assistant template for OpenClaw, Hermes, NemoClaw
* feat: add Export Template to bank actions + map all integrations to templates
- Add "Export Template" to the bank Actions dropdown — exports config,
mental models, and directives as JSON, copies to clipboard
- Add export API route and client method
- Map remaining integrations to templates: CrewAI, AG2, Agno, Strands,
LlamaIndex, local-mcp, skills → Conversation; hindclaw → Personal Assistant
* feat: add --template flag to LoCoMo benchmark + remove schema from Hub
- LoCoMo benchmark accepts --template <path> to apply a bank template
manifest (config, mental models, directives) before ingestion
- Template is applied per-bank in both single-phase and two-phase modes
- BenchmarkRunner.apply_template() reuses the same engine methods as
the /import API endpoint
- Remove Manifest Schema section from Bank Templates Hub page
(schema stays in the API reference doc)
* refactor: remove description field from bank template manifest
* docs: remove tags, fact_types, and directives from starter templates
* docs: remove reflect_mission and disposition fields from starter templates
* build: validate template manifests against JSON Schema during docs build
* cleanup: remove unused JsonSchemaViewer component
* docs: remove retain_extraction_mode from starter templates
* ui: enable word wrap in template manifest preview
* docs: add link to Bank Templates reference doc from Hub page
* docs: convert bank templates doc to mdx with multi-language code snippets
- Convert bank-templates.md to .mdx with Tabs/CodeSnippet components
- Add example files: bank-templates.py, .mjs, .sh, .go with doc markers
- Examples cover import, dry-run, export, round-trip, and schema
- Regenerate OpenAPI spec and all client SDKs (Python, TS, Rust, Go)
* fix: migration revision collision + use typed models in benchmark template
- Rename merge migration d6e7f8a9b0c1 -> d6e7f8a9b0c2 to resolve
revision ID collision with case_insensitive_entities_trgm_index
- Update a4b5c6d7e8f9 down_revision to point to the renamed migration
- Fix f-string lint in case_insensitive migration
- BenchmarkRunner.apply_template() now validates manifest through
BankTemplateManifest Pydantic model instead of raw dict access
- Remove redundant inline imports (json, Path already at module top)
* fix(docs): add missing Go tab to dry-run code snippet
* ci: retrigger
* fix: sync skills openapi.json + fix bankId null type error in export
- Copy updated openapi.json to skills/hindsight-docs/references/
- Add null guard for bankId in Export Template onClick handler
* fix: sync generated files (memory_engine formatting, docs skill references)
* cleanup: remove obsolete migration collision workaround
* feat: add detail parameter to list/get mental models (#825)
Add a `detail` query parameter (metadata|content|full) to both list and get
mental model endpoints (HTTP + MCP) to control response size. This reduces
payload for agent boot flows and MCP clients where context budget is limited.
Closes#825
* fix: update Rust CLI for optional mental model fields
The generated Rust client now has content/source_query as Option<String>
after the detail parameter was added. Update CLI code to handle optionals.
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
* 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
* 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
* 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.
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: add fact_types and mental model exclusion filters to reflect and mental models
Adds three new filtering options to both the reflect endpoint and mental model creation/refresh:
- `fact_types`: restrict which fact types (world, experience, observation) are retrieved.
Disables irrelevant agent tools entirely (no wasted tokens).
- `exclude_mental_models`: skip the search_mental_models tool altogether.
- `exclude_mental_model_ids`: exclude specific mental models by ID (merged with the
existing self-exclusion logic during mental model refresh).
For mental models, options are persisted in the existing `trigger` JSONB column so they
are automatically applied on every refresh. The `UpdateMentalModelRequest` already
proxies `trigger`, so no extra endpoint changes are needed.
Also fixes the test fixture (`pg0_db_url` in conftest.py) to correctly resolve pg0://
URLs and run migrations before tests, which was causing all DB-dependent tests to fail
with "relation public.banks does not exist" when HINDSIGHT_API_DATABASE_URL=pg0://uuuu.
* fix: guard against disabled-tool hallucination and regenerate clients
- Add enabled_tools guard in reflect agent: if an LLM calls a tool that
was excluded (e.g. recall when fact_types=["observation"]), return an
error result instead of executing it
- Regenerate OpenAPI spec and all SDK clients (Go, Python, TypeScript)
to include new fact_types / exclude_mental_models fields
* fix: add missing ReflectRequest fields in Rust CLI struct initializers
* fix: filter hallucinated tool calls before trace to prevent disabled tools appearing in results
* chore: merge main, fix lint formatting and update skills openapi.json
* feat: expose fact_types, exclude_mental_models, exclude_mental_model_ids in control plane UI
* fix: add missing trigger fields to MentalModel type in control plane api.ts
* fix: add missing trigger fields to local MentalModel interface in mental-models-view
* feat: tabbed mental model dialogs (Basic / Options tabs)
* refactor: shared FactTypeFilter component, tabbed mental model dialogs use General tab, clean up labels
* feat: pill-style toggle buttons for fact type filter (blue/emerald/amber per type)
* fix: add spacing between Fact Types label and pills, rename to Exclude all mental models
* fix: prevent silent memory loss on consolidation LLM failure
When all LLM retries are exhausted during consolidation, memories were
being marked consolidated_at unconditionally, permanently excluding them
from future consolidation runs without producing any observations.
Fix with two complementary mechanisms:
- Adaptive batch splitting: on LLM failure, the batch is halved and
retried recursively down to batch_size=1, recovering most transient
failures (rate limits, Pydantic validation on long prompts) without
operator intervention
- consolidation_failed_at column: only single-memory batches that still
fail after all retries are marked here instead of consolidated_at, so
they remain visible and retryable
- New API endpoint POST /v1/default/banks/{bank_id}/consolidation/retry-failed
resets these memories for the next consolidation run
* chore: regenerate OpenAPI spec
* fix: rename consolidation endpoint from /retry-failed to /recover
* fix: add consolidation_failed_at column, adaptive batch splitting, and recovery API
- Migration a3b4c5d6e7f8: add consolidation_failed_at TIMESTAMPTZ column to
memory_units with an index for efficient failure queries; properly chains off
g7h8i9j0k1l2 (backsweep_orphan_observations)
- Consolidator: filter pending memories with consolidation_failed_at IS NULL
so failed memories are not re-fetched in an infinite loop
- Consolidator: adaptive batch splitting — when a batch exhausts all 3 LLM
retries, halve it and retry sub-batches recursively; only single-memory
batches that also exhaust all retries get consolidation_failed_at set
- New tests (9 total) covering: adaptive splitting recovers all memories,
larger batch splitting, single-memory permanent failure, exclusion from
next run, partial batch failure, recover resets columns, recover returns
0 when none failed, recover-then-consolidate succeeds, HTTP endpoint
* chore: regenerate Go, Python, TypeScript clients with recover consolidation endpoint
* feat: add Recover Consolidation action to bank Actions dropdown
* style: apply ruff formatting to http.py and config.py
* fix: handle consolidation scope in large batch test mock LLM
The mock LLM was returning {"facts": ...} for ALL calls including consolidation.
Consolidation doesn't use skip_validation=True so it expects a _ConsolidationBatchResponse
instance, not a raw dict. Before this PR consolidation silently swallowed the AttributeError
(failed=False was returned); now failed=True triggers adaptive splitting and timeouts.
Fix: return _ConsolidationBatchResponse() when scope=="consolidation".
* fix: restrict claude-agent-sdk to macOS platform only (no Linux wheel available)
Also fix pre-existing type errors: use setattr for XLM-RoBERTa monkey-patch
and add missing reranker_local_fp16/bucket_batching/batch_size fields to main.py config constructor.
* fix: add UV_INDEX_STRATEGY=unsafe-best-match to fix markupsafe cp314 wheel conflict
PyTorch CPU index serves markupsafe==3.0.3 with only cp314 wheels.
uv's default first-index strategy stops at the first index with any version
even if no compatible wheel exists. unsafe-best-match searches all indices
for the best compatible wheel, falling back to PyPI for markupsafe.
* fix: use explicit pytorch index to prevent markupsafe wheel conflict
Configure the pytorch CPU index as explicit=true in pyproject.toml so it is
ONLY used for torch (via [tool.uv.sources]). All other packages (including
markupsafe) are resolved exclusively from PyPI, preventing the pytorch index
from serving incompatible cp314-only wheels for non-pytorch packages.
Remove UV_INDEX and UV_INDEX_STRATEGY from CI workflow (no longer needed
since the index is now configured in pyproject.toml).
* ci: trigger CI run
* ci: retry trigger
* ci: trigger after remote URL fix
* ci: add workflow_dispatch to unblock manual trigger
* fix: remove empty env blocks left after UV_INDEX removal
* fix: add type: ignore for optional claude_agent_sdk imports (macOS-only)
* fix: correct type: ignore rules for claude_agent_sdk and fix utcnow deprecation
* feat(retain): add verbatim extraction mode
Adds retain_extraction_mode="verbatim" that stores each chunk as-is
without LLM summarization. The LLM still runs to extract entities,
temporal info, and location for full indexability — only the fact text
is replaced with the original chunk content (one memory per chunk).
Useful for RAG-style indexing and benchmarks where original text
must be preserved in memory.
- Add "verbatim" to RETAIN_EXTRACTION_MODES in config.py
- Add VERBATIM_FACT_EXTRACTION_PROMPT with instructions to preserve text
- Add _collapse_to_verbatim() post-processing to enforce 1 fact/chunk
- Expose in bank config UI dropdown with updated description
- Update configuration.md docs with verbatim mode description
- Add unit test for _collapse_to_verbatim and integration test via LLM
- Fix pre-existing main.py CLI override missing new reranker fields
- Fix pre-existing cross_encoder.py ty type error via setattr
* refactor(retain): verbatim mode skips 'what' field entirely
Instead of asking the LLM to echo the chunk text back into 'what' and
then discarding it, verbatim mode now uses a dedicated schema
(VerbatimExtractedFact) that omits the 'what' field altogether.
The LLM only returns metadata (entities, temporal info, location, who),
saving output tokens and avoiding any risk of paraphrasing before the
backfill.
- Add VerbatimExtractedFact / VerbatimFactExtractionResponse models
- Verbatim mode skips causal-relations section (nothing to relate causally)
- _extract_facts_from_chunk: allow missing 'what' in verbatim mode,
set combined_text="" (backfilled by _collapse_to_verbatim)
- Update verbatim prompt to say DO NOT include 'what'
* feat(retain): add index_only extraction mode
Zero-LLM retain mode: chunks are stored as-is with no LLM call, no
entity extraction, and no temporal indexing. Embeddings still run for
semantic search. User-provided entities via RetainContent.entities
are the sole source of entity data.
Early return placed before the batch-API check so no LLM queue or
concurrency locks are acquired.
- Add "index_only" to RETAIN_EXTRACTION_MODES
- Add _extract_facts_index_only() with pure Python chunking path
- Add to UI dropdown and update description
- Update configuration.md with index_only docs and table entry
- Add unit test asserting zero token usage and exact text preservation
* feat(retain): add named retain strategies
Allows mixing extraction modes in a single bank via named strategies.
Each strategy is a set of hierarchical config overrides (extraction_mode,
chunk_size, entity_labels, entities_allow_free_form, etc.) applied on
top of the resolved bank config at retain time.
- retain_strategies: dict of strategy_name → config overrides (bank config)
- retain_default_strategy: default strategy when none specified (bank config)
- strategy field on /retain request: per-call override
- apply_strategy() in config_resolver applies overrides via dataclasses.replace()
- strategy propagates through retain_batch_async → _retain_batch_async_internal
and through the async worker task payload
- Any hierarchical field is overridable per strategy, including entity_labels
and entities_allow_free_form
- Docs updated with strategy configuration example and RRF fairness note
- Unit test for apply_strategy covering overrides, unknown strategy, and
non-hierarchical field filtering
* feat(retain): add per-item strategy and strategy tests
- Add `strategy` field to `MemoryItem` so individual items in a retain
request can override the request-level strategy
- Add `strategy` field to `FileRetainMetadata` for per-file strategy
override in file retain requests
- Group memory items by effective strategy in `api_retain`; each group
is processed as a separate batch, results are aggregated
- Thread strategy through `submit_async_file_retain` →
`_handle_file_convert_retain` → retain task payload
- Add `operation_ids` to `RetainResponse` for async requests with
mixed per-item strategies
- Add `test_strategy_overrides_extraction_mode_for_index_only`: unit
test verifying a named strategy with index_only bypasses the LLM
- Add `test_retain_request_per_item_strategy_field`: unit test for
per-item strategy grouping logic
* feat(ui): add retain strategies and default strategy to bank config UI
- Add StrategiesEditor component: per-strategy cards with name input and
JSON overrides textarea; supports add/remove; validates JSON inline
- Add Default Strategy text input (retain_default_strategy)
- Update RetainEdits type and retainSlice() to include both new fields
- Regenerate OpenAPI spec (retain_strategies, retain_default_strategy,
per-item strategy on MemoryItem/FileRetainMetadata, operation_ids on
RetainResponse)
* refactor(ui): move retain strategies into its own dedicated config section
* feat(ui): improve retain strategies UX and add strategy to document dialog
- Strategy form now includes entity section (free form toggle + entity labels editor)
- Default strategy selector moved outside tab panel, above strategy chips
- Strategy tabs redesigned with underline indicator style for clarity
- Remove strategy confirms with AlertDialog
- Fix tab re-render bug when typing strategy name (skipSyncRef)
- Add strategy field to Add New Document dialog (text + per-file for uploads)
- File upload collapsible uses same Document/Tags/Source tabbed layout
- API: validate empty strategy names in config_resolver
- api.ts: add strategy field to retain and uploadFiles types
* fix: forward strategy through HTTP layer and SDK; add integration test
- route.ts: extract and forward `strategy` from request body to retainBatch
- TypeScript SDK: accept and forward `strategy` in retainBatch options and per-item
- config_resolver.py: validate empty strategy name keys on update
- bank-config-view.tsx: merge entity fields into RetainStrategyForm, redesign strategy tabs with underline style, add confirmation dialog for removal, fix tab-reset-on-typing with skipSyncRef, move default strategy selector outside panel
- bank-selector.tsx: add strategy field to Add Document dialog (per-file in tabbed collapsible)
- test_retain.py: add end-to-end integration test verifying named strategy application (index_only = 0 LLM tokens)
* fix: regenerate TypeScript client with strategy field in RetainRequest/MemoryItem
- Regenerate OpenAPI spec to include strategy field in RetainRequest and MemoryItem
- Regenerate TypeScript client from updated spec
- Add strategy to MemoryItemInput interface
- Remove (item as any) cast now that strategy is properly typed
* rename: index_only extraction mode → chunks
* remove top-level strategy from RetainRequest; strategy is per-item only
* fix(clients): update Go and Python generated clients with strategy/operation_ids fields
* fix(ci): update hierarchical field count, add strategy to Rust MemoryItem initializers
* fix(go-client): minimal targeted YAML updates for strategy/operation_ids fields
* feat: add compound tag filtering via tag_groups
Adds tag_groups to RecallRequest and ReflectRequest to express arbitrary
boolean tag predicates: leaf {tags, match}, and/or/not compounds.
Top-level groups are AND-ed. Existing tags/tags_match unchanged.
Examples:
Step filter AND user scope:
tag_groups: [{tags: ["step:5","step:8"], match: "any_strict"},
{tags: ["user:alice"], match: "all_strict"}]
Exclusion:
tag_groups: [{tags: ["user:alice"], match: "all_strict"},
{not: {tags: ["archived"], match: "any_strict"}}]
- Recursive SQL builder (build_tag_groups_where_clause) threads through
all 4 retrieval strategies (semantic/BM25, temporal, graph, MPFP)
- Python-side filter (filter_results_by_tag_groups) for post-traversal
- 22 new unit tests
- OpenAPI spec + all clients regenerated (Rust, Python, TypeScript, Go)
* fix: add tag_groups: None to Rust CLI struct initializers
* fix: add tag_groups: None to Rust client test RecallRequest initializer
* feat: reject tags+tag_groups together, add tag_groups integration tests
- Add model_validator to RecallRequest and ReflectRequest that returns 422
when both `tags` and `tag_groups` are set (mutually exclusive)
- Add 5 integration tests for tag_groups compound filtering:
* validation: 422 when both fields are set
* AND filter: two leaf groups (step scope AND user scope)
* OR compound: user:alice OR user:bob
* NOT compound: user:alice AND NOT archived
* Nested: user:alice AND (step:5 OR step:8)
* ci: trigger CI run
- API: POST /v1/default/banks/{bank_id}/operations/{operation_id}/retry
resets status to pending so the worker re-executes the task
- UI: Retry button on failed operations in the operations view
- Control plane proxy route + ControlPlaneClient.retryOperation()
- Updated OpenAPI spec, all generated clients, and operations docs
* feat: filter operations by type + fix stale closure in auto-refresh
- Add `type` query param to GET /operations endpoint and engine layer
- Add operation type dropdown filter in Background Operations UI
- Fix auto-refresh interval using stale statusFilter/offset closure by
adding filter state to useEffect deps and wrapping loadOperations in
useCallback (fixes#522)
- Regenerate OpenAPI spec and all SDK clients
* fix: update Rust CLI list_operations call with new type parameter
* feat: add update document tags endpoint with observation invalidation
Adds PATCH /v1/default/banks/{bank_id}/documents/{document_id} to change
tags on a document without re-processing content.
- Updates tags on the document and all associated memory units atomically
- Invalidates observations derived from the document's memory units
- Resets consolidated_at on the document's own units for re-consolidation
- Also resets consolidated_at on co-source memories from other documents
that shared those observations (matching delete_document behavior)
- Triggers async consolidation when observations are invalidated
- 9 new tests covering all invalidation scenarios
UI: adds inline tag editor to the document detail panel in the control plane
Docs: new "Update Document Tags" section in documents.mdx with Python/JS examples
* refactor: simplify UpdateDocumentTagsResponse to {success: true}
* refactor: make PATCH /documents generic update_document endpoint
Renames update_document_tags → update_document (engine + HTTP + clients + UI).
Currently only tags are supported; the structure is open for future fields.
Tags are the only field with side effects (observation invalidation + re-consolidation).
* feat: mental model refresh history tracking and UI diff view
- DB migration: add history JSONB column to mental_models table
- Track previous content on each refresh in update_mental_model
- Add get_mental_model_history() engine method
- New GET /mental-models/{id}/history endpoint
- Control plane proxy route and getMentalModelHistory() in api.ts
- MentalModelDetailModal: add History tab with lazy loading, carousel
navigation (left=older, right=newer), word-level content diff view
* fix: resolve alembic migration head conflict for mental model history
* feat: mental model history tracking, side-by-side diff UI, and config flag
- Track content changes on every mental model update/refresh (persisted in JSONB history column)
- New GET /mental-models/{id}/history endpoint returning changes most-recent-first
- Side-by-side diff view in History tab (Before/After columns, line-level highlights)
- Actions dropdown in detail panel (Edit, Refresh, View History, Delete)
- HINDSIGHT_API_ENABLE_MENTAL_MODEL_HISTORY config flag (default: true)
- Also adds missing HINDSIGHT_API_ENABLE_OBSERVATION_HISTORY to configuration docs
- Python client wrapper method get_mental_model_history()
- Tests for history persistence (recorded, ordered, name-only skipped, missing returns None)
- Fix NameError: timezone not imported in update_mental_model
* fix: call get_mental_model_history before delete in doc example
* feat: add source facts token limits to consolidation and recall
- Add two new configurable (per-bank) parameters:
- consolidation_source_facts_max_tokens: total token budget for source
facts across all observations in the consolidation prompt (-1 = unlimited)
- consolidation_source_facts_max_tokens_per_observation: per-observation
cap so each observation gets a fair share of source facts (-1 = unlimited,
default 256)
- Both are also exposed as recall API parameters via SourceFactsIncludeOptions
(max_tokens and max_tokens_per_observation)
- Consolidation now uses resolve_full_config to respect bank-level overrides
- Improve consolidation prompt: temporal metadata (occurred_start=, | Involving:)
is now clearly separated from observation text, with a concrete example showing
the expected synthesis style and explicit rules not to copy raw fact lines
- Add tests for recall source facts capping and consolidation config forwarding
- Expose all three new fields in the control plane bank config UI
- Document new env vars in configuration.md
- Regenerate OpenAPI spec and all SDK clients
* fix: reorder observations UI fields and rename Label Groups to Entity Labels
* fix: revert Entities section title (only rename inner label)
* doc: add consolidation source facts and batch size fields to memory-banks docs
* feat: add observation history tracking and UI diff view
- Track observation changes over time in a JSONB history column,
appending each update's previous state (text, tags, dates, sources)
instead of overwriting
- Add HINDSIGHT_API_ENABLE_OBSERVATION_HISTORY config flag (default: true)
to toggle history recording
- Expose history field in get_memory_unit for observations
- Fix observations/[modelId] route that was proxying to wrong endpoint
- Add History tab in observation modal and History section in panel,
showing word-level and tag diffs between each change (newest first)
- Extract shared ObservationHistoryView component used by both modal and panel
- Add --random-port flag to start.sh to run multiple dev instances
- Scope Next.js distDir by port to prevent lock file collisions between instances
- Restyle consolidation pending badge (rounded-md with border) and add
inline refresh button; fix loading flicker on data refresh
* feat: dedicated observation history endpoint with source facts diff
- Add GET /memories/{id}/history endpoint returning enriched history with
resolved source fact texts and is_new flags per change
- Deprecate history field in GET /memories/{id} (always returns empty list)
- Reconstruct cumulative source facts per history entry by working backwards
from current state, marking newly added facts with is_new
- Replace inline history panel with "View History" button opening modal
- History modal fetches from dedicated endpoint lazily on tab switch
- Timeline view now opens MemoryDetailModal instead of side panel
- History view uses prev/next navigation (left = older, right = newer)
- Fix --random-port: pass dynamic API_PORT as HINDSIGHT_CP_DATAPLANE_API_URL
to control plane, preserving caller values over .env
* feat: allow per-request file parser selection with fallback chains
Clients can now specify which parser(s) to use when calling the file
retain endpoint, instead of being locked to the server-side default.
Changes:
- `parser` field added to `FileRetainRequest` (request-level default)
and `FileRetainMetadata` (per-file override); accepts a single name
or an ordered fallback chain (list)
- Resolution priority: per-file > request-level > server default
- `HINDSIGHT_API_FILE_PARSER` now accepts a comma-separated fallback
chain (e.g. `iris,markitdown`); fully backward-compatible
- New `HINDSIGHT_API_FILE_PARSER_ALLOWLIST` env var restricts which
parsers clients may request (defaults to all registered parsers)
- Invalid/disallowed parser names are rejected with HTTP 400
- `FileParserRegistry.convert_with_fallback()` tries each parser in
order, falling back on UnsupportedFileTypeError, empty content, or
any other error
- Worker updated to use the fallback chain stored per-task
- OpenAPI spec and all generated clients regenerated
* fix: handle on_file_convert_complete hook and rebase onto main
- Return ConvertResult dataclass from convert_with_fallback() instead
of a plain str, carrying both the content and the winning parser name
- Use winning_parser_name in the on_file_convert_complete hook so
parser_name reflects the parser that actually succeeded, not the chain
- Update all test calls to submit_async_file_retain() to use the new
per-item parser field instead of the removed top-level parser= kwarg
* docs: document HINDSIGHT_API_FILE_PARSER fallback chain and ALLOWLIST
* feat: add source facts token limits to consolidation and recall
- Add two new configurable (per-bank) parameters:
- consolidation_source_facts_max_tokens: total token budget for source
facts across all observations in the consolidation prompt (-1 = unlimited)
- consolidation_source_facts_max_tokens_per_observation: per-observation
cap so each observation gets a fair share of source facts (-1 = unlimited,
default 256)
- Both are also exposed as recall API parameters via SourceFactsIncludeOptions
(max_tokens and max_tokens_per_observation)
- Consolidation now uses resolve_full_config to respect bank-level overrides
- Improve consolidation prompt: temporal metadata (occurred_start=, | Involving:)
is now clearly separated from observation text, with a concrete example showing
the expected synthesis style and explicit rules not to copy raw fact lines
- Add tests for recall source facts capping and consolidation config forwarding
- Expose all three new fields in the control plane bank config UI
- Document new env vars in configuration.md
- Regenerate OpenAPI spec and all SDK clients
* fix: reorder observations UI fields and rename Label Groups to Entity Labels
* fix: revert Entities section title (only rename inner label)
* doc: add consolidation source facts and batch size fields to memory-banks docs
* doc: update cookbook
* fix(cookbook): preserve tag keys during sync, strip local .md links
- Fix extract_tags_from_readme/notebook to return dict[str,str] preserving
sdk/topic keys instead of bare values, preventing topics like
"Customer Service" from being misclassified as SDK
- Add strip_local_md_links() to remove relative .md references that
would cause broken link errors in Docusaurus build
* ci: run test-doc-examples independently without waiting for test-rust-cli
Build the CLI directly in the job instead of downloading the artifact,
so test-doc-examples can start at the beginning in parallel with all other jobs.
* feat: webhook system with task-owned retry, retain.completed event, and UI
- New webhook system: register per-bank webhooks with HMAC signing, configurable
HTTP method/timeout/headers/params (http_config JSONB), and PATCH support
- Webhook deliveries run as async_operations (webhook_delivery type) with
task-owned retry via RetryTaskAt exception and exponential backoff
(60s / 5m / 30m / 2h / 8h, max 6 attempts)
- New retain.completed event fires per-document for both sync and async retain
- Delivery debug info (status code, response body) stored in result_metadata
- Control plane UI: webhooks tab per bank with create/edit/delete and a
deliveries table with cursor pagination and expandable response details
- 28 webhook tests covering HMAC signing, delivery retries, CRUD endpoints,
PATCH update, and retain.completed queuing
- Docs page at developer/api/webhooks documenting event payloads and delivery
- OpenAPI spec and all client SDKs (Python, TypeScript, Rust, Go) regenerated
* fix: update tests for task-owned retry model and guard _webhook_manager attribute
- test_worker.py: test_executor_exception_triggers_retry now raises RetryTaskAt
(plain exceptions are immediate failures in the new system); rename
test_executor_exception_marks_failed_after_max_retries to
test_executor_exception_marks_failed_immediately to reflect new semantics
- test_batch_api.py: remove max_retries kwarg from WorkerPoller constructor
- memory_engine.py: use getattr for _webhook_manager in _fire_retain_webhook
to avoid AttributeError when engine is created without __init__ (tests)
* fix: remove max_retries from benchmark WorkerPoller call
* fix(webhooks): transactional outbox, observations_deleted tracking, sidebar
- Queue webhook delivery rows atomically with the primary operation using the
transactional outbox pattern — prevents lost events on process crash:
- Retain (sync + async): outbox_callback passed into orchestrator.retain_batch
and called inside the DB transaction, replacing the post-commit fire call
- Consolidation: new _mark_operation_completed_and_fire_webhook combines the
status UPDATE and webhook INSERT in one transaction
- Added fire_event_with_conn() to WebhookManager for in-connection delivery
- Track observations_deleted count in consolidation stats and expose it in the
consolidation.completed webhook payload (was always None)
- Add Webhooks page to docs sidebar
- Document at-least-once delivery guarantee with operation_id dedup guidance
* fix(ui): add retain.completed to available webhook event types
* feat(ui): add delete confirmation dialog for webhooks
* fix(webhooks): include operation_id in task_payload so delivery is marked completed
The task_payload JSON was missing the operation_id field, causing execute_task
to see operation_id=None and skip _mark_operation_completed — leaving every
delivery row stuck in 'pending' forever.
Added a test that inserts a real async_operations row and verifies the status
transitions to 'completed' after a successful execute_task call.
* style: fix prettier formatting in webhooks-view
* feat: add Pydantic AI integration to CI, release pipeline, and docs
- Add test-pydantic-ai-integration job to CI (test.yml)
- Add build, publish, and artifact steps to release workflow (release.yml)
- Add hindsight-integrations/pydantic-ai to release.sh version bumping
- Add Pydantic AI documentation page (sdks/integrations/pydantic-ai.md)
- Add Pydantic AI entry to sidebar with icon
* docs: remove Requirements section from pydantic-ai integration page
* feat: add tags filtering and fix offset pagination docs for list documents API
- Add `tags` and `tags_match` query params to GET /banks/{bank_id}/documents
- Supports any, all, any_strict, all_strict matching modes (default: any_strict)
- Fix `q` param description — it's a case-insensitive substring match on document ID only
- Add tests for offset pagination and all tags_match modes
- Regenerate OpenAPI spec and Python/TypeScript/Go clients
- Document the new filtering options in docs/developer/api/documents.mdx
* fix(cli): pass new tags/tags_match args to list_documents
* feat: support timestamp="unset" to retain content without a date
When callers retain timeless content (e.g. fictional documents, static
reference material), passing timestamp="unset" now skips the utcnow()
default so mentioned_at is stored as NULL instead of an artificial date.
- HTTP: validate_timestamp recognises "unset" sentinel and threads it
through api_retain as event_date=None (key present, value None), which
the orchestrator distinguishes from key-absent (still defaults to now)
- Orchestrator: new branching logic separates "key absent" → utcnow()
from "key present but None" → no date
- types.py: RetainContent.event_date and ProcessedFact.mentioned_at are
now datetime | None; removed the unused _now_utc factory
- fact_extraction.py: all event_date params accept datetime | None;
_build_user_message emits "Event Date: Unknown" when None; removed
mentioned_at from the Fact LLM response model (LLM never sets it)
- embedding_processing: skip date suffix when fact_date is None
- entity_resolver: COALESCE(event_date, now()) for first_seen/last_seen
so entities table NOT NULL constraint is preserved
- link_utils: skip temporal linking for units without event_date
- Migration aa2b3c4d5e6f: DROP NOT NULL on memory_units.event_date
- Tests: test_retain_no_timestamp and test_retain_omit_timestamp_defaults_to_now
- Docs + OpenAPI + TypeScript client updated
* refactor: replace _TIMESTAMP_UNKNOWN sentinel with plain string comparison
The sentinel object() was only needed to distinguish "unset" from None
at the boundary — but since the field type is datetime | str | None,
"unset" can pass through the validator unchanged and be compared directly.
* chore: regenerate OpenAPI spec and clients after timestamp type change
timestamp field is now datetime | str | None to accept the "unset" sentinel value.
* feat: observation_scopes field to drive observations granularity
* fix(migration): make a2b3c4d5e6f7 a no-op to fix CI on fresh DB
The z1u2v3w4x5y6 migration already creates observation_scopes directly,
so the rename migration fails on fresh installs where observation_tags
never existed.
* chore: remove no-op migration a2b3c4d5e6f7
* feat: regenerate clients with observation_scopes field
- Add observation_scopes to OpenAPI spec and all generated clients
- Fix Rust build.rs to handle anyOf with >2 variants containing null
(previously only handled 2-item anyOf, causing progenitor to panic
on the observation_scopes union type)
* fix(rust): add observation_scopes: None to MemoryItem struct literals
* fix(api): add title to observation_scopes Field for deterministic client generation
Adding title="ObservationScopes" makes the inline anyOf schema use
the explicit name instead of deriving it from the field name, which
was non-deterministic between arm64 (macOS) and amd64 (CI) Docker.
Also fixes description: "each entity" -> "each tag".
* fix(scripts): use linux/amd64 Docker for client generation to ensure reproducibility
Both Python and Go client generation now use --platform linux/amd64
Docker, ensuring identical output on macOS arm64 (local) and Linux
amd64 (CI). Also switches Go from JAR+Java to Docker to eliminate
Java version variability.
* chore: update generated clients to API v0.4.14
* fix(test): add retry logic to test_retain_chinese_content to handle non-deterministic LLM output
* fix(test): mark test_retain_chinese_content as xfail due to non-deterministic LLM translation
* feat: filter graph memories with tags
* fix(cli): pass new q/tags/tags_match args to get_graph
* docs: use CodeSnippet for tags_match examples in recall.mdx
* fix: handle observations regeneration when memories get deleted
* feat: add clear_memory_observations endpoint and regenerate clients
- Add DELETE /banks/{id}/memories/{memory_id}/observations endpoint
- Add observations lifecycle/invalidation section to docs
- Regenerate OpenAPI spec and all clients (Python, TypeScript, Go, Rust)
* refactor: use dedicated response model for clear_memory_observations, remove code example from docs
* 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
* feat: accept pdf, images and office files
* refactor: rename FileConverter to FileParser, simplify file retain API
- Rename engine/converters/ → engine/parsers/, FileConverter → FileParser,
ConverterRegistry → FileParserRegistry, MarkitdownConverter → MarkitdownParser
- Rename env var HINDSIGHT_API_FILE_CONVERTER → HINDSIGHT_API_FILE_PARSER
- Remove async/document_tags params from FileRetainRequest (always async now)
- Add retain_files() to Python Hindsight client and retainFiles() to TypeScript client
- Add sample.pdf to doc examples for working file upload demonstrations
- Update test_file_retain.py to use new parser names and always-async behavior
- Fix Go client missing os import in api_files.go
- Simplify postgresql.py storage to minimal schema
* fix: update rust CLI tests to use is_supported_file instead of is_text_file
* fix: patch Go api_files.go to add missing 'os' import after generation
* fix: insert 'os' import after 'net/url' in api_files.go patch for correct position
* chore: regenerate OpenAPI spec and clients (converter→parser description update)
* fix: improve async batch retain with large payloads
* fix: improve async batch retain with large payloads
* api
* api
* api
* api
* api
* Clean up perf benchmark: keep only Python files
- Remove README.md and PERFORMANCE_FINDINGS.md
- Remove results/ JSON files (gitignored)
- Remove test_data/ directory
- Keep only __init__.py and retain_perf.py
* docs: explain automatic batch optimization for async retain
- Add section explaining Hindsight automatically handles batch sizing
- Users don't need to manually tune batch sizes with async mode
- Hindsight splits large batches (>10k tokens) into optimized sub-batches
- Include example showing best practices
* docs: remove emojis and code example from performance page
* fix: correct OperationDetails type to match API response
- Change optional fields to use | null instead of ?
- Fixes TypeScript compilation error in control plane build
* fix: use discriminated union for OperationDetails type
- Support both success and error states properly
- Fixes TypeScript error when setting error state
* fix: use unique document_ids in batch retain examples
- Each item in a batch must have unique document_id
- Update both Python and JavaScript examples
- Fixes test-doc-examples CI failure
* chore: trigger CI
* fix: test mocking and duplicate document_ids in examples
- Mock _get_pool() in test_async_retain_tags.py to avoid _initialized error
- Set _initialized = True on mocked MemoryEngine instances
- Fix duplicate document_ids in retain.py and retain.mjs examples
* fix: properly mock async pool/connection and fix more duplicate document_ids
- Use AsyncMock for pool.acquire() to fix 'can't be used in await' error
- Fix duplicate document_ids in retain-async examples (retain.py and retain.mjs)
- Remove batch-level document_id parameter that caused duplicates
* ci: collect all doc example failures and show summary
- Run all Python/Node.js/CLI examples regardless of individual failures
- Collect failure list and display summary at the end
- Show pass/fail count and list of failed files
- Exit with failure only after running all examples
* refactor: extract doc example testing to standalone script
- Create scripts/test-doc-examples.sh to run all examples
- Collects logs of failed examples separately
- Shows full error logs only for failures at the end
- Clean summary with pass/fail counts
- Proper exit codes
- Replaces inline bash in CI workflow
* fix: doc examples - duplicate document_ids and error handling
- retain.py: move document_id to item level to avoid duplicates
- documents.mjs: add error handling for getDocument to show clear error message
* fix: update tests for duplicate document_id validation
- test_async_retain_tags: verify operation structure instead of exact UUID
- test_delete_bank: use unique document_ids (team-doc-1, team-doc-2)
* feat: implement hierarchical configuration (system, tenant, bank)
* feat: implement hierarchical configuration (system, tenant, bank)
* docs: add instructions for hierarchical config in CLAUDE.md
* feat: add ENABLE_BANK_CONFIG_API flag (disabled by default)
- Add HINDSIGHT_API_ENABLE_BANK_CONFIG_API env var (default: false)
- Return 403 Forbidden from bank config endpoints when disabled
- Update tests to enable the flag
- Update CLAUDE.md documentation
This provides security control over the bank configuration API,
ensuring it's only accessible when explicitly enabled.
* docs: add hierarchical configuration section
* feat(cli): add bank config commands (config, set-config, reset-config)
- Add 'hindsight bank config' to view bank configuration
- Add 'hindsight bank set-config' to update LLM settings per bank
- Add 'hindsight bank reset-config' to reset to defaults
- Implements client API calls to new bank config endpoints
* fix(cli): fix compilation errors in bank config commands
- Fix type signature: use ApiClient instead of api::Client
- Fix confirmation: use ui::prompt_confirmation instead of ui::confirm
- Fix error handling: use anyhow! macro instead of errors::Error
- Fix type conversion: convert HashMap to serde_json::Map for API call
* feat: implement type-safe hierarchical config with bank overrides
Implements a production-ready hierarchical configuration system that prevents
accidentally using global defaults when bank-specific overrides exist.
- Created StaticConfigProxy that wraps HindsightConfig
- get_config() now returns proxy that blocks access to bank-configurable fields
- Raises ConfigFieldAccessError with clear message when accessing configurable fields
- Added _get_raw_config() for internal use only
- Forces developers to use resolve_full_config(bank_id, context) for bank settings
- Added resolve_full_config() method that returns complete HindsightConfig
- Resolves hierarchy: Global (env) → Tenant → Bank
- No caching to support multi-server deployments (always fresh from DB)
- LLM provider pooling handles expensive operations separately
- Updated entire retain pipeline to pass resolved config through call chain
- memory_engine.py: Resolves config at top level where bank_id/context available
- orchestrator.py: Accepts and passes config to fact_extraction
- fact_extraction.py: Uses passed config instead of get_config()
- utils.py: Added optional config param for backward compatibility
- consolidator.py: Uses resolve_full_config() for enable_observations check
- memory_engine.py: Resolves config before triggering consolidation
- Renamed "Memory Bank" to "Bank Configuration" with tabs
- Combined Stats and Operations into "General" tab
- Consolidated Profile and Configuration into "Configuration" tab
- Moved Actions dropdown to page level (outside tabs)
- Created new component for managing bank-specific config
- Displays configurable fields: retain_chunk_size, retain_extraction_mode, etc.
- Edit via dialog with form validation
- Reset to defaults via AlertDialog confirmation
- Shows field IDs in monospace for clarity
- Visual separation with borders and hover effects
- Removed inline edit mode, switched to dialog-based editing
- Separate dialogs for Disposition and Mission editing
- Read-only display with clear edit buttons
- Removed duplicate stats cards and operations
- bank-stats-view.tsx: Overview statistics (memories, links, documents, pending ops)
- bank-operations-view.tsx: Background operations table with filtering
**Problem**: Consolidation always used global enable_observations, ignoring bank overrides
**Root Cause**: consolidator.py called get_config() instead of resolving bank-specific config
**Solution**: Pass resolved config through the entire pipeline
**Problem**: asyncpg returning JSONB as JSON string instead of parsed dict
**Solution**: Explicit JSON parsing in config_resolver.py with type checking
- All 19 API integration tests pass
- All 10 hierarchical config tests pass
- Retain operations work correctly with bank-specific config
- Consolidation respects bank-specific enable_observations setting
- Updated developer/configuration.md with type-safe config access pattern
- Added examples showing correct usage patterns
- Documented ConfigFieldAccessError and resolution methods
- get_config() now returns StaticConfigProxy (blocks configurable field access)
- Code accessing bank-configurable fields must use resolve_full_config()
- Clear migration path with helpful error messages
Fixes hierarchical configuration to be production-ready with proper type safety.
* refactor: remove LLM client pool and simplify config resolver
Since LLM config (provider, model, api_key) is now static and not
bank-configurable, the LLMClientPool is no longer needed.
Changes:
- Remove hindsight_api/llm_client_pool.py (no longer needed)
- Remove memory_engine._get_bank_llm_config() (dead code, never called)
- Simplify config_resolver.py by eliminating duplication between
resolve_full_config() and get_bank_config()
- get_bank_config() now calls resolve_full_config() and filters results
- Remove outdated "LLM provider pooling" comments from docstrings
All tests pass (10 hierarchical config tests, 19 API integration tests)
* fix: update tests to use _get_raw_config() for configurable fields
Fixed test fixtures that were accessing configurable fields (like
enable_observations) from get_config(), which now raises
ConfigFieldAccessError due to type-safe config access.
Changes:
- test_consolidation.py: Changed enable_observations fixture to use
_get_raw_config() instead of get_config()
- test_consolidation.py: Updated test_consolidation_returns_disabled_status
to set bank config instead of mocking get_config()
- test_link_expansion_retrieval.py: Changed fixture to use _get_raw_config()
- test_observations.py: Changed disable_observations fixture to use
_get_raw_config()
- Regenerated OpenAPI spec and clients
All 39 previously failing tests now pass.
* fix: add missing config parameter to test calls of extract_facts_from_text()
Fixed 45 test failures where tests were calling extract_facts_from_text()
without the new required config parameter.
Changes:
- Added config=_get_raw_config() to all extract_facts_from_text() calls
- Fixed test_main_module.py to patch _get_raw_config instead of get_config
- Updated 6 test files with 37 function call sites
All tests should now pass.
* fix: add missing config parameter to test_skip_podcast_meta_commentary
One more test was missing the config parameter for extract_facts_from_text().
* fix: add default values to OpenAPI schema for default_factory fields
This commit fixes the OpenAPI schema to include default values for fields
using default_factory, which improves schema accuracy and client generation.
Changes:
1. Added FieldWithDefault() helper to inject default values into OpenAPI schema
2. Updated 14 fields using default_factory to include defaults in schema:
- ReflectBasedOn.{memories, mental_models, directives}
- ReflectTrace.{tool_calls, llm_calls}
- All tags fields
- All trigger fields
- All include fields
3. Regenerated OpenAPI spec with proper defaults
4. Added tests to verify API returns correct format with empty banks
Note: This fixes the schema but doesn't change the v0.3.0 -> v0.4.0 breaking
change where based_on went from list to object. Clients should handle both
formats for backward compatibility.
* fix: remove client imports from API test
The test was failing in CI because it imported the client library
which isn't installed in the API test environment.
Changed to test only API JSON response format, not client parsing.
This is more appropriate for an API test anyway.
* test: add client tests for ReflectResponse parsing
Added comprehensive tests in hindsight-clients/python/tests to verify:
- v0.4.0+ format with empty based_on object
- v0.4.0+ format with null based_on
- v0.4.0+ format with populated facts
- v0.3.0 format (list) correctly fails validation
- Missing based_on field handling
These tests document the v0.3.0 -> v0.4.0 breaking change where
based_on changed from list to object.