* 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>
* feat(typescript-client): add Deno compatibility
- Switch build from tsc to tsup for dual CJS + ESM output with proper exports field
- Add deno_setup.ts preload that injects Jest-compatible globals (describe/test/expect) via @std/testing/bdd and @std/expect
- Fix generated client.gen.ts: exclude hey-api internal `client` field from RequestInit spread to avoid conflict with Deno.HttpClient
- Add test:deno npm script using --unstable-sloppy-imports and --preload
- Add test-typescript-client-deno CI job using denoland/setup-deno@v2 (v2.x)
- Update docs: rename page to TypeScript / JavaScript Client, add Deno installation section
* feat: add Deno compatibility to ai-sdk and chat integrations
- Switch ai-sdk and chat builds from tsc to tsup (ESM bundle, eliminates
extension-less import issues in Deno)
- Add deno.json import map to ai-sdk redirecting 'vitest' to a custom
vitest-compat.ts shim and bare npm specifiers to npm: URLs
- Add vitest-compat.ts shim implementing vi.fn()/vi.spyOn()/vi.mocked()
using @std/expect's Symbol.for("@MOCK") interface so toHaveBeenCalledWith
and other mock matchers work under Deno
- Add test:deno script to ai-sdk (all 30 tests pass under Deno)
* ci: add Deno test job for ai-sdk integration
Adds a new test-ai-sdk-integration-deno CI job that runs the ai-sdk
unit tests under Deno LTS, verifying Deno compatibility of the package.
* fix: remove broken link to non-existent n8n blog post in streamlit post
* fix: patch client.gen.ts for Deno compatibility during generation
Add a post-generation patch step to generate-clients.sh that removes
the hey-api internal 'client' field from the RequestInit spread in
client.gen.ts. Deno's Request constructor rejects 'client' because it
conflicts with the Deno.HttpClient option name.
* feat: add Agno integration with Hindsight memory toolkit
Add hindsight-agno package providing Hindsight memory tools (retain,
recall, reflect) as an Agno Toolkit, following the same pattern as
Agno's Mem0Tools. Includes per-user bank isolation, global config,
bank auto-creation, and memory_instructions() for system prompt
injection.
Also adds cookbook documentation page with architecture diagrams,
quick start examples, and configuration reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove n8n blog post, add Agno icon, bind to release process
- Remove n8n blog post from the agno integration branch
- Add Agno logo icon and map hindsight-agno SDK tag in CookbookGrid
- Add hindsight-agno to release.sh PYTHON_PACKAGES array
- Add build, publish, artifact upload, and release asset steps in release.yml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove cookbook page (moved to hindsight-cookbook repo)
The Agno cookbook application now lives in
vectorize-io/hindsight-cookbook/applications/agno-memory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add hindsight-hermes integration for Hermes Agent
* chore: add Hermes docs page, icon, and release process bindings
- Add cookbook page for Hermes integration (synced with README)
- Add Hermes icon and map hindsight-hermes SDK tag in CookbookGrid
- Add cookbook entry to index.mdx
- Add hindsight-hermes to release.sh PYTHON_PACKAGES array
- Add build, publish, artifact upload, and release asset steps in release.yml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update openclaw tests to use before_prompt_build hook and split doc-examples CI per language
- Update hooks.integration.test.ts: rename describe block and all
triggerHook calls from 'before_agent_start' to 'before_prompt_build'
to match the hook registered in index.ts (changed in PR #480)
- Fix 'includes the user message' test: prependContext contains memories
(bullet list), not the raw user query; update assertion accordingly
- Split test-doc-examples CI job into a matrix over [python, node, cli, go]
so each language runs in parallel; language-specific setup steps
(Rust/CLI build, Node.js, Python client, TypeScript client) are
conditional on matrix.language to avoid unnecessary work
* fix: spy on HindsightClient prototype to intercept all per-bank client instances
getClientForContext creates new HindsightClient instances per bank when
dynamicBankId is true, so vi.spyOn(c, 'recall') on the default client
never captured calls. Spy on HindsightClient.prototype instead so all
dynamically created bank clients are intercepted.
* feat(openclaw): squash branch updates for fork PR
* revert(api): drop memory_engine query normalization from this PR
* fix(openclaw): harden hook isolation and sanitize recall logging
* chore(openclaw): gate missing-senderId notice behind debug logger
* fix(openclaw): address remaining PR review follow-ups
* fix(openclaw): address upstream review comments on isolation and tests
* feat(openclaw): prepend current timestamp to recalled memory context
* chore(openclaw): sync package-lock version to 0.4.14
* chore(openclaw): format recall timestamp as yyyy-mm-dd HH:MM
* feat(openclaw): add configurable recall context composition
- Add recallRoles config to filter which message roles are included in recall query context
- Add recallContextTurns to control how many user turns of prior context to include
- Add recallMaxQueryChars to cap composed query length
- Reduce default max_tokens from 2048 to 1024 for recall responses
- Update documentation and plugin schema with new configuration options
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): put latest user message at end of recall query, add debug to schema
- Reorder composed recall query so latest user message is at the bottom,
giving embedding models the most weight where it matters most
- Update truncateRecallQuery to trim oldest context lines first,
always preserving the suffix (priority instruction + latest message)
- Add debug flag to openclaw.plugin.json schema
- Update tests to reflect new query order
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): add verbose debug logging for recall/retain
- Log full recall query (not just first 50 chars)
- Log all raw recall results with scores and content before topK trimming
- Log retain transcript preview and document ID
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): strip sender metadata envelope from prior context in recall query
Prior context messages passed to composeRecallQuery contained raw OpenClaw
envelope blocks (Sender/untrusted metadata JSON) which were diluting the
semantic signal of the recall query. Strip them the same way extractRecallQuery
already does for the latest message.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): add debug log for event.messages at recall time
Helps diagnose why recallContextTurns > 1 may not show extra context
by logging message count and roles available in event.messages.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): strip sender metadata envelope from rawMessage before recall query extraction
The rawMessage from Telegram group chats arrives wrapped in a:
---
Sender (untrusted metadata):
```json {...}```
<actual message>
---
envelope. This wasn't being stripped before extractRecallQuery used it,
so the full envelope including JSON metadata was being sent as the recall
query, severely diluting semantic relevance.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): warn when recallContextTurns > 1 but event.messages is empty
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): read messages from event.context.sessionEntry.messages for recall and retain
event.messages was always empty — the actual conversation history is at
event.context.sessionEntry.messages. Fall back to event.messages for
backwards compatibility. This fixes recallContextTurns and retain both
being unable to see the conversation history.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): extract stripMetadataEnvelopes helper and apply to retain path
- Add shared stripMetadataEnvelopes() to strip OpenClaw sender/conversation
metadata blocks from message content in all paths (recall query extraction,
prior context composition, and retain transcript)
- This prevents metadata-polluted memories (name/sender ID facts) from being
stored and ensures recall queries contain clean user text only
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): strip metadata envelopes after channel envelope extraction too
The prompt format is: [ChannelName ...]\n<metadata envelope>\n<message>
After extracting content after [ChannelName], the metadata envelope was
still present. Now stripMetadataEnvelopes runs again after the channel
envelope extraction step.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): switch recall hook from before_agent_start to before_prompt_build
before_prompt_build runs after session load and has messages available,
enabling recallContextTurns to work correctly. before_agent_start runs
pre-session with no messages.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): move current time inside memory tag, simplify recall query format
- Move "Current time" line inside <hindsight_memories> so it's not exposed
to the recall search as part of the query context
- Remove RECALL_QUERY_PRIORITY_INSTRUCTION and "Latest user message:" label
from composed recall query — the raw message is more effective for
semantic search without the extra prompt noise
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): address PR review comments on bank ID fallback and memory leaks
- Add early return in deriveBankId when ctx is undefined, falling back
to static default bank instead of generating a placeholder-filled ID
- Remove unused RECALL_QUERY_PRIORITY_INSTRUCTION dead constant
- Evict from banksWithMissionSet when evicting from clientsByBankId
to prevent unbounded memory growth in long-running instances
- Fix integration test hook name: before_agent_start → before_prompt_build
- Fix integration test assertions to match actual composeRecallQuery output
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): extract sender ID from inbound metadata blocks for bank ID derivation
Agent-phase hooks (before_prompt_build, agent_end) don't carry senderId in ctx
by design. Parse it from the "Conversation info / Sender (untrusted metadata)"
JSON blocks that OpenClaw injects into the prompt/messages instead.
- Add extractSenderIdFromText() helper that scans all metadata blocks and
returns the first sender_id / id field found
- before_prompt_build: extract from event.prompt/rawMessage, spread into ctx
before calling deriveBankId and getClientForContext
- agent_end: scan user messages for the metadata block, spread into effectiveCtx
before calling deriveBankId and getClientForContext
- Gracefully skipped when senderId is already present in ctx
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): scan messages from end for sender ID to handle group chats
When multiple users have spoken in a session, scanning from the front
returns the first sender in history rather than the one who triggered
the current agent run. Reverse the slice before finding so we always
pick the most recent user message's sender ID.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): use event.messages for sender ID in agent_end, not sessionEntry
sessionEntry.messages is the cleaned-up history without OpenClaw's injected
metadata prefix blocks. event.messages is the raw payload that still contains
the "Conversation info (untrusted metadata)" JSON — so parse sender_id from
there instead.
Also removes the unnecessary senderIdBySession cache added in the previous
attempt, since event.messages has everything needed directly.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): cache sender ID from before_prompt_build for use in agent_end
event.prompt in before_prompt_build contains OpenClaw's injected metadata
blocks with sender_id. event.messages in agent_end is clean history without
them — so parsing messages in agent_end never finds a sender ID.
Fix: cache the resolved sender ID (keyed by sessionKey) when it's extracted
in before_prompt_build, then look it up by sessionKey in agent_end.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* docs(openclaw): revert Auto-Recall token count to 1024 as unchanged from main
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): revert recallMaxTokens default from 2048 to 1024 to match main
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace ~73 console.log calls with a debug() helper that is silent by default.
Debug output is now controlled via plugin config param (debug: true) instead of
environment variables, making it easier for users to configure.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Pydantic AI integration for persistent agent memory
Adds hindsight-pydantic-ai package providing Hindsight-backed memory
tools for Pydantic AI agents. Since Pydantic AI is async-native, tools
use the hindsight-client async API directly (no thread-pool compat layer).
- create_hindsight_tools(): factory returning retain/recall/reflect Tool instances
- memory_instructions(): auto-injects relevant memories via Agent instructions
- Global configure()/get_config()/reset_config() following existing integration pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* doc: add README for Pydantic AI integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: zeroentropy rerank URL missing /v1 prefix and MCP routing tests
- Fix ZeroEntropy reranker URL: /models/rerank -> /v1/models/rerank (#453)
- Fix test_mcp_routing tests: update assertions to use submit_async_retain
instead of the non-existent async_processing=False/retain_batch_async pattern
* fix(openclaw): pass retainEveryNTurns through getPluginConfig and set it to 1 in tests
getPluginConfig was not forwarding retainEveryNTurns from the raw config,
so pluginConfig.retainEveryNTurns was always undefined (defaulting to 10).
The integration tests use retainEveryNTurns: 1 so retain fires every turn.
Adds @vectorize-io/hindsight-chat, a wrapper for the Vercel Chat SDK
that gives any chat bot (Slack, Discord, Teams, etc.) long-term memory
via Hindsight. Includes withHindsightChat() handler wrapper with
auto-recall, auto-retain, and memoriesAsSystemPrompt() formatting.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The checkExternalApiHealth function didn't include the Bearer token
in its requests. When the Hindsight API requires authentication
(HINDSIGHT_API_TENANT_API_KEY), health checks would fail with 401/403,
preventing plugin initialization.
Pass apiToken to all checkExternalApiHealth call sites and include
the Authorization header when a token is configured.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add `autoRecall` config option (default: true) to allow disabling
automatic memory recall injection when the host agent has its own
dedicated recall tool. This is backward compatible — existing
deployments continue auto-recalling as before.
Also add the existing `excludeProviders` field to the plugin.json
configSchema so it appears in the UI and docs.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add CrewAI integration for persistent crew memory
Implements a CrewAI ExternalMemory storage backend that maps CrewAI's
Storage interface (save/search/reset) to Hindsight's retain/recall/delete
APIs, giving crews long-term memory with fact extraction, entity tracking,
and temporal awareness across runs.
Key features:
- HindsightStorage: drop-in Storage backend for CrewAI ExternalMemory
- HindsightReflectTool: BaseTool exposing Hindsight's reflect API
- Per-agent memory banks with customizable bank resolver
- Async compatibility layer for CrewAI's threading model
- 35 unit tests, docs site page, example script
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move CrewAI example to hindsight-cookbook
Move research_crew.py example from hindsight-integrations/crewai/examples/
to the cookbook repo and update the integration README to link there instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add GitHub Actions test job for CrewAI integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add uv.lock for frozen installs in CI
The test-crewai-integration CI job uses `uv sync --frozen` which
requires a committed lock file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: improve openclaw test coverage
* test(openclaw): export stripMemoryTags/extractRecallQuery and add hook integration tests
- Extract stripMemoryTags and extractRecallQuery as exported pure functions
from index.ts so hooks share one implementation and tests cover the real code
- Update before_agent_start to call extractRecallQuery; update agent_end to
call stripMemoryTags instead of duplicating the regex inline
- Rewrite index.test.ts to import the real functions (no more local duplicate)
and add 11 tests for extractRecallQuery covering all envelope-stripping cases
- Add tests/hooks.integration.test.ts: loads the plugin via mock MoltbotPluginAPI
in HTTP mode, spies on client.recall/retain, and exercises all hook behaviours:
excluded providers, short messages, memory injection format, tag stripping,
transcript formatting, array content blocks, metadata, document_id derivation
- exec→execFile: bypass shell entirely, preventing injection via
special characters in chat history
- HTTP dual-mode: client can now talk directly to the Hindsight API
via HTTP (setBankMission, retain, recall) when apiUrl is configured,
bypassing the subprocess/CLI entirely for production deployments
- HindsightClientOptions: replace 5 positional constructor args with
a typed options object for clarity and extensibility
- sanitize(): strip null bytes from strings — Node 22 rejects them
in execFile() args
- recall timeout: accept optional timeoutMs parameter for both HTTP
and subprocess modes; subprocess gets a longer 30s default
- In-flight recall dedup: concurrent recalls for the same bank reuse
one promise instead of firing duplicate requests
- Timeout/abort handling: graceful warn-level logging instead of
error spam when recall times out
- Error cause chaining: wrap errors with { cause } for better
debugging stack traces
- lazyReinit: recover from startup health check failure with 30s
cooldown and concurrency guard
- Per-user banks: derive bank ID from senderId (not channelId) for
proper memory isolation per user across channels
- buildClientOptions(): centralized helper replaces 7 duplicated
constructor call sites
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused `fs` and `execSync` imports from `embed-manager.ts`
- Remove unused `join` import from `index.ts`
- Add retry logic to external API health check (3 attempts, 2s delay) —
container DNS may not be ready on first boot
- Use ES2022 `{ cause: error }` for better error chain preservation
- Add `.catch(() => {})` to `initPromise` to suppress Node.js unhandled
rejection warnings (error is properly handled later in `service.start()`)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds an `excludeProviders` option to the OpenClaw plugin config that allows
users to specify message providers (e.g. 'telegram', 'discord') to exclude
from Hindsight memory recall and retention.
Closes#331
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Move the Supabase tenant extension into the hindsight-api package so users
can enable it with just an environment variable — no file copying or Docker
image modifications needed.
Key improvements over the original submission:
- JWKS-based local JWT verification (no network call per request) with
automatic fallback to /auth/v1/user for legacy HS256 projects
- Service key is now optional (only needed for HS256 or health checks)
- UUID validation on user IDs before schema name construction
- Schema prefix validation against Postgres identifier rules
- Key rotation handling with automatic JWKS cache refresh
- Proper logging via Python logging module
- Tenant extension lifecycle hooks (on_startup/on_shutdown) wired into
the server lifespan
- Public tenant_extension property on MemoryEngine
- 54 unit tests covering both verification modes, cache behavior, error
paths, and the extension loader
- README updated to reflect JWKS-first architecture
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use unique document_id per conversation (sessionKey + timestamp) instead
of static sessionKey. The backend CASCADE-deletes old memories when the
same document_id is reused, causing all prior facts to be lost.
Also:
- Universal envelope stripping for all channels (was Telegram-only)
- Prefer rawMessage over prompt for cleaner recall queries
- Increase recall max_tokens from 512 to 2048
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remove `format: "uri"` from hindsightApiUrl schema property.
OpenClaw's schema validator uses Ajv without ajv-formats loaded, causing:
unknown format "uri" ignored in schema at path "#/properties/hindsightApiUrl"
The URI validation isn't critical since invalid URLs will fail at connection time.
This removes the warning without affecting functionality.
* docs: add AI SDK integration documentation
- Add comprehensive AI SDK documentation in docs/sdks/integrations/ai-sdk.md
- Detailed description of all three memory tools (retain, recall, reflect)
- Complete parameter documentation and return types
- Advanced usage patterns (streaming, multi-user, ToolLoopAgent)
- HTTP client example for zero-dependency usage
- TypeScript types and API reference
- Best practices and system prompt examples
- Update AI SDK README to brief quickstart with link to docs
- Single source of truth: comprehensive docs in documentation site
- README now focuses on quick setup and points to full docs
- Maintains features list and basic example for npm page
* fix
* feat: ai sdk integration
* more fixes
* fix(security): mental model refresh tag-based security
- Mental model refresh now passes tags with all_strict matching
- Consolidation only triggers refresh for mental models with matching tags
- Consolidation filters related observations by tags (all_strict)
- Added tests to verify tag-based security boundaries
- Updated OpenAPI spec to include tags and text_preview in list_documents
- Added tags column to documents UI table
* chore: regenerate OpenAPI spec after rebase
* fix: improve consolidation prompt for contradiction handling and mental model refresh security
- Enhanced consolidation prompt to be more explicit about capturing temporal changes in contradictions
- Fixed mental model refresh security: tagged memories now only trigger refresh of mental models with matching tags
- Added stricter tag filtering to prevent cross-scope mental model refreshes
Fixes test_consolidation_merges_contradictions by improving LLM instructions to use temporal markers like "used to X, now Y" when merging contradictory facts.
Note: test_refresh_with_tags_only_accesses_same_tagged_models still needs investigation - REFLECT operation may need additional tag filtering.
* fix: mental model refresh security - proper tag filtering in search
Fixed tool_search_mental_models to properly handle all_strict tag matching mode by using the centralized build_tags_where_clause function. Previously, the function only handled "all" vs "any" modes and always included untagged mental models when using non-"all" modes.
This ensures that when a tagged mental model is refreshed with all_strict matching, it cannot access untagged mental models, preventing cross-scope information leakage.
Fixes test_refresh_with_tags_only_accesses_same_tagged_models.
Note: test_sensory_dimension_preservation is failing but this is a pre-existing issue on main branch - the LLM model (gpt-oss-20b) is not extracting facts from sensory text. Not related to security changes.
* chore: apply formatting from pre-commit hook
* fix: allow untagged mental models to be refreshed by any consolidation
Untagged mental models are considered "global" and should be refreshed
by any consolidation, regardless of whether tagged or untagged memories
were consolidated. This maintains security boundaries while allowing
global mental models to stay fresh.
When tagged memories are consolidated:
- Refresh mental models with matching tags (security boundary)
- Also refresh untagged mental models (they're global)
- DO NOT refresh mental models with different tags
When untagged memories are consolidated:
- Only refresh untagged mental models
- DO NOT refresh tagged mental models (security boundary)
Fixes test_consolidation_only_refreshes_matching_tagged_models.
Add support for per-channel memory isolation in OpenClaw plugin.
Each channel (Slack, Telegram, Discord, etc.) gets its own memory bank,
preventing memory leakage between channels.
Changes:
- Add deriveBankId() to create channel-specific bank IDs
- Bank ID format: {messageProvider}-{channelId} (e.g., slack-C123)
- Add getClientForContext() for context-aware client access
- Update hook handlers to (event, ctx) signature
- Set bank mission on first use per dynamic bank
- Add dynamicBankId and bankIdPrefix config options
Configuration:
- dynamicBankId: true (default) enables per-channel isolation
- bankIdPrefix: optional prefix for namespacing (e.g., "prod")
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add support for connecting to an external Hindsight API instead of
starting a local daemon. This enables:
- Shared memory across multiple OpenClaw instances
- Centralized Hindsight deployment (e.g., on GKE)
- Reduced resource usage (no local daemon per instance)
Configuration:
- HINDSIGHT_EMBED_API_URL env var or hindsightApiUrl in plugin config
- HINDSIGHT_EMBED_API_TOKEN env var or hindsightApiToken for auth
When external API is configured:
- Skip local daemon startup
- Health check external API on startup
- Pass API URL/token to CLI commands via env vars
Falls back to local daemon mode when not configured.
Add comprehensive shell argument escaping using POSIX single-quote method.
Problem:
- Current code only escapes single quotes inline
- Other shell metacharacters ($, `, !, etc.) not explicitly handled
- Document ID in retain() was not escaped
Solution:
- Add exported escapeShellArg() function using POSIX single-quote escaping
- Replace inline escaping with shared function
- Escape document ID in retain()
- Add comprehensive tests (17 test cases) covering all shell-special chars
The POSIX single-quote method handles ALL shell metacharacters by wrapping
in single quotes (which protect everything except single quotes themselves)
and escaping any embedded single quotes with '\'' sequence.