Skip to content

Recalled memory containing {{ ... }} breaks system prompt assembly (malformed prompt variable reference "{{ }}") #80

Description

@vongostev

Summary

When Graph Memory recall returns a node (or an episodic message) whose content contains {{, assembling the system prompt fails with:

malformed prompt variable reference "{{ }}" in context "graph-memory:recall" (variable names match /^[a-z][a-z0-9_]*$/)

The failure is data-dependent: it only happens once a stored node/message containing {{...}} is recalled into context, and it fails the whole turn, not just the recall.

Environment

  • Plugin: graph-memory 1.6.0-beta.1, commit dbd72cc (docs: relaunch Graph Memory for DeepSeek Harness)
  • Host: DeepSeek Harness (DSH) --profile web, Node 25, macOS
  • Store: SQLite (~/.dsh/graph-memory/graph-memory.db)

Steps to reproduce

  1. Have a node in the graph whose content (or a stored conversation message) contains {{ ... }} — in our case an EVENT node documenting a ${{ ... }} workaround:
    现象: Error: ... 只要代码中含 ${{ ... }} 就报错
    
  2. Start a session whose query recalls that node (semantic recall + active-session nodes).
  3. The system-prompt/assemble handler pushes the assembled text as context graph-memory:recall, and DSH's prompt interpolator throws the error above; the turn fails.

Root cause

assembleContext() in src/format/assemble.ts embeds recalled data into the prompt context with only XML escaping:

  • node name / descriptionescapeXml(...) (escapes & < > " only),
  • node content → inserted raw,
  • edge instruction / conditionescapeXml(...),
  • episodic trace lines [ROLE] <text>escapeXml(...).

None of these neutralize {, so any {{...}} from memory reaches the prompt text.

On the harness side, @deepseek-ai/dsh-system-prompt treats every {{ in a contributed context as a variable group: it parses {{<name>}} and requires <name> to match ^[a-z][a-z0-9_]*$ and to be a registered variable, otherwise it throws (malformed reference / invalid name / unknown variable). So not only malformed groups like {{ }} fail — even well-formed-looking {{ foo }} from memory would fail with unknown prompt variable.

Memory content is untrusted historical data (the plugin itself labels it "untrusted reference material"), so it must be neutralized before being embedded into a prompt-templating context.

Suggested fix

Neutralize {{ / }} in all user-derived strings before embedding them into the assembled prompt, e.g.:

function neutralizePromptBraces(s: string): string {
  return s.replace(/\{\{/g, "{ {").replace(/\}\}/g, "} }");
}

Apply it to node name / description / content, edge instruction / condition, and episodic message text in src/format/assemble.ts. That covers both callers: dsh.ts (DSH context graph-memory:recall) and index.ts (OpenClaw systemPromptAddition).

I verified this locally: with the patch, a node containing ${{ ... }} assembles into the context without any {{, the harness interpolation no longer throws, and the full plugin test suite (107 tests, incl. test/assemble.test.ts) passes.

Regression test suggestion

Add a unit test in test/assemble.test.ts that feeds a node whose content contains {{ ... }} and asserts the assembled XML contains no {{ group.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions