Skip to content

Commit aa308ad

Browse files
fix(openclaw): strip runtime metadata from memory content (#1968)
* feat(openclaw): pass retain context guidance to prevent routing metadata misattribution Hindsight's fact extraction LLM was misinterpreting routing identifiers (sender open_id, bank ID, channel, provider) as semantic actors, project names, or organizations. After many conversation turns, the bank name (e.g. saber-prod) would override the actual project being discussed (e.g. x-power-cli). This adds interpretation guidance via the retain API 'context' field: - New DEFAULT_RETAIN_CONTEXT constant explains that [context] block sender/channel/provider are routing identifiers, not human names - Bank IDs, session keys, agent IDs, thread IDs, and tags are also marked as operational routing identifiers, not project names - Assistant-role first-person statements are attributed to the AI - Context is passed through the full chain: buildRetainRequest → scopeClient.retain → Hindsight SDK API - RetainQueue persists and flushes context correctly - Backfill CLI also passes context - New 'retainContext' config option allows customization includeSenderContext behavior is unchanged; the [context] block remains in transcript content, but extraction LLM now knows how to interpret it. 7 files changed, 97 insertions(+). * fix(openclaw): remove platform-specific examples from DEFAULT_RETAIN_CONTEXT * test(openclaw): harden retain context handling * fix(openclaw): strip runtime metadata from memory content * refactor(openclaw): remove dead session-context surface Following the removal of transcript context-prepending, drop the now-unused formatRetentionSessionContext / RetentionSessionContext and the ignored prepareRetentionTranscript session-context parameter (and the discarded object built at the live call site). Remove the inert includeSenderContext config option (no longer read) from the type, manifest schema, and UI label. Collapse the session-context tests to two regression guards asserting that retained JSON/text content carries no context header. --------- Co-authored-by: Nicolò Boschi <boschi1997@gmail.com>
1 parent cb73790 commit aa308ad

8 files changed

Lines changed: 346 additions & 123 deletions

File tree

hindsight-integrations/openclaw/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh
9595
| `bankIdPrefix` || Prefix for bank IDs (e.g. `"prod"`) |
9696
| `retainTags` | `[]` | Tags applied to every retained document, useful for cross-agent/source labeling (e.g. `source_system:openclaw`, `agent:agentname`). Auto-retain also merges inline per-message tags from `<retain_tags>...</retain_tags>` or `<hindsight_retain_tags>...</hindsight_retain_tags>` blocks in user messages. |
9797
| `retainSource` | `"openclaw"` | `source` value written into retained document metadata |
98+
| `retainContext` | built-in OpenClaw guidance | Interpretation guidance sent through the Hindsight retain API `context` field. The default tells the extraction LLM that sender/channel/provider metadata, bank IDs, session keys, source systems, and tags are operational routing metadata, not human names, project names, or organizations. |
9899
| `dynamicBankGranularity` | `["agent", "channel", "user"]` | Fields used to derive bank ID. Options: `agent`, `channel`, `user`, `provider` |
99100
| `excludeProviders` | `["heartbeat"]` | Message providers to skip for recall/retain (e.g. `heartbeat`, `slack`, `telegram`, `discord`) |
100101
| `autoRecall` | `true` | Auto-inject memories before each turn. Set to `false` when the agent has its own recall tool. |
@@ -162,6 +163,8 @@ Glob syntax:
162163

163164
Retained documents use stable session-scoped IDs derived from the OpenClaw `sessionKey`. Every retain in a session shares one document id like `openclaw:agent:agentname:discord:channel:123`, so all turns of the conversation accumulate under a single Hindsight document (on legacy APIs without `update_mode: 'append'` support, the integration falls back to per-retain ids — `...:turn:000001`, `...:window:000002` for chunked retention — so prior turns aren't overwritten). Retained documents include richer metadata such as `session_key`, `agent_id`, `provider`, `channel_id`, `thread_id`, `sender_id`, `turn_index`, and `retention_scope`. Each message in the retained JSON also carries a structured `timestamp` field (ISO 8601) lifted from OpenClaw's per-message time, so facts are not polluted by inline weekday/date prefixes.
164165

166+
`retainContext` is sent separately from the transcript content and gives Hindsight's extraction LLM interpretation guidance for the retained document. The default is designed for OpenClaw transcripts: it explains that sender/channel/provider metadata is operational routing data, that assistant-role first-person statements belong to the AI assistant, and that bank IDs or tags should not be treated as the discussed project. Sender/channel/provider stay in retain request metadata/context and are not prepended into retained transcript content.
167+
165168
## Documentation
166169

167170
For full documentation, configuration options, troubleshooting, and development guide, see:

hindsight-integrations/openclaw/openclaw.plugin.json

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
"configContracts": {
66
"secretInputs": {
77
"paths": [
8-
{ "path": "llmApiKey", "expected": "string" },
9-
{ "path": "hindsightApiToken", "expected": "string" }
8+
{
9+
"path": "llmApiKey",
10+
"expected": "string"
11+
},
12+
{
13+
"path": "hindsightApiToken",
14+
"expected": "string"
15+
}
1016
]
1117
}
1218
},
@@ -112,6 +118,11 @@
112118
"description": "Source value written into retained document metadata. Defaults to 'openclaw'.",
113119
"default": "openclaw"
114120
},
121+
"retainContext": {
122+
"type": "string",
123+
"description": "Interpretation guidance sent via the Hindsight retain API context field. It tells the extraction LLM that OpenClaw sender/channel/provider values, bank IDs, session keys, source systems, and tags are operational routing metadata, not human names, project names, or organizations.",
124+
"default": "This content is an AI-assistant conversation transcript from OpenClaw. Retain request metadata may include routing identifiers such as 'sender_id' (an opaque user ID, not a human name), 'channel_id' (a chat identifier), and 'provider' (the messaging platform name). These are operational routing metadata, not semantic actors or people. Messages with role 'assistant' are from the AI assistant; first-person statements in assistant messages refer to the AI, not the human user. Messages with role 'user' are from the human user. Bank IDs, session keys, agent IDs, thread IDs, source systems, and tags in metadata are also operational routing identifiers, not human names, project names, or organizations."
125+
},
115126
"autoRecall": {
116127
"type": "boolean",
117128
"description": "Automatically recall memories on every prompt and inject them as context. Set to false when agent has its own recall tool.",
@@ -159,11 +170,6 @@
159170
"description": "When true (default) and retainFormat is 'json', each message's content is an Anthropic-shaped array of typed blocks including tool_use (agent tool calls) and tool_result (truncated at 2000 chars). Operational MCP tools — Hindsight's own recall/retain/search — are filtered out to avoid feedback loops. Set to false to retain text-only content, one string per message.",
160171
"default": true
161172
},
162-
"includeSenderContext": {
163-
"type": "boolean",
164-
"description": "When true (default), prepend a session context block (sender, channel, provider) to retained transcripts so similarity search can distinguish memories by speaker even when dynamicBankGranularity does not include 'user'. Set false to omit.",
165-
"default": true
166-
},
167173
"retainEveryNTurns": {
168174
"type": "integer",
169175
"description": "Retain every Nth turn instead of every turn. 1 = every turn (default). Values > 1 enable chunked retention with a sliding window.",
@@ -277,12 +283,16 @@
277283
},
278284
"ignoreSessionPatterns": {
279285
"type": "array",
280-
"items": { "type": "string" },
286+
"items": {
287+
"type": "string"
288+
},
281289
"description": "Session key glob patterns to skip entirely (no recall, no retain). E.g. [\"agent:main:**\", \"agent:*:cron:**\"]. * matches non-colon chars, ** matches anything."
282290
},
283291
"statelessSessionPatterns": {
284292
"type": "array",
285-
"items": { "type": "string" },
293+
"items": {
294+
"type": "string"
295+
},
286296
"description": "Session key glob patterns for read-only sessions: retain is always skipped, recall is skipped when skipStatelessSessions is true. E.g. [\"agent:*:subagent:**\", \"agent:*:heartbeat:**\"]."
287297
},
288298
"skipStatelessSessions": {
@@ -377,6 +387,10 @@
377387
"label": "Retain Source",
378388
"placeholder": "openclaw"
379389
},
390+
"retainContext": {
391+
"label": "Retain Context",
392+
"placeholder": "Guidance for interpreting retained OpenClaw transcripts"
393+
},
380394
"autoRecall": {
381395
"label": "Auto-Recall",
382396
"placeholder": "true (inject memories on every prompt)"
@@ -405,10 +419,6 @@
405419
"label": "Retain Tool Calls",
406420
"placeholder": "true (default) — include tool_use / tool_result blocks in retained JSON"
407421
},
408-
"includeSenderContext": {
409-
"label": "Include Sender Context",
410-
"placeholder": "true (default) — prepend sender/channel/provider context to retained content"
411-
},
412422
"retainEveryNTurns": {
413423
"label": "Retain Every N Turns",
414424
"placeholder": "1 (every turn, default)"

hindsight-integrations/openclaw/src/backfill.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,9 @@ describe("backfill helpers", () => {
197197
it("treats a symlinked bin path as direct execution", async () => {
198198
const { isDirectExecution } = await import("./backfill.js");
199199
const dir = mkdtempSync(join(tmpdir(), "hindsight-backfill-bin-"));
200-
const modulePath = join(process.cwd(), "dist", "backfill.js");
200+
const modulePath = join(dir, "backfill.js");
201201
const symlinkPath = join(dir, "hindsight-openclaw-backfill");
202+
writeFileSync(modulePath, "#!/usr/bin/env node\n", "utf8");
202203
symlinkSync(modulePath, symlinkPath);
203204

204205
const moduleUrl = pathToFileURL(modulePath).href;

hindsight-integrations/openclaw/src/backfill.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function loadPackageVersion(): string {
1717
}
1818

1919
const USER_AGENT = `hindsight-openclaw/${loadPackageVersion()}`;
20-
import { detectExternalApi, detectLLMConfig } from "./index.js";
20+
import { DEFAULT_RETAIN_CONTEXT, detectExternalApi, detectLLMConfig } from "./index.js";
2121
import type { BankStats, PluginConfig } from "./types.js";
2222
import {
2323
buildBackfillPlan,
@@ -547,6 +547,7 @@ export async function runCli(argv: string[] = process.argv.slice(2)): Promise<vo
547547
}
548548
await client.retain(entry.bankId, entry.transcript, {
549549
documentId: entry.documentId,
550+
context: pluginConfig.retainContext || DEFAULT_RETAIN_CONTEXT,
550551
metadata,
551552
async: true,
552553
});

0 commit comments

Comments
 (0)