-
Notifications
You must be signed in to change notification settings - Fork 2
fix(memory): orphaned tool-pair messages not soft-deleted when content field is non-empty #2529
Description
Summary
PR #2519 introduced soft-deletion of orphaned tool-pair messages from SQLite after load_history strips them. However, the soft-delete is only triggered when the stripped message becomes fully empty (content.trim().is_empty() && parts.is_empty()). Messages where content is non-empty (e.g., messages where both content and parts carry the tool data) are never added to orphan_db_ids and therefore never soft-deleted.
Reproduction
- Run a session that produces a tool call (e.g.,
memory_save). - Interrupt or allow the session to complete normally — creates messages with both
content = '[tool_use: ...]'andparts = [{kind:'tool_use',...}]. - Restart the agent.
- The
strip_mid_history_orphansWARN appears every restart:WARN zeph_core::agent::persistence: stripping orphaned mid-history tool_use parts from assistant message tool_ids={...} index=1 WARN zeph_core::agent::persistence: stripping orphaned mid-history tool_result parts from user message tool_use_ids={...} index=4 SELECT deleted_at FROM messages WHERE id IN (2, 5)→ both NULL — not soft-deleted.
Root cause
In strip_mid_history_orphans (persistence.rs):
let is_empty = messages[i].content.trim().is_empty() && messages[i].parts.is_empty();
if is_empty {
if let Some(db_id) = messages[i].metadata.db_id { db_ids.push(db_id); }
messages.remove(i);
...
}When content is non-empty (e.g. [tool_use: memory_save(...)]), is_empty is false — the message is kept in the slice with stripped parts, and its db_id is never added to the deletion list.
Expected
After stripping all ToolUse/ToolResult parts from a message, if no meaningful user-visible content remains, the message should be soft-deleted from SQLite regardless of the content field.
Actual
content field retains the legacy string representation, preventing soft-delete. Same WARN repeats on every session startup.
Discovered in
CI-358 (2026-03-31), v0.18.1 (commit 71f2eb8)