Skip to content

fix(memory): orphaned tool-pair messages not soft-deleted when content field is non-empty #2529

@bug-ops

Description

@bug-ops

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

  1. Run a session that produces a tool call (e.g., memory_save).
  2. Interrupt or allow the session to complete normally — creates messages with both content = '[tool_use: ...]' and parts = [{kind:'tool_use',...}].
  3. Restart the agent.
  4. The strip_mid_history_orphans WARN 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
    
  5. 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)

Metadata

Metadata

Assignees

Labels

P3Research — medium-high complexitybugSomething isn't workingmemoryzeph-memory crate (SQLite)

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions