fleet-memory/hindsight-docs/docs/sdks/integrations/openclaw.md
Tian Z d425e93cb4
feat(openclaw): v2 recall/retention controls, scalability fixes, and Gemini safety settings (#480)
* 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>
2026-03-05 16:55:16 +01:00

11 KiB

sidebar_position
4

OpenClaw

Local, long term memory for OpenClaw agents using Hindsight.

This plugin integrates hindsight-embed, a standalone daemon that bundles Hindsight's memory engine (API + PostgreSQL) into a single command. Everything runs locally on your machine, reuses the LLM you're already paying for, and costs nothing extra.

Quick Start

Step 1: Set up LLM for memory extraction

Choose one provider and set its API key:

# Option A: OpenAI (uses gpt-4o-mini for memory extraction)
export OPENAI_API_KEY="sk-your-key"

# Option B: Anthropic (uses claude-3-5-haiku for memory extraction)
export ANTHROPIC_API_KEY="your-key"

# Option C: Gemini (uses gemini-2.5-flash for memory extraction)
export GEMINI_API_KEY="your-key"

# Option D: Groq (uses openai/gpt-oss-20b for memory extraction)
export GROQ_API_KEY="your-key"

# Option E: Claude Code (uses claude-sonnet-4-20250514, no API key needed)
export HINDSIGHT_API_LLM_PROVIDER=claude-code

# Option F: OpenAI Codex (uses gpt-4o-mini, no API key needed)
export HINDSIGHT_API_LLM_PROVIDER=openai-codex

Step 2: Install the plugin

openclaw plugins install @vectorize-io/hindsight-openclaw

Step 3: Start OpenClaw

openclaw gateway

The plugin will automatically:

  • Start a local Hindsight daemon (port 9077)
  • Capture conversations after each turn
  • Inject relevant memories before agent responses

Important: The LLM you configure above is only for memory extraction (background processing). Your main OpenClaw agent can use any model you configure separately.

How It Works

Auto-Capture: Every conversation is automatically stored after each turn. Facts, entities, and relationships are extracted in the background.

Auto-Recall: Before each agent response, relevant memories are automatically injected into the context (up to 1024 tokens). The agent uses past context without needing to call tools.

Feedback Loop Prevention: The plugin automatically strips injected memory tags (<hindsight_memories>) before storing conversations. This prevents recalled memories from being re-extracted as new facts, which would cause exponential memory growth and duplicate entries.

Traditional memory systems give agents a search_memory tool - but models don't use it consistently. Auto-recall solves this by injecting memories automatically before every turn.

Configuration

Plugin Settings

Optional settings in ~/.openclaw/openclaw.json:

{
  "plugins": {
    "entries": {
      "hindsight-openclaw": {
        "enabled": true,
        "config": {
          "apiPort": 9077,
          "daemonIdleTimeout": 0,
          "embedVersion": "latest"
        }
      }
    }
  }
}

Options:

  • apiPort - Port for the openclaw profile daemon (default: 9077)
  • daemonIdleTimeout - Seconds before daemon shuts down from inactivity (default: 0 = never)
  • embedVersion - hindsight-embed version (default: "latest")
  • bankMission - 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 — not a recall prompt.
  • dynamicBankId - Enable per-context memory banks (default: true)
  • bankIdPrefix - Optional prefix for bank IDs (e.g. "prod""prod-slack-C123")
  • dynamicBankGranularity - Fields used to derive bank ID: agent, channel, user, provider (default: ["agent", "channel", "user"])
  • excludeProviders - Message providers to skip for recall/retain (e.g. ["slack"], ["telegram"], ["discord"])
  • autoRecall - Auto-inject memories before each turn (default: true). Set to false when the agent has its own recall tool.
  • autoRetain - Auto-retain conversations after each turn (default: true)
  • retainRoles - Which message roles to retain (default: ["user", "assistant"]). Options: user, assistant, system, tool
  • recallBudget - Recall effort: "low", "mid", or "high" (default: "mid"). Higher budgets use more retrieval strategies for better results.
  • recallMaxTokens - Max tokens for recall response (default: 1024). Controls how much memory context is injected per turn.

Memory Isolation

The plugin creates separate memory banks based on conversation context. By default, banks are derived from the agent, channel, and user fields — so each unique combination gets its own isolated memory store.

You can customize which fields are used for bank segmentation with dynamicBankGranularity:

{
  "plugins": {
    "entries": {
      "hindsight-openclaw": {
        "enabled": true,
        "config": {
          "dynamicBankGranularity": ["provider", "user"]
        }
      }
    }
  }
}

In this example, memories are isolated per provider + user, meaning the same user shares memories across all channels within a provider.

Available isolation fields:

  • agent - The agent/bot identity
  • channel - The channel or conversation ID
  • user - The user interacting with the agent
  • provider - The message provider (e.g. Slack, Discord)

Use bankIdPrefix to namespace bank IDs across environments (e.g. "prod", "staging"). Set dynamicBankId to false to use a single shared bank for all conversations.

Retention Controls

By default, the plugin retains user and assistant messages after each turn. You can customize this behavior:

{
  "plugins": {
    "entries": {
      "hindsight-openclaw": {
        "enabled": true,
        "config": {
          "autoRetain": true,
          "retainRoles": ["user", "assistant", "system"]
        }
      }
    }
  }
}
  • autoRetain - Set to false to disable automatic retention entirely (useful if you handle retention yourself)
  • retainRoles - Controls which message roles are included in the retained transcript. Only messages from the last user message onward are retained each turn, preventing duplicate storage.

LLM Configuration

The plugin auto-detects your LLM provider from these environment variables:

Provider Env Var Default Model Notes
OpenAI OPENAI_API_KEY gpt-4o-mini
Anthropic ANTHROPIC_API_KEY claude-3-5-haiku-20241022
Gemini GEMINI_API_KEY gemini-2.5-flash
Groq GROQ_API_KEY openai/gpt-oss-20b
Claude Code HINDSIGHT_API_LLM_PROVIDER=claude-code claude-sonnet-4-20250514 No API key needed
OpenAI Codex HINDSIGHT_API_LLM_PROVIDER=openai-codex gpt-4o-mini No API key needed

Override with explicit config:

export HINDSIGHT_API_LLM_PROVIDER=openai
export HINDSIGHT_API_LLM_MODEL=gpt-4o-mini
export HINDSIGHT_API_LLM_API_KEY=sk-your-key

# Optional: custom base URL (OpenRouter, Azure, vLLM, etc.)
export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1

Example: Free OpenRouter model

export HINDSIGHT_API_LLM_PROVIDER=openai
export HINDSIGHT_API_LLM_MODEL=xiaomi/mimo-v2-flash  # FREE!
export HINDSIGHT_API_LLM_API_KEY=sk-or-v1-your-openrouter-key
export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1

External API (Advanced)

Connect to a remote Hindsight API server instead of running a local daemon. This is useful for:

  • Shared memory across multiple OpenClaw instances
  • Production deployments with centralized memory storage
  • Team environments where agents share knowledge

Plugin Configuration

Configure in ~/.openclaw/openclaw.json:

{
  "plugins": {
    "entries": {
      "hindsight-openclaw": {
        "enabled": true,
        "config": {
          "hindsightApiUrl": "https://your-hindsight-server.com",
          "hindsightApiToken": "your-api-token"
        }
      }
    }
  }
}

Options:

  • hindsightApiUrl - Full URL to external Hindsight API (e.g., https://mcp.hindsight.example.com)
  • hindsightApiToken - API token for authentication (optional, only if API requires auth)

Environment Variables (Alternative)

You can also configure via environment variables:

export HINDSIGHT_EMBED_API_URL=https://your-hindsight-server.com
export HINDSIGHT_EMBED_API_TOKEN=your-api-token  # Optional

openclaw gateway

Note: Plugin config takes precedence over environment variables.

Behavior

When external API mode is enabled:

  • No local daemon is started (no hindsight-embed process)
  • Health check runs on startup to verify API connectivity
  • All memory operations (retain, recall, reflect) go to the external API
  • Faster startup since no local PostgreSQL or embedding models are needed

Verification

Check OpenClaw logs for external API mode:

tail -f /tmp/openclaw/openclaw-*.log | grep Hindsight

# Should see on startup:
# [Hindsight] External API mode enabled: https://your-hindsight-server.com
# [Hindsight] External API health check passed

If you see daemon startup messages instead, verify your configuration is correct.

Inspecting Memories

Check Configuration

View the daemon config that was written by the plugin:

cat ~/.hindsight/profiles/openclaw.env

This shows the LLM provider, model, port, and other settings the daemon is using.

Check Daemon Status

# Check if daemon is running
uvx hindsight-embed@latest -p openclaw daemon status

# View daemon logs
tail -f ~/.hindsight/profiles/openclaw.log

Query Memories

# Search memories
uvx hindsight-embed@latest -p openclaw memory recall openclaw "user preferences"

# View recent memories
uvx hindsight-embed@latest -p openclaw memory list openclaw --limit 10

# Open web UI (uses openclaw profile's daemon)
uvx hindsight-embed@latest -p openclaw ui

Troubleshooting

Plugin not loading

openclaw plugins list | grep hindsight
# Should show: ✓ enabled │ Hindsight Memory │ ...

# Reinstall if needed
openclaw plugins install @vectorize-io/hindsight-openclaw

Daemon not starting

# Check daemon status (note: -p openclaw uses the openclaw profile)
uvx hindsight-embed@latest -p openclaw daemon status

# View logs for errors
tail -f ~/.hindsight/profiles/openclaw.log

# Check configuration
cat ~/.hindsight/profiles/openclaw.env

# List all profiles
uvx hindsight-embed@latest profile list

No API key error

Make sure you've set one of the provider API keys (or use a provider that doesn't require one):

# Option 1: OpenAI
export OPENAI_API_KEY="sk-your-key"

# Option 2: Anthropic
export ANTHROPIC_API_KEY="your-key"

# Option 3: Claude Code (no API key needed)
export HINDSIGHT_API_LLM_PROVIDER=claude-code

# Option 4: OpenAI Codex (no API key needed)
export HINDSIGHT_API_LLM_PROVIDER=openai-codex

# Verify it's set
echo $OPENAI_API_KEY
# or
echo $HINDSIGHT_API_LLM_PROVIDER

Verify it's working

Check gateway logs for memory operations:

tail -f /tmp/openclaw/openclaw-*.log | grep Hindsight

# Should see on startup:
# [Hindsight] ✓ Using provider: openai, model: gpt-4o-mini
# or
# [Hindsight] ✓ Using provider: claude-code, model: claude-sonnet-4-20250514

# Should see after conversations:
# [Hindsight] Retained X messages for session ...
# [Hindsight] Auto-recall: Injecting X memories