fix(core): strip ephemeral Image parts before persistence writes - #6307
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 14, 2026 22:14
Enforces invariant C1/M5 (spec-072 4) -- MessagePart::Image must never reach SQLite parts_json, Qdrant embeddings, or the durable JSONL session log. Agent::persist_message now computes a stripped copy of parts before both persistence writers; the in-memory Message retains its Image parts for the current turn's provider request. Also closes pre-existing persistence waste on the user-upload image path.
bug-ops
force-pushed
the
feat/issue-6239/persistence-strip
branch
from
July 14, 2026 22:21
a84f927 to
f01eca9
Compare
8 tasks
bug-ops
added a commit
that referenced
this pull request
Jul 16, 2026
TranscriptWriter::append serialized the full Message verbatim to the sub-agent's JSONL transcript with no filtering. Sub-agents never call Agent::persist_message, so their transcripts bypassed the MessagePart::Image strip landed in #6307 for spec-072 C1 entirely. Extract the strip logic into a shared MessagePart::strip_images helper in zeph-llm and call it from both Agent::persist_message and TranscriptWriter::append, closing the defense-in-depth gap before #6240 (MCP image emission in tool-result processing) can make it reachable. Closes #6305
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
specs/072-multimodal-mcp-passthrough/spec.md§4):MessagePart::Imagemust never reach SQLiteparts_json, Qdrant embeddings, or the durable JSONL session log.Agent::persist_messagenow computes a stripped copy ofparts(Image-filtered) and passes it to both persistence writers, before any code path that can produce an MCP-sourcedImagepart lands. The in-memoryMessage(already pushed viapush_message) keeps itsImageparts for the current turn's provider request — only the two persistence writers get the stripped copy.Closes #6239
Test plan
crates/zeph-core/src/agent/persistence/tests.rs(mod image_persistence_strip): strip verified against SQLiteparts_jsonround-trip, the Qdrant-embed-path message-count/round-trip, the session JSONL log (SessionEvent::AssistantMessage), and a test proving the in-memoryMessageis unaffected.cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins --no-fail-fast— 13722 passed, 0 failed (one pre-existing unrelated flaky test,debug_dump::tests::json_dump_request_includes_request_metadata, confirmed by isolated re-run and independently by the reviewer)RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler")[Unreleased]Follow-ups filed during review
Two adjacent gaps were identified but are out of this issue's literal scope (spec-072 §4/AC-5 name exactly SQLite/Qdrant/session-JSONL, bound to
Agent::persist_message) and are tracked separately:zeph-subagent'sTranscriptWriter::appendhas noImagefilter; currently latent (no code path produces a newImagepart inside a subagent turn today), becomes reachable once Implement: P2 MediaSanitizer decode/validate/attach + vision-tier routing for MCP image passthrough #6240 (P2, MCP image emission) lands.debug_dumpwrites rawImagebase64 bytes to disk when enabled (opt-in, off by default; dumping the wire payload is the feature's purpose).