Summary
Feedback classification responses can leak the raw LLM completion — including closing <think> tags and DeepSeek special tokens — into the persisted feedback rationale. The existing thinking-disable only covers capture.summarize; every other op (e.g. feedback.classify) can receive thinking-enabled model output, and nothing strips think-tags from message.content before it is stored.
This is related to #2308 (the op parameter was dropped at the LLM boundary): even with op correctly propagated, thinking is only disabled for capture.summarize.
Reproduction / evidence
Two feedback rows stored on 2026-09-02 in our deployment contain a rationale that is clearly a raw model completion, not classified output:
rationale: "</think>\n<|end▁of▁sentence|>\n<|end▁of▁session|>\n\n---\n\n[Writing Rule]..."
polarity: negative
Code path (v2.0.17)
dist/core/feedback/llm-classifier.js → classifyByLlm() calls llm.complete(..., { op: "feedback.classify" })
dist/core/llm/providers/openai.js only disables thinking for one op:
if (opts.op === "capture.summarize") {
body.thinking = { type: "disabled" };
}
- For thinking-enabled models served through gateways that keep
<think> blocks inside message.content, the raw completion (with </think>, <|end▁of▁sentence|>, …) flows back unmodified.
dist/core/experience/feedback-builder.js then persists feedback.rationale (and raw verifier text) verbatim into the store — polluted text becomes retrieval-visible memory.
Suggested fix
Either (ideally both):
- Sanitize at the provider choke point: strip
<think>…</think> blocks, orphan closing </think> fragments, and DeepSeek special tokens (<|begin▁of▁sentence|>, <|end▁of▁sentence|>, <|end▁of▁session|>) from choice.message.content before returning from the provider — one place, fixes all ops.
- Extend thinking-disable to deterministic/analytical ops (
feedback.classify, l3.abstraction, verifier, retrieval filter), not just capture.summarize.
Environment
- memos-local plugin 2.0.17 (npm
memtensor-memos-local-plugin-2.0.17)
- Adapter: Hermes (
adapters/hermes/memos_provider)
- Windows 11, Node v24, LLM provider
openai_compatible (deepseek-family model)
Summary
Feedback classification responses can leak the raw LLM completion — including closing
<think>tags and DeepSeek special tokens — into the persisted feedbackrationale. The existing thinking-disable only coverscapture.summarize; every other op (e.g.feedback.classify) can receive thinking-enabled model output, and nothing strips think-tags frommessage.contentbefore it is stored.This is related to #2308 (the
opparameter was dropped at the LLM boundary): even withopcorrectly propagated, thinking is only disabled forcapture.summarize.Reproduction / evidence
Two feedback rows stored on 2026-09-02 in our deployment contain a
rationalethat is clearly a raw model completion, not classified output:Code path (v2.0.17)
dist/core/feedback/llm-classifier.js→classifyByLlm()callsllm.complete(..., { op: "feedback.classify" })dist/core/llm/providers/openai.jsonly disables thinking for one op:<think>blocks insidemessage.content, the raw completion (with</think>,<|end▁of▁sentence|>, …) flows back unmodified.dist/core/experience/feedback-builder.jsthen persistsfeedback.rationale(and raw verifier text) verbatim into the store — polluted text becomes retrieval-visible memory.Suggested fix
Either (ideally both):
<think>…</think>blocks, orphan closing</think>fragments, and DeepSeek special tokens (<|begin▁of▁sentence|>,<|end▁of▁sentence|>,<|end▁of▁session|>) fromchoice.message.contentbefore returning from the provider — one place, fixes all ops.feedback.classify,l3.abstraction, verifier, retrieval filter), not justcapture.summarize.Environment
memtensor-memos-local-plugin-2.0.17)adapters/hermes/memos_provider)openai_compatible(deepseek-family model)