-
-
Notifications
You must be signed in to change notification settings - Fork 52.5k
Description
Summary
PR #20597 incomplete — message_id still injected into system prompt via buildInboundMetaSystemPrompt() in three unpatched bundle files
Steps to reproduce
-
Install OpenClaw 2026.2.19-2 (the version that includes the fix from PR fix(auto-reply): restore prompt cache stability by moving per-turn ids to user context #20597)
-
Configure a local model provider (LM Studio or llama-server)
-
Start the gateway: openclaw gateway restart
-
Start a fresh session and send a first message
-
Send a second message in the same session
-
Observe LM Studio logs — full prompt prefill from 0% occurs on every turn
-
Insert a logging proxy between OpenClaw and LM Studio that captures the
developer-role system prompt from each /v1/chat/completions request -
Diff turn 2 vs turn 3 — message_id will differ every time:
- "message_id": "6678",
- "message_id": "6680",
Expected behavior
After PR #20597, message_id should no longer appear in the system prompt. Consecutive turns in the same session should produce identical system prompts, allowing prefix-based KV cache reuse on local model providers (LM Studio/MLX, llama-server).
Actual behavior
message_id still changes on every turn in the system prompt (openclaw.inbound_meta.v1 JSON block). Full KV cache invalidation occurs on every message. PR #20597 only patched pi-embedded-Cn8f5u97.js — the file actually loaded by the gateway (subagent-registry-DOZpiiys.js) and two others remain unpatched.
OpenClaw version
2026.2.2019-2
Operating system
macOS 26.3
Install method
No response
Logs, screenshots, and evidence
Impact and severity
No response
Additional information
This report provides:
- A precise explanation of why PR fix(auto-reply): restore prompt cache stability by moving per-turn ids to user context #20597 did not fully solve the problem
- Instrumented proof via per-turn system prompt diffs
- The exact files and lines that require patching
- The patch itself, verified to resolve the issue
Why PR #20597 Did Not Solve the Issue
What the PR correctly identified
PR #20597 correctly diagnosed that commit bed8e7a introduced volatile per-message fields (message_id, message_id_full, reply_to_id) into the system prompt via buildInboundMetaSystemPrompt(). Since message_id is unique per message, including it in the system prompt guaranteed cache invalidation on every turn, forcing a full prefill of the entire context (26K+ tokens) regardless of whether anything else had changed.
The fix — moving these fields out of the system prompt and into the user context — is architecturally correct.
Where the PR fell short
The OpenClaw build system produces multiple bundle files, each containing a copy of buildInboundMetaSystemPrompt(). PR #20597 patched the source code, but the fix only propagated to one of the four bundle files present in the installed package:
| File | Contains unpatched message_id? |
Loaded by gateway? |
|---|---|---|
pi-embedded-Cn8f5u97.js |
❌ Patched by #20597 | No (unused) |
pi-embedded-CHb5giY2.js |
✅ Still unpatched | Indirectly |
reply-B4B0jUCM.js |
✅ Still unpatched | Indirectly |
subagent-registry-DOZpiiys.js |
✅ Still unpatched | Yes — direct import |
plugin-sdk/reply-Bsg9j6AP.js |
✅ Still unpatched | Plugin path |
Critically, subagent-registry-DOZpiiys.js is the file directly imported by gateway-cli-D4HbtwPr.js (line 23). This is the file that actually runs when the gateway processes inbound messages. Because it was not patched, the deployed fix has no effect on the runtime behavior.
Proof: gateway-cli-D4HbtwPr.js line 23 imports from subagent-registry-DOZpiiys.js (visible via grep -n "subagent-registry" dist/gateway-cli-D4HbtwPr.js), and grep -rln "message_id: messageId" dist/ confirms that file still contains the unpatched code.
Instrumented Proof
To verify the issue after updating to 2026.2.19-2, a logging proxy was inserted between OpenClaw and LM Studio that captures the developer-role system prompt from each /v1/chat/completions request and diffs it against the previous turn.
Turn 2 → Turn 3 diff (same session, consecutive messages, after 2026.2.19-2 update)
--- turn_002
+++ turn_003
@@ -213,7 +213,7 @@
```json
{
"schema": "openclaw.inbound_meta.v1",
- "message_id": "6678",
+ "message_id": "6680",
"sender_id": "xxx",
"chat_id": "telegram:xxx",message_id changes on every single turn. All other fields are stable by turn 3. This is the sole cache buster in steady-state conversation and it is caused entirely by the unpatched files.
Turn 1 → Turn 2 diff (additional lazy-init differences, separate issue)
The turn 1 → turn 2 transition has additional diffs beyond message_id:
+- Inline buttons supported. Use `action=send` with `buttons=...`
+ "provider": "telegram",
- "was_mentioned": true,
+## Reactions
+Reactions are enabled for Telegram in MINIMAL mode.
-Runtime: ...| shell=zsh | thinking=off
+Runtime: ...| shell=zsh | channel=telegram | capabilities=inlineButtons | thinking=offThese represent lazy-initialized context blocks that get appended only after the first turn establishes session context (capabilities detection, reactions config, channel resolution). These are a separate, secondary issue — they cause one unavoidable cache miss on the first exchange of every session which causes increases in cost for users who rely on APIs — but they are outside the scope of this report.
The message_id issue is the primary problem because it re-invalidates the cache on every subsequent turn for the entire lifetime of the session.
Root Cause: The Unpatched Code
The following code is still present in the three unpatched files. Here it is from subagent-registry-DOZpiiys.js (the one the gateway loads):
function buildInboundMetaSystemPrompt(ctx) {
const chatType = normalizeChatType(ctx.ChatType);
const isDirect = !chatType || chatType === "direct";
const messageId = safeTrim(ctx.MessageSid);
const messageIdFull = safeTrim(ctx.MessageSidFull);
const replyToId = safeTrim(ctx.ReplyToId);
const chatId = safeTrim(ctx.OriginatingTo);
const payload = {
schema: "openclaw.inbound_meta.v1",
message_id: messageId, // ← volatile, unique per message
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : void 0, // ← volatile
sender_id: safeTrim(ctx.SenderId),
chat_id: chatId,
reply_to_id: replyToId, // ← volatile (changes with every reply)
channel: safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface) ?? safeTrim(ctx.Provider),
provider: safeTrim(ctx.Provider),
surface: safeTrim(ctx.Surface),
chat_type: chatType ?? (isDirect ? "direct" : void 0),
flags: { ... }
};
return [ "## Inbound Context (trusted metadata)", ..., JSON.stringify(payload, null, 2), ... ].join("\n");
}message_id is unique per-message by design (it is the Telegram/platform message ID). Including it in a system prompt that is supposed to be stable across a session guarantees that no two consecutive turns will share a cache-valid prefix. The local inference engine (LM Studio/MLX, llama-server) must therefore discard and rebuild the entire KV cache on every turn.
The Fix
Remove message_id, message_id_full, and reply_to_id from buildInboundMetaSystemPrompt() in all affected files. These fields already exist in the user-role conversation context block (as established by #20597) and do not need to be duplicated in the system prompt.
Files to patch
dist/subagent-registry-DOZpiiys.js ← primary (gateway import)
dist/reply-B4B0jUCM.js
dist/pi-embedded-CHb5giY2.js
dist/plugin-sdk/reply-Bsg9j6AP.js
Change in each file
Before:
const payload = {
schema: "openclaw.inbound_meta.v1",
message_id: messageId,
message_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : void 0,
sender_id: safeTrim(ctx.SenderId),
chat_id: chatId,
reply_to_id: replyToId,
channel: ...After:
const payload = {
schema: "openclaw.inbound_meta.v1",
sender_id: safeTrim(ctx.SenderId),
chat_id: chatId,
channel: ...sender_id and chat_id remain — both are session-stable and appropriate in the system prompt.
Patch script (for immediate relief while a proper build fix is prepared)
import os
DIST = os.path.expanduser(
"~/.nvm/versions/node/v24.13.0/lib/node_modules/openclaw/dist"
)
files = [
os.path.join(DIST, "subagent-registry-DOZpiiys.js"),
os.path.join(DIST, "reply-B4B0jUCM.js"),
os.path.join(DIST, "pi-embedded-CHb5giY2.js"),
os.path.join(DIST, "plugin-sdk/reply-Bsg9j6AP.js"),
]
old = (
'schema: "openclaw.inbound_meta.v1",\n'
'\t\tmessage_id: messageId,\n'
'\t\tmessage_id_full: messageIdFull && messageIdFull !== messageId ? messageIdFull : void 0,\n'
'\t\tsender_id: safeTrim(ctx.SenderId),\n'
'\t\tchat_id: chatId,\n'
'\t\treply_to_id: replyToId,'
)
new = (
'schema: "openclaw.inbound_meta.v1",\n'
'\t\tsender_id: safeTrim(ctx.SenderId),\n'
'\t\tchat_id: chatId,'
)
for path in files:
with open(path, "r", encoding="utf-8") as f:
src = f.read()
if old in src:
with open(path, "w", encoding="utf-8") as f:
f.write(src.replace(old, new, 1))
print(f"✅ Patched: {os.path.basename(path)}")
else:
print(f"⚠️ Not found (may already be patched): {os.path.basename(path)}")After running, restart the gateway: openclaw gateway restart
Verification
After applying the fix, turn 2 → turn 3 diff is completely clean:
TURN 3 — 87227 chars
✅ SYSTEM PROMPT IDENTICAL — cache prefix should be stable.
LM Studio no longer trims the prompt cache on every turn. Second and subsequent messages process only the new tokens rather than re-prefilling the full 26K+ token context, reducing per-turn latency from 3 minutes to ~2 seconds on MiniMax M2.5 (8-bit, LM Studio MLX backend, Mac Studio M3 Ultra 512GB).
Proper Fix Recommendation
The source-level fix should ensure that buildInboundMetaSystemPrompt() in all compilation units is consistent with what PR #20597 intended. The build pipeline should be checked to understand why a source change in one function resulted in only one of four bundle copies being updated. If the bundles are produced from a single source file, this is a build artifact caching issue. If they are produced from different source entry points, each source copy needs to be patched.
The three fields removed (message_id, message_id_full, reply_to_id) are already present in the user-role conversation context prefix established by #20597, so no information is lost by removing them from the system prompt.
Environment: macOS 26.3 arm64 · Mac Studio M3 Ultra 512GB · OpenClaw 2026.2.19-2 · LM Studio 0.4.2+2 (MLX) · MiniMax M2.5 8-bit · Telegram DM channel