* docs: add 0.4.20 release blog post and changelog
Add release notes blog post covering Claude Code integration, LangGraph
integration, NemoClaw integration, independent integration versioning,
and reflect improvements. Auto-generated changelog entry included.
* docs: add 0.4.20 release blog cover image
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>
Daemon cold start takes ~25s but hooks have short timeouts, causing
retain to time out on first use. Fix by firing daemon startup as a
detached background process in SessionStart so it warms up before the
first recall/retain hook fires.
Also bumps the daemon start timeout in _ensure_daemon_running from 10s
to 30s as a fallback for when retain fires before pre-start completes.
* fix(entity_resolver): prevent _pending_stats/_pending_cooccurrences memory leak
Add discard_pending_stats() to EntityResolver to clean up both pending dicts
for the current task key. Call it at the start of each _run_db_work attempt so
that exceptions between accumulation and flush_pending_stats() — including
deadlock retries — never leave stale entries keyed by recycled task IDs.
Fixes#660
* test(entity_resolver): add unit tests for discard_pending_stats()
Covers: clears both dicts for current task, is idempotent when empty,
and does not touch entries belonging to other task keys.
No database required — purely in-memory logic.
* doc: add Claude Code + Telegram + Hindsight blog post
* doc: add fabioscarsi to blog authors
* doc: update fabioscarsi title to Contributor
* doc: remove horizontal rule dividers from blog post
* doc: update cover image and add image frontmatter for claude-code-telegram blog post
* doc: remove horizontal rule dividers
* doc: align Hindsight setup steps with PR #661 README
* fix: move marketplace.json to repo root and update source path
* doc: add Claude Code integration page, sidebar, and integrations hub entry
* doc: update versioned docs to 0.4.19
---------
Co-authored-by: Ben <ben.bartholomew@vectorize.io>
* fix(claude-code): fix plugin installation and release workflow
- Fix plugin.json author field (string → object) to pass claude plugin validate
- Add hindsight-integrations/.claude-plugin/marketplace.json so users can install
via: claude plugin marketplace add vectorize-io/hindsight --sparse hindsight-integrations
- Update README and install.sh with correct two-command install flow
- Fix release-integration.yml: add explicit package.json check for typescript type
and add plugin type for integrations with neither pyproject.toml nor package.json
(prevents claude-code from incorrectly falling into the typescript build path)
- Add CHANGELOG.md for the claude-code integration
* remove install.sh — users install via claude plugin commands directly
* test(claude-code): add 116 unit tests for plugin hooks and lib modules
* feat(claude-code): user settings.json at CLAUDE_PLUGIN_DATA for stable config
Plugin now checks CLAUDE_PLUGIN_DATA/settings.json after the versioned
plugin default, giving users a path that persists across updates:
~/.claude/plugins/data/hindsight-memory-hindsight/settings.json
Loading order: defaults → plugin settings.json → user settings.json → env vars
* fix(claude-code): use ~/.hindsight/claude-code.json for user config
Matches the ~/.openclaw/openclaw.json convention. Removes the confusing
CLAUDE_PLUGIN_DATA path whose name depends on marketplace+plugin identifiers.
* docs(claude-code): add ToS hint for claude-code LLM provider option
* fix(claude-code): set author to Hindsight Team in plugin.json
* ci: add test-claude-code-integration job to run plugin unit tests
* feat: Add Claude Code integration plugin
Complete port of hindsight-openclaw (v0.4.19) adapted to Claude Code's
hook-based plugin architecture. Pure Python stdlib, no external dependencies.
- Auto-recall via UserPromptSubmit hook (additionalContext injection)
- Auto-retain via async Stop hook (chunked retention with sliding window)
- Daemon management (auto-start/stop hindsight-embed via uvx)
- Dynamic bank IDs with per-agent/project/channel/user granularity
- All 34 configuration options with env var overrides
- File-based state persistence with fcntl locking
- Graceful degradation on all error paths
Works with Claude Code Channels (Telegram, Discord, Slack) and
interactive sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Set correct chunked retention defaults (10/2, not 1/0)
retainEveryNTurns=10 and retainOverlapTurns=2 are the production-tested
values — every 10 turns, retain a 12-turn sliding window. The previous
defaults (1/0) would retain every single turn with no overlap, defeating
the chunked retention design that prevents API bombardment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Align recallBudget and daemonIdleTimeout with Openclaw defaults
recallBudget: "low" → "mid" (Openclaw default)
daemonIdleTimeout: 300 → 0 (Openclaw default, never auto-stop)
As an official Hindsight integration, defaults should match Openclaw.
Users can optimize locally via settings.json or env vars.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename hindsight-openclaw-pro → HindClaw and update description to
reflect the current architecture: server-side Hindsight extensions
(hindclaw-extension on PyPI), Terraform provider for infrastructure
management, and the hindclaw-openclaw gateway plugin.
Link points to https://github.com/mrkhachaturov/hindclaw.
* test: add unit tests for pg_trgm auto-detection and ValidationResult.accept_with() enrichment
Two recent PRs landed without dedicated tests:
- #626/#649 (pg_trgm fallback in EntityResolver): add 5 mocked unit tests
covering the trigram→full fallback, single-check guarantee, and sticky
downgrade behaviour.
- #639 (accept_with() enrichment): add 7 pure unit tests for the factory
method plus 5 integration tests verifying the engine applies enriched
contents (retain) and tags/tag_groups (recall) returned by validators.
Also verifies RecallContext carries tag filter state.
* fix: remove 504 from reflect OpenAPI spec to fix progenitor Rust client build
progenitor-impl-0.11.2 panics with `assertion failed: response_types.len() <= 1`
when an endpoint declares more than one response type. PR #643 added
`responses={504: ...}` to the reflect decorator, which injected a second
response type into the generated OpenAPI spec and broke the Rust client build.
Remove the `responses=` kwarg — the 504 is still raised at runtime via
JSONResponse(status_code=504), it just won't appear in the OpenAPI schema.
Regenerate openapi.json accordingly.
* chore: sync generated files and ruff formatting (lint + docs skill)
On managed PostgreSQL services (e.g. Azure Flexible Server), the pg_trgm
extension may not be available, causing two failures:
1. Migration c1a2b3d4e5f6 crashes on CREATE EXTENSION
2. Even if migration is bypassed, the default 'trigram' entity lookup
strategy uses the % operator which requires pg_trgm, causing retain
background tasks to fail silently
Changes:
- Migration now gracefully skips pg_trgm and index creation if the
extension cannot be loaded
- EntityResolver auto-detects pg_trgm availability on first use and
falls back to 'full' lookup strategy with a warning log
Co-authored-by: coder999999999 <coder999999999@users.noreply.github.com>
Validators can now return enriched data via ValidationResult.accept_with()
instead of only accepting or rejecting operations. The engine applies
returned fields (contents, tags, tag_groups) to the operation parameters.
- Add accept_with() factory to ValidationResult with optional enrichment
fields: contents, tags, tags_match, tag_groups
- Add tags, tags_match, tag_groups to RecallContext so validators can
see current filter state
- Update _validate_operation to return ValidationResult
- Apply enrichment from result at all retain (2 sites) and recall call
sites in MemoryEngine
- Existing validators using accept()/reject() work unchanged
LLM providers like MiniMax wrap JSON responses in markdown code fences
(```json ... ```), causing JSON parse failures and 5-11 retries per
extraction. The existing fence stripping logic was gated to only
"lmstudio" and "ollama" providers (and for Ollama, unreachable due to
the _call_ollama_native redirect).
Changes:
- Extract _strip_code_fences() helper function
- Apply fence stripping to all providers in call() (not just local)
- Add fence stripping safety net to _call_ollama_native()
- Add 10 tests covering bare JSON, fenced JSON, malformed fences,
and real-world MiniMax response format
Fixesvectorize-io/hindsight#645
Co-authored-by: feniix <feniix@desktop>
* fix(recall): reject empty queries with 400 and fix SQL parameter gap causing IndeterminateDatatypeError
When query text contains only punctuation/symbols (no word characters after
normalization), the BM25 arms are skipped but the old code still placed `limit`
at \$3 in the params list. If tags or tag_groups were also set, their params
(\$4+) were referenced in the SQL while \$3 was a gap, causing PostgreSQL to
raise IndeterminateDatatypeError.
Fix the parameter layout so `limit` is only appended to params when tokens are
present (i.e. when BM25 arms actually use LIMIT \$3), and shift tags_param_idx
from 4 to 3 in the no-tokens path.
Also add a field_validator on RecallRequest.query that rejects empty-after-
normalization queries at the API layer with a 400 before they reach the DB.
* refactor: extract tokenize_query helper and reuse in RecallRequest validator
Remove the sys_platform == 'darwin' constraint that prevented
claude-agent-sdk from installing on Linux, breaking the claude-code
provider in Docker containers.
Fixes#640
* fix(litellm): fall back to last user message when hindsight_query not provided
inject_memories=True no longer requires an explicit hindsight_query. The
injection path now falls back to extracting the last user message, matching
the documented Quick Start behavior that was broken since #167 (v0.4.18).
* test(litellm): add regression tests for inject_memories without hindsight_query
* fix: MCP tool calls fail when MCP_AUTH_TOKEN and TENANT_API_KEY differ
When both HINDSIGHT_API_MCP_AUTH_TOKEN and ApiKeyTenantExtension are
configured with different values, MCP transport auth passes but tool
execution fails because the MCP token gets re-validated against the
tenant API key in the engine layer.
Add mcp_authenticated flag to RequestContext so the engine skips tenant
re-validation when MCP transport auth already succeeded.
Fixes#627
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: strengthen assertion to verify no auth error in tool response
The original test only checked that "banks" key existed in the response,
which was true even for error responses like {"error": "...", "banks": []}.
Now asserts "error" not in parsed to properly catch auth failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
PyPI was not displaying package READMEs because the `readme` field
was missing from pyproject.toml. Hatchling requires this to be
explicitly declared. Fixes langgraph, agno, hermes, and pydantic-ai.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs(blog): add NemoClaw persistent memory blog post
Covers external API mode, OpenShell network egress policy pattern,
and the LaunchAgent symlink gotcha from the live test run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(blog): update NemoClaw blog post with SEO-optimized draft
- Add slug, TL;DR, pitfalls, tradeoffs table, recap, next steps sections
- Restructure into numbered implementation steps
- Remove internal blog links that don't exist yet
* docs(blog): fix docs link to include /recall/ path
* docs(blog): add correct internal links to NemoClaw blog post
* docs(blog): make hindsight-nemoclaw setup command the primary path
One-command setup is now the default; manual 4-step process moved to
'Manual Alternative' section for reference.
* docs(blog): update title to lead with NemoClaw and best-in-class memory
* Add cover image to NemoClaw memory blog post
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add LangGraph integration with tools, nodes, and store patterns
Add hindsight-langgraph SDK providing three integration patterns:
- Tools: retain/recall/reflect as LangChain tools for ReAct agents
- Nodes: automatic memory injection and storage as graph steps
- Store: LangGraph BaseStore implementation for checkpoint-based memory
Fix: remove `from __future__ import annotations` in nodes.py which
prevented LangGraph from passing RunnableConfig to node functions
(runtime type inspection saw string annotations instead of actual types).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: register langgraph with independent versioning system
- Set version to 0.1.0 (integrations are versioned independently)
- Add langgraph to VALID_INTEGRATIONS in release-integration.sh
- Add changelog page for langgraph integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove manual cookbook recipe page
The sync-cookbook script will auto-generate this from the notebook
in hindsight-cookbook once PR #17 is merged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: comprehensive improvements to langgraph integration
Code fixes:
- Retain node only stores latest messages instead of all history (prevents duplicates)
- Handle multimodal msg.content (list type) in nodes
- Fix store docstring separator "/" → "."
- Apply search filters before pagination in store
- Add ttl parameter to store.aput for LangGraph BaseStore compat
- Fix _ensure_bank to not cache failed bank creations
- Fix falsy value bugs (or → is not None) in tools
- Remove from __future__ import annotations from all files
- Consistent default budget="mid" across tools/nodes/store
- Bump langgraph floor to >=0.3.0, remove duplicate dev deps
Docs fixes:
- Fix broken Cloud client example (base_url is required)
- Complete API reference tables with all parameters
- Add Limitations and Notes section (async-only store, etc.)
- Add Requirements section
- Fix broken cookbook link and Cloud claim in blog post
All 61 unit tests pass. E2E tested against Hindsight Cloud:
tools, nodes, store, configure(), multimodal content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove blog post (lives in hindsight-marketing-content)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove Hindsight Cloud section from langgraph docs
Keep OSS docs self-hosted-first, consistent with other integration
docs (crewai, pydantic-ai, agno). Cloud setup details live in the
cookbook notebooks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: explicitly mention LangChain compatibility in langgraph integration
The tools pattern (create_hindsight_tools) only depends on
langchain-core and works with plain LangChain via bind_tools() —
no LangGraph required. Update docs to make this clear with both
LangGraph and LangChain quick start examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review findings
1. Guard manual test files with if __name__ == "__main__" so pytest
doesn't collect and execute them during test runs
2. Remove semantic fallback in HindsightStore.aget() — only return
exact document_id matches, not unrelated semantic search hits
3. Make langgraph an optional dependency — tools pattern only needs
langchain-core. Install with pip install hindsight-langgraph[langgraph]
for nodes and store patterns. Lazy imports with clear error messages.
4. Clean up README to be self-hosted-first, consistent with other
integration docs
5. Update docs requirements section to reflect optional langgraph dep
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback for langgraph integration
- Fix#2: Add per-bank asyncio.Lock to _ensure_bank for concurrency safety
- Fix#3: Clamp search score to max(0.0, ...) to prevent negative values
- Fix#4: Implement suffix matching in _handle_list_namespaces
- Fix#5: Truncate namespaces to max_depth instead of filtering (per BaseStore contract)
- Fix#6: Remove list_namespaces/alist_namespaces overrides — let base class handle prefix=/suffix= kwargs
- Fix#7: Document ephemeral namespace tracking and get() limitations in class docstring
- Fix#8: Add stable ID to recall node SystemMessage, document ordering behavior
- Fix#9: Change budget/max_tokens/recall_tags_match defaults to None so global config fallback works
- Fix#10: Conditionally populate __all__ so import * works without langgraph installed
- Fix#11: Bump langgraph lower bound from >=0.3.0 to >=0.5.0
- Fix#12: Extract _resolve_client to shared _client.py module
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address remaining review gaps for langgraph integration
- Add output_key parameter to create_recall_node for prompt ordering control
- Add prefix/suffix/combined filter tests for list_namespaces
- Add output_key unit tests (memory text, none on empty, none on error)
- Remove unused imports and backward-compat alias in tools.py
- Update docs with output_key usage example and API reference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: relax langgraph version constraint to >=0.3.0
Research confirmed all required APIs (BaseStore, SearchItem, Result,
GetOp, PutOp, SearchOp, ListNamespacesOp) are available since
langgraph-checkpoint 2.0.7, which maps to langgraph >=0.2.63.
Using >=0.3.0 as a clean semver boundary — >=0.5.0 was unnecessarily
conservative and excluded many compatible versions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The hindsight-api meta-package was missing [project.scripts], causing
`uvx hindsight-api@{version}` to fail with exit code 28 when used in
hindsight-embed's daemon launcher.
Re-export the same scripts defined in hindsight-api-slim so uvx can
resolve the executable without requiring --from.
* 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 non-atomic async operation creation in _submit_async_operation
Previously the method performed two separate database round-trips:
1. INSERT into async_operations with no task_payload (null)
2. submit_task → UPDATE to set task_payload
A process crash or network error between steps 1 and 2 left a row with
task_payload IS NULL permanently. The worker's claim query requires
task_payload IS NOT NULL, so these orphaned rows could never be picked up
and the queue appeared degraded indefinitely.
Fix: build full_payload before the INSERT and include task_payload in the
same INSERT statement, making operation creation atomic. submit_task is
still called afterwards — for SyncTaskBackend it executes the task
immediately (unchanged behaviour); for BrokerTaskBackend it becomes an
idempotent UPDATE (payload already set) kept for symmetry.
* Preserve datetime payloads in atomic async insert
* Fix orphaned batch_retain parents when child fails via unhandled exception
When a child retain operation fails with an unhandled exception (e.g. a DB
constraint violation), the memory engine's transaction is rolled back entirely,
including any call to _maybe_update_parent_operation. The poller's fallback
_mark_failed then updates the child status but leaves the parent batch_retain
permanently stuck in 'pending'.
Fix: wrap _mark_failed in a transaction and call a new poller-level
_maybe_update_parent_operation after marking the child failed. This mirrors
the memory engine's own parent-update logic and ensures the parent is
resolved to completed/failed regardless of how the child failure was detected.
The poller's implementation locks the parent row, checks all siblings, and
only finalises the parent once all siblings have reached a terminal state.
Errors in parent propagation are logged but do not affect the child failure
path, which is the critical state change.
* Add tests for _mark_failed parent propagation in WorkerPoller
Tests cover the new _maybe_update_parent_operation logic:
- Last sibling fails → parent batch_retain becomes failed
- Sole child fails → parent becomes failed
- Sibling still pending → parent stays pending (no premature resolution)
- No parent in result_metadata → safe no-op
- End-to-end: unhandled exception via execute_task propagates to parent
* feat(skill): validate links, strip images, include openapi.json and changelog
- Add post-processing step to rewrite Docusaurus site-root paths (e.g.
/developer/foo) to proper relative .md paths within the skill
- Strip markdown and HTML images from all generated files since assets
are not bundled with the skill
- Copy hindsight-docs/static/openapi.json into references/openapi.json
and map /api-reference links to it
- Include changelog.md from src/pages/ alongside faq and best-practices
- Add final validation step that fails the build if any link still
points outside the skill directory
* ci: run generate-docs-skill in verify-generated-files job
* fix(skill): strip unresolvable site-root links instead of leaving them broken
* fix(skill): write file when images stripped but no links rewritten
* chore(skill): regenerate with fixed links, stripped images, changelog and openapi
* fix(skill): handle changelog as directory, add agno/hermes integrations, rebase on main
- Add IntegrationsBanner component with infinite left-to-right CSS scroll animation showing all clients, integrations, and LLM providers
- Place banner below the navbar on every page via Navbar theme wrapper
- Add Agno and Hermes to both the IntegrationsGrid and the banner
- Remove right border from doc sidebar via custom.css
* feat: independent versioning for integrations
- Add per-integration changelog pages at /changelog/integrations/<name>
- Move main changelog to changelog/index.md (URL unchanged)
- Add --integration flag to generate-changelog for LLM-based per-integration changelog generation
- Add scripts/release-integration.sh <name> <version> for cutting integration releases
- Add .github/workflows/release-integration.yml to publish on integrations/** tags
- Remove integrations from main release.sh and release.yml cycle
* fix: add agno and hermes integration docs to version-0.4 for production build
* chore: apply ruff formatting to generate_changelog.py
* feat: add 4-tab code parity across all documentation examples
Every code snippet Tabs block now has Python, Node.js, CLI, and Go variants.
Raw HTTP/curl tabs replaced with proper SDK calls.
New example files:
- Go: retain.go, recall.go, reflect.go, memory-banks.go, directives.go,
mental-models.go, documents.go, main-methods.go
- Shell: memory-banks.sh, directives.sh, mental-models.sh
- Node.js: mental-models.mjs
Extended example files with missing sections:
- recall.mjs/sh: world/experience/observation types, token-budget, all tag modes
- reflect.sh: reflect-with-params, reflect-disposition, reflect-sources, reflect-with-tags
- reflect.mjs: reflect-with-tags, fixed reflect-sources API usage
- retain.mjs/sh: retain-conversation, retain-batch, retain-files-batch
SDK/CLI additions:
- TypeScript: getMentalModelHistory method
- CLI recall: --tags, --tags-match flags
- CLI reflect: --tags, --tags-match, --include-facts flags
- CLI directive update: --is-active flag
- CLI bank set-config: --retain-mission, --retain-extraction-mode,
--observations-mission, --reflect-mission, --disposition-* flags
Build validation:
- scripts/check-code-parity.mjs validates 4-tab parity across all MDX files
- Integrated into npm run build — fails if any Tabs block is missing a variant
* fix: fix doc examples for Go, Node.js, CLI + add mental model with-id examples
- Fix Go Budget constants: BUDGET_HIGH/LOW/MID → HIGH/LOW/MID
- Fix Go documents.go: ListDocuments returns []map[string]interface{}, use map access
- Fix Go retain.go: use correct relative path for sample.pdf
- Fix Node.js createMentalModel: use positional args (name, sourceQuery) not object
- Add CLI 'history' subcommand for mental models (api.rs, main.rs, mental_model.rs)
- Rebuild TypeScript/Python clients to support id param in createMentalModel
- Add create-mental-model-with-id examples across all 4 languages and docs
* fix: move id param to end of create_mental_model signature for backwards compat
* Fix entity_id null constraint for non-ASCII entity names (Turkish İ etc.)
Python's str.lower() and PostgreSQL's LOWER() produce different results for
some Unicode characters. The most common case is Turkish İ (U+0130):
Python: 'İstanbul'.lower() == 'i\u0307stanbul' (i + combining dot, 2 chars)
PostgreSQL: LOWER('İstanbul') == 'istanbul' (plain i, 1 char)
In _resolve_from_candidates, the fallback SELECT for conflicted entity names
passed Python-lowercased strings to LOWER(canonical_name) = ANY($names), so
PostgreSQL couldn't match them. entity_ids[idx] stayed None, which then
caused a NOT NULL violation on unit_entities.entity_id, failing the entire
retain.
Fix: pass original mixed-case names to the fallback SELECT and use
LOWER(canonical_name) = ANY(SELECT LOWER(n) FROM unnest($2) AS n) so
PostgreSQL lowercases both sides identically. The query also returns the
original input_name so we can add a Python-lowercased key to id_by_name
for the assignment loop that uses Python-lowercased keys.
* Add regression test for Unicode entity conflict
The Pydantic model extraction paths (batch API and parallel extraction) used
fact_from_llm.fact_type directly, bypassing the \"assistant\" → \"experience\"
conversion and causing DB CHECK constraint violations.
Unified the conversion logic across all paths:
- \"assistant\" → \"experience\"
- \"world\" → \"world\"
- anything else: fall back to fact_kind (\"assistant\" → \"experience\"), else \"world\"