* 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>
316 lines
11 KiB
JSON
316 lines
11 KiB
JSON
{
|
|
"id": "hindsight-openclaw",
|
|
"name": "Hindsight Memory",
|
|
"kind": "memory",
|
|
"configSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"daemonIdleTimeout": {
|
|
"type": "number",
|
|
"description": "Seconds before daemon shuts down from inactivity (0 = never)",
|
|
"default": 0
|
|
},
|
|
"embedPort": {
|
|
"type": "number",
|
|
"description": "Port for hindsight-embed server (auto-assigned if not specified)",
|
|
"default": 0
|
|
},
|
|
"bankMission": {
|
|
"type": "string",
|
|
"description": "Agent identity/purpose stored on the memory bank. Helps the memory engine understand context for better fact extraction during retain. Set once per bank on first use — this is not a recall prompt.",
|
|
"default": "You are an AI assistant helping users across multiple communication channels (Telegram, Slack, Discord, etc.). Remember user preferences, instructions, and important context from conversations to provide personalized assistance."
|
|
},
|
|
"embedVersion": {
|
|
"type": "string",
|
|
"description": "hindsight-embed version to use (e.g. 'latest', '0.4.2', or empty for latest)",
|
|
"default": "latest"
|
|
},
|
|
"llmProvider": {
|
|
"type": "string",
|
|
"description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code'). Takes priority over auto-detection but not over HINDSIGHT_API_LLM_PROVIDER env var.",
|
|
"enum": [
|
|
"openai",
|
|
"anthropic",
|
|
"gemini",
|
|
"groq",
|
|
"ollama",
|
|
"openai-codex",
|
|
"claude-code"
|
|
]
|
|
},
|
|
"llmModel": {
|
|
"type": "string",
|
|
"description": "LLM model to use (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022'). Used with llmProvider."
|
|
},
|
|
"llmApiKeyEnv": {
|
|
"type": "string",
|
|
"description": "Name of the env var holding the API key (e.g. 'MY_CUSTOM_KEY'). If not set, uses the standard env var for the chosen provider."
|
|
},
|
|
"embedPackagePath": {
|
|
"type": "string",
|
|
"description": "Local path to hindsight package for development (e.g. '/path/to/hindsight'). When set, uses 'uv run --directory <path>' instead of 'uvx hindsight-embed@latest'."
|
|
},
|
|
"apiPort": {
|
|
"type": "number",
|
|
"description": "Port for the openclaw profile daemon (default: 9077)",
|
|
"default": 9077
|
|
},
|
|
"hindsightApiUrl": {
|
|
"type": "string",
|
|
"description": "External Hindsight API URL (e.g. 'https://mcp.hindsight.devcraft.team'). When set, skips local daemon and connects directly to this API."
|
|
},
|
|
"hindsightApiToken": {
|
|
"type": "string",
|
|
"description": "API token for external Hindsight API authentication. Required if the external API has authentication enabled."
|
|
},
|
|
"dynamicBankId": {
|
|
"type": "boolean",
|
|
"description": "Enable per-user memory banks. When true, memories are isolated by user per channel (e.g., slack-U123, telegram-456789). When false, all users share a single 'openclaw' bank.",
|
|
"default": true
|
|
},
|
|
"bankIdPrefix": {
|
|
"type": "string",
|
|
"description": "Optional prefix for bank IDs (e.g., 'prod' results in 'prod-slack-U123'). Useful for separating environments."
|
|
},
|
|
"autoRecall": {
|
|
"type": "boolean",
|
|
"description": "Automatically recall memories on every prompt and inject them as context. Set to false when agent has its own recall tool.",
|
|
"default": true
|
|
},
|
|
"excludeProviders": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Message providers to exclude from recall and retain (e.g. ['telegram', 'discord'])"
|
|
},
|
|
"dynamicBankGranularity": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": [
|
|
"agent",
|
|
"channel",
|
|
"user",
|
|
"provider"
|
|
]
|
|
},
|
|
"description": "Fields used to derive bank ID. Controls memory isolation granularity. Default: ['agent', 'channel', 'user'].",
|
|
"default": [
|
|
"agent",
|
|
"channel",
|
|
"user"
|
|
]
|
|
},
|
|
"autoRetain": {
|
|
"type": "boolean",
|
|
"description": "Automatically retain conversation as memories after each interaction. Set to false to disable.",
|
|
"default": true
|
|
},
|
|
"retainRoles": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": [
|
|
"user",
|
|
"assistant",
|
|
"system",
|
|
"tool"
|
|
]
|
|
},
|
|
"description": "Message roles to include in retained transcript. Default: ['user', 'assistant'].",
|
|
"default": [
|
|
"user",
|
|
"assistant"
|
|
]
|
|
},
|
|
"retainEveryNTurns": {
|
|
"type": "integer",
|
|
"description": "Retain every Nth turn instead of every turn. 1 = every turn (default). Values > 1 enable chunked retention with a sliding window.",
|
|
"minimum": 1,
|
|
"default": 1
|
|
},
|
|
"retainOverlapTurns": {
|
|
"type": "integer",
|
|
"description": "Extra prior turns to include when chunked retention fires. Window = retainEveryNTurns + retainOverlapTurns. Only applies when retainEveryNTurns > 1.",
|
|
"minimum": 0,
|
|
"default": 0
|
|
},
|
|
"recallBudget": {
|
|
"type": "string",
|
|
"description": "Recall effort level. Higher budgets use more retrieval strategies for better results but take longer.",
|
|
"enum": ["low", "mid", "high"],
|
|
"default": "mid"
|
|
},
|
|
"recallMaxTokens": {
|
|
"type": "integer",
|
|
"description": "Maximum tokens for recall response. Controls how much memory context is injected per turn.",
|
|
"minimum": 1,
|
|
"default": 1024
|
|
},
|
|
"recallTypes": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": ["world", "experience", "observation"]
|
|
},
|
|
"description": "Memory types to recall. Defaults to ['world', 'experience'] — excludes verbose observation entries.",
|
|
"default": ["world", "experience"]
|
|
},
|
|
"recallRoles": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": ["user", "assistant", "system", "tool"]
|
|
},
|
|
"description": "Roles to include when composing contextual recall query. Default: ['user', 'assistant'].",
|
|
"default": ["user", "assistant"]
|
|
},
|
|
"recallContextTurns": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Number of user turns to include in recall query context. 1 keeps latest-message-only behavior.",
|
|
"default": 1
|
|
},
|
|
"recallMaxQueryChars": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Maximum character length for composed recall query before calling recall.",
|
|
"default": 800
|
|
},
|
|
"recallTopK": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Maximum number of memories to inject per turn. Applied after API response as a hard cap."
|
|
},
|
|
"recallPromptPreamble": {
|
|
"type": "string",
|
|
"description": "Text shown above recalled memories in the injected context block.",
|
|
"default": "Relevant memories from past conversations (prioritize recent when conflicting). Only use memories that are directly useful to continue this conversation; ignore the rest:"
|
|
},
|
|
"debug": {
|
|
"type": "boolean",
|
|
"description": "Enable debug logging for Hindsight plugin operations.",
|
|
"default": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
"uiHints": {
|
|
"daemonIdleTimeout": {
|
|
"label": "Daemon Idle Timeout",
|
|
"placeholder": "0 (never timeout)"
|
|
},
|
|
"embedPort": {
|
|
"label": "Embed Server Port",
|
|
"placeholder": "0 (auto-assign)"
|
|
},
|
|
"bankMission": {
|
|
"label": "Bank Mission",
|
|
"placeholder": "Custom context for what this agent does..."
|
|
},
|
|
"embedVersion": {
|
|
"label": "Hindsight Embed Version",
|
|
"placeholder": "latest (or pin to specific version like 0.4.2)"
|
|
},
|
|
"llmProvider": {
|
|
"label": "LLM Provider",
|
|
"placeholder": "e.g. openai, anthropic, gemini, groq"
|
|
},
|
|
"llmModel": {
|
|
"label": "LLM Model",
|
|
"placeholder": "e.g. gpt-4o-mini, claude-3-5-haiku-20241022"
|
|
},
|
|
"llmApiKeyEnv": {
|
|
"label": "API Key Env Var",
|
|
"placeholder": "e.g. MY_CUSTOM_API_KEY (optional)"
|
|
},
|
|
"embedPackagePath": {
|
|
"label": "Local Package Path (Dev)",
|
|
"placeholder": "/path/to/hindsight (for local development)"
|
|
},
|
|
"apiPort": {
|
|
"label": "API Port",
|
|
"placeholder": "9077 (default)"
|
|
},
|
|
"hindsightApiUrl": {
|
|
"label": "External Hindsight API URL",
|
|
"placeholder": "e.g. https://mcp.hindsight.devcraft.team (leave empty for local daemon)"
|
|
},
|
|
"hindsightApiToken": {
|
|
"label": "External API Token",
|
|
"placeholder": "API token if external API requires authentication"
|
|
},
|
|
"dynamicBankId": {
|
|
"label": "Dynamic Bank IDs",
|
|
"placeholder": "true (isolate memories per channel)"
|
|
},
|
|
"bankIdPrefix": {
|
|
"label": "Bank ID Prefix",
|
|
"placeholder": "e.g., prod, staging (optional)"
|
|
},
|
|
"autoRecall": {
|
|
"label": "Auto-Recall",
|
|
"placeholder": "true (inject memories on every prompt)"
|
|
},
|
|
"excludeProviders": {
|
|
"label": "Excluded Providers",
|
|
"placeholder": "e.g. telegram, discord"
|
|
},
|
|
"dynamicBankGranularity": {
|
|
"label": "Bank Granularity",
|
|
"placeholder": "e.g. ['agent', 'channel', 'user']"
|
|
},
|
|
"autoRetain": {
|
|
"label": "Auto-Retain",
|
|
"placeholder": "true (enable auto-retention)"
|
|
},
|
|
"retainRoles": {
|
|
"label": "Retain Roles",
|
|
"placeholder": "e.g. ['user', 'assistant']"
|
|
},
|
|
"retainEveryNTurns": {
|
|
"label": "Retain Every N Turns",
|
|
"placeholder": "1 (every turn, default)"
|
|
},
|
|
"retainOverlapTurns": {
|
|
"label": "Retain Overlap Turns",
|
|
"placeholder": "0 (no overlap, default)"
|
|
},
|
|
"recallBudget": {
|
|
"label": "Recall Budget",
|
|
"placeholder": "low, mid, or high"
|
|
},
|
|
"recallMaxTokens": {
|
|
"label": "Recall Max Tokens",
|
|
"placeholder": "1024 (default)"
|
|
},
|
|
"recallTypes": {
|
|
"label": "Recall Types",
|
|
"placeholder": "e.g. ['world', 'experience']"
|
|
},
|
|
"recallRoles": {
|
|
"label": "Recall Roles",
|
|
"placeholder": "e.g. ['user', 'assistant']"
|
|
},
|
|
"recallContextTurns": {
|
|
"label": "Recall Context Turns",
|
|
"placeholder": "1 (latest only, default)"
|
|
},
|
|
"recallMaxQueryChars": {
|
|
"label": "Recall Max Query Chars",
|
|
"placeholder": "800 (default)"
|
|
},
|
|
"recallTopK": {
|
|
"label": "Recall Top K",
|
|
"placeholder": "e.g. 5 (no limit by default)"
|
|
},
|
|
"recallPromptPreamble": {
|
|
"label": "Recall Prompt Preamble",
|
|
"placeholder": "Instruction shown above recalled memories in injected context"
|
|
},
|
|
"debug": {
|
|
"label": "Debug"
|
|
}
|
|
}
|
|
}
|