fix(seahorse): prevent tool-call format leakage into LLM summaries - #3279
fix(seahorse): prevent tool-call format leakage into LLM summaries#3279MrTreasure wants to merge 3 commits into
Conversation
|
This PR has had no activity for 7 days and has been marked as stale. If you are still working on it, please push an update or leave a comment; otherwise it will be closed automatically in 7 days. |
|
Status update: this fix is still actively maintained. The original issue remains reproducible in long-lived Seahorse sessions: raw tool-call protocol text can enter compacted summaries and later surface to users. I have continued validating the summary/compaction path against current upstream master and am reviewing compatibility with tool-only history entries before the next branch update. Feedback on the preferred serialization behavior is welcome. |
partsToReadableContent used [tool_use: name, args: {...}] format for
FTS5 indexing and summary generation. When summaries containing this
format were fed back to LLMs, the models learned to mimic it and output
mock [tool_use: ...] text as content, which was then displayed to users.
Two fixes:
1. formatMessagesForSummary / truncateSummary: skip messages with empty
content (pure tool calls/results) — these are mechanical, not
conversational, and have no place in summaries.
2. partsToReadableContent: use emoji markers (🔧/📋) instead of the
bracket-colon format that LLMs are prone to mimic.
3. outboundMessageForTurn: strip any residual [tool_use: ...] patterns
from content before sending to channels, as a safety net.
Root cause: same class of bug as sipeed#3153 (Doubao <seed:tool_call> leak).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ffa4674 to
78ca23f
Compare
|
Updated this PR with the complete fix reproduced from a live DeepSeek/OpenAI-compatible response. The provider returned both native tool_calls and a duplicate |
|
This PR has had no activity for 7 days and has been marked as stale. If you are still working on it, please push an update or leave a comment; otherwise it will be closed automatically in 7 days. |
|
This PR has been closed after 14 days of inactivity. If you would like to continue, feel free to reopen it or submit a new PR. |
Related: same symptom triggered by seahorse summaries
I just hit this exact class of bug — tool-call format leaking into user messages — but triggered through a different path: seahorse's
partsToReadableContent.The seahorse variant
partsToReadableContent(inpkg/seahorse/store.go) serializes tool calls as[tool_use: name, args: {...}]for FTS5 indexing. This format was also used as a fallback in two summary-generation paths (formatMessagesForSummaryandtruncateSummaryinshort_compaction.go). When summaries containing[tool_use: exec, args: {...}]were fed back into the LLM context, the model learned to mimic this format in its own responses — outputting mock tool calls as visible text content.The pipeline
Fix
PR incoming at: https://github.com/sipeed/picoclaw/pulls (MrTreasure:fix/seahorse-tool-call-leak)
Three changes:
short_compaction.go: Skip messages with empty content in summary/truncation paths — tool calls aren't conversational contentstore.go: Change FTS5 format from[tool_use: name, args: {...}]to emoji markers — the bracket-colon pattern is what LLMs latch ontoagent_utils.go: Strip residual[tool_use: ...]from outbound messages as a safety netSame root cause as this issue: LLM context leaking internal serialization format → model mimics it → user sees raw tool-call text. Different trigger (seahorse vs Doubao provider), same outcome.