Skip to content

fix(memory): use MessagePart::Summary for summary serialization (#2257)#2271

Merged
bug-ops merged 2 commits intomainfrom
2257-summary-kind-deser
Mar 27, 2026
Merged

fix(memory): use MessagePart::Summary for summary serialization (#2257)#2271
bug-ops merged 2 commits intomainfrom
2257-summary-kind-deser

Conversation

@bug-ops
Copy link
Copy Markdown
Owner

@bug-ops bug-ops commented Mar 27, 2026

Summary

  • apply_tool_pair_summaries in crates/zeph-memory/src/sqlite/messages/mod.rs was writing parts as a hardcoded externally-tagged JSON literal {"Summary":{"text":"..."}}, which is incompatible with the internally-tagged serde representation used by MessagePart (#[serde(tag = "kind", rename_all = "snake_case")]). On next session restore, these records failed to deserialize with "missing field 'kind'", falling back to empty — causing silent context loss for compacted messages.
  • Fix: replace serde_json::json!({"Summary": ...}) with MessagePart::Summary { text: summary.clone() } so the correct {"kind":"summary","text":"..."} format is written.
  • Add three regression tests: round-trip via apply_tool_pair_summaries, correct JSON key assertion in provider.rs, and a negative test documenting that old externally-tagged records cannot be deserialized (known limitation — requires a DB migration for pre-fix records).

Test plan

  • cargo +nightly fmt --check passes
  • cargo clippy --workspace --features full -- -D warnings — 0 warnings
  • cargo nextest run --workspace --features full --lib --bins — 6779/6779 passed
  • New regression tests cover the fix path and document backward-compat limitation

Closes #2257

bug-ops added 2 commits March 27, 2026 14:18
…ply_tool_pair_summaries

Replaces hardcoded externally-tagged JSON literal with proper
MessagePart::Summary serialization. The MessagePart enum uses
so the old {"Summary": {"text": "..."}} format was incompatible with
deserialization, causing "missing field 'kind'" errors when loading
compacted messages from SQLite.

Closes #2257
…tion format (#2257)

Adds three tests:
- apply_tool_pair_summaries_parts_deserialize_as_summary_variant: verifies
  that apply_tool_pair_summaries writes internally-tagged parts that round-trip
  through load_history as MessagePart::Summary
- old_external_tag_summary_format_fails_to_deserialize: documents that the
  pre-fix externally-tagged format is incompatible with the current schema
- summary_roundtrip: verifies MessagePart::Summary serializes to
  {"kind":"summary","text":"..."} and deserializes back correctly
@bug-ops bug-ops enabled auto-merge (squash) March 27, 2026 13:18
@github-actions github-actions bot added documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) memory zeph-memory crate (SQLite) rust Rust code changes bug Something isn't working size/M Medium PR (51-200 lines) labels Mar 27, 2026
@bug-ops bug-ops merged commit dba6cc0 into main Mar 27, 2026
25 checks passed
@bug-ops bug-ops deleted the 2257-summary-kind-deser branch March 27, 2026 13:26
bug-ops added a commit that referenced this pull request Mar 27, 2026
…ePart format (#2278)

SQLite records written before v0.17.1 use the old externally-tagged format
({"Summary":{"text":"..."}}). After #2271 introduced the internally-tagged
format ({"kind":"summary","text":"..."}), these rows silently deserialized
to vec![] — 80 failures observed in a single session log.

- Add `try_parse_legacy_parts()` compat fallback in `parse_parts_json()`:
  recognizes all 12 old-format variant keys and converts to new format;
  emits tracing::warn on the legacy path for observability.
- Add migration 045 that resets legacy-format `parts` rows to `[]`;
  the plain-text `content` column is preserved as the display fallback.
- Update and extend unit tests; rename test that previously asserted
  the old format fails to assert it now succeeds via the compat path.
bug-ops added a commit that referenced this pull request Mar 27, 2026
…ePart format (#2278)

SQLite records written before v0.17.1 use the old externally-tagged format
({"Summary":{"text":"..."}}). After #2271 introduced the internally-tagged
format ({"kind":"summary","text":"..."}), these rows silently deserialized
to vec![] — 80 failures observed in a single session log.

- Add `try_parse_legacy_parts()` compat fallback in `parse_parts_json()`:
  recognizes all 12 old-format variant keys and converts to new format;
  emits tracing::warn on the legacy path for observability.
- Add migration 045 that resets legacy-format `parts` rows to `[]`;
  the plain-text `content` column is preserved as the display fallback.
- Update and extend unit tests; rename test that previously asserted
  the old format fails to assert it now succeeds via the compat path.
bug-ops added a commit that referenced this pull request Mar 27, 2026
…ePart format (#2278)

SQLite records written before v0.17.1 use the old externally-tagged format
({"Summary":{"text":"..."}}). After #2271 introduced the internally-tagged
format ({"kind":"summary","text":"..."}), these rows silently deserialized
to vec![] — 80 failures observed in a single session log.

- Add `try_parse_legacy_parts()` compat fallback in `parse_parts_json()`:
  recognizes all 12 old-format variant keys and converts to new format;
  emits tracing::warn on the legacy path for observability.
- Add migration 045 that resets legacy-format `parts` rows to `[]`;
  the plain-text `content` column is preserved as the display fallback.
- Update and extend unit tests; rename test that previously asserted
  the old format fails to assert it now succeeds via the compat path.
bug-ops added a commit that referenced this pull request Mar 27, 2026
…ePart format (#2278) (#2287)

SQLite records written before v0.17.1 use the old externally-tagged format
({"Summary":{"text":"..."}}). After #2271 introduced the internally-tagged
format ({"kind":"summary","text":"..."}), these rows silently deserialized
to vec![] — 80 failures observed in a single session log.

- Add `try_parse_legacy_parts()` compat fallback in `parse_parts_json()`:
  recognizes all 12 old-format variant keys and converts to new format;
  emits tracing::warn on the legacy path for observability.
- Add migration 045 that resets legacy-format `parts` rows to `[]`;
  the plain-text `content` column is preserved as the display fallback.
- Update and extend unit tests; rename test that previously asserted
  the old format fails to assert it now succeeds via the compat path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) memory zeph-memory crate (SQLite) rust Rust code changes size/M Medium PR (51-200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(memory): old Summary message parts fail to deserialize with missing field 'kind'

1 participant