refactor(tinycortex): W7 — shim memory_diff over the crate DiffEngine - #4788
Conversation
Reduce memory_diff to a thin host shim over tinycortex::memory::diff: the snapshot/diff/checkpoint/ledger engine is now the crate's DiffEngine (a byte-identical port over the same <workspace>/memory_diff/repo libgit2 layout — P9 parity: existing ledgers keep working unchanged). - ops.rs: the 9 async fns become thin spawn_blocking wrappers that build a DiffEngine + the host item-source seam and call the matching engine method, preserving the async + Result<_,String> signatures, DomainEvent publishes, and tracing that RPC/tools/sync/subconscious callers expect. - source.rs (new): ChunkStoreItemSource implements the crate's SnapshotItemSource seam by querying the authoritative mem_tree_chunks (the exact grouped/ordered query take_snapshot used before). It holds a source_id -> LIKE-prefix map (built from the full MemorySourceEntry list) because the Composio prefix (<toolkit>:%) isn't derivable from the logical id the crate passes. - types.rs: re-export the crate wire types (ChangeKind/Snapshot/DiffResult/ Checkpoint/CrossSourceDiff/ItemChange/DiffSummary/SnapshotTrigger). The old JsonSchema derive was vestigial — the RPC surface is hand-written TypeSchema::Ref schemas, not derived. - rpc.rs/tools.rs: repoint the direct Ledger::open list calls to the crate Ledger. - Delete git_store.rs (the whole libgit2 ledger engine — now the crate's). Parity note: the crate seam (items_for_source) has no Result channel, so a rare chunk-store read failure during snapshot yields an empty snapshot rather than the host's old propagated error. Self-healing (the ledger is a derived, rebuildable view; the next good snapshot restores state) and logged loudly. cargo check --lib: exit 0. Claude-Session: https://claude.ai/code/session_01X39btnEnHSTuPSYYvgyjrb
📝 WalkthroughWalkthroughThe memory-diff host module now delegates persistence and diff operations to tinycortex’s ChangesMemory Diff Engine Migration
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/openhuman/memory_diff/source.rs`:
- Around line 80-83: Escape LIKE metacharacters in the source-prefix pattern
before binding it to the queries around the mem_tree_chunks SELECT, including
the corresponding logic at the second referenced location. Treat
source_id/toolkit prefix characters literally by escaping backslashes,
underscores, and percent signs, then append only the final wildcard so matching
remains prefix-based.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 23e85ff6-5dba-4862-9cc6-42abeffd0dc3
📒 Files selected for processing (7)
src/openhuman/memory_diff/git_store.rssrc/openhuman/memory_diff/mod.rssrc/openhuman/memory_diff/ops.rssrc/openhuman/memory_diff/rpc.rssrc/openhuman/memory_diff/source.rssrc/openhuman/memory_diff/tools.rssrc/openhuman/memory_diff/types.rs
💤 Files with no reviewable changes (1)
- src/openhuman/memory_diff/git_store.rs
| "SELECT source_id, content \ | ||
| FROM mem_tree_chunks \ | ||
| WHERE source_id LIKE ?1 \ | ||
| ORDER BY source_id, seq_in_source", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Escape LIKE metacharacters in source prefixes.
source_id/toolkit are bound safely, but _ and % still act as wildcards. For example, the valid src_a ID can also match chunks for srcXa, mixing another source’s content into this source’s snapshot and ledger history. Escape literal prefix characters and retain only the final wildcard.
Proposed fix
+fn like_prefix_pattern(prefix: &str) -> String {
+ let literal = prefix.strip_suffix('%').unwrap_or(prefix);
+ format!(
+ "{}%",
+ literal
+ .replace('\\', r"\\")
+ .replace('%', r"\%")
+ .replace('_', r"\_")
+ )
+}
+
let mut stmt = conn.prepare(
"SELECT source_id, content \
FROM mem_tree_chunks \
- WHERE source_id LIKE ?1 \
+ WHERE source_id LIKE ?1 ESCAPE '\\' \
ORDER BY source_id, seq_in_source",
)?;
+ let pattern = like_prefix_pattern(prefix);
let mut groups: HashMap<String, Vec<String>> = HashMap::new();
- let rows = stmt.query_map([prefix], |r| {
+ let rows = stmt.query_map([pattern], |r| {Also applies to: 128-136
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/memory_diff/source.rs` around lines 80 - 83, Escape LIKE
metacharacters in the source-prefix pattern before binding it to the queries
around the mem_tree_chunks SELECT, including the corresponding logic at the
second referenced location. Treat source_id/toolkit prefix characters literally
by escaping backslashes, underscores, and percent signs, then append only the
final wildcard so matching remains prefix-based.
Summary
memory_diffto a thin host shim overtinycortex::memory::diff: the snapshot / diff / checkpoint / git-ledger engine is now the crate'sDiffEngine, and this module just async-wraps it, supplies the chunk-store seam, and keeps the RPC + agent-tool surface.git_store.rs) — the crateDiffEnginewrites the same git ledger at the same<workspace>/memory_diff/repopath with the same libgit2 layout (snapshots = commits, checkpoints = tags, read markers = refs), so existing ledgers keep working byte-for-byte (P9 parity).Problem
memory_diffduplicatedtinycortex::memory::diff— a complete port whoseDiffEngine<S: SnapshotItemSource>already implements all nine host operations (take_snapshot,auto_snapshot_after_sync,compute_diff,diff_since_last,diff_since_read,mark_read,create_checkpoint,diff_since_checkpoint,cleanup) pluslist_*. The engine is generic over a chunk-source injection seam precisely so it doesn't hard-depend on the (separately-ported) chunk store.Solution
ops.rs→ the 9 async fns become thinspawn_blockingwrappers that build aDiffEngine+ the host seam and call the matching sync engine method, preserving theasync+Result<_, String>signatures, theDomainEventpublishes (MemoryDiffSnapshotTaken/MemoryDiffMarkedRead), and the tracing that RPC / tools /memory_sources::sync/subconscious::profiles::memorycallers depend on. Timestamps now come from the engine (chrono::Utc::now), identical to before.source.rs(new) →ChunkStoreItemSourceimplements the crate'sSnapshotItemSourceseam by running the exactmem_tree_chunksquery the oldtake_snapshotused (group by item id, concatenate chunk bodies inseq_in_sourceorder, sort by item id). It holds asource_id → LIKE-prefixmap built from the fullMemorySourceEntrylist, because the crate callsitems_for_source(source_id)with the logical id while the host Composio prefix (<toolkit>:%) isn't derivable from that id alone.create_checkpointbaselines several sources, so the map covers all enabled sources; read-only ops use a no-op adapter.types.rs→ re-export the crate wire types (ChangeKind/Snapshot/DiffResult/Checkpoint/CrossSourceDiff/ItemChange/DiffSummary/SnapshotTrigger). The oldschemars::JsonSchemaderive was vestigial — the RPC surface is described by hand-writtenTypeSchema::Refschemas inschemas.rs, not derived ones.rpc.rs/tools.rs→ repoint the directLedger::openlist-calls (list_snapshots/list_checkpoints/snapshot_count_for_source) to the crateLedger.git_store.rs(the whole libgit2 ledger engine — now the crate's).Parity note
The crate seam
items_for_sourcereturnsVec<SnapshotItem>with no error channel, so a (rare) chunk-store read failure during a snapshot now yields an empty snapshot rather than the host's old propagated error. This is self-healing — the ledger is a derived, rebuildable view, so the next successful snapshot restores the true state — and it is logged aterrorlevel. All other paths (diff/checkpoint/read-marker) are unaffected.Submission Checklist
seed()repointed to the crateLedger; new seam tests coversource_id_prefix(folder / Composio / missing-toolkit) and the read-only adapter.memory_difftests; CI diff-cover is the gate.docs/tinycortex-*migration plan.Impact
Result<_, String>APIs unchanged.cargo check --libexit 0;cargo test --lib memory_diff→ 19 passed; 0 failed (10 ops-over-real-ledger, 4 seam, 4 tool-format, 2 schema-sync).--no-verify— the hook fails on two environmental gaps unrelated to the diff (app/src-taurican't buildglib-sysfor lack of GTK libs;lint:commands-tokensneedsripgrep). No changed code lives in theapp/src-tauriworld.Related
memory_tools— blocked on gap G1 (the hostMemorytrait'ssqlite_conn()escape hatch is not yet unified with the crate'sMemory; unblocks at W3). This PR completes every flippable W7 module.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:check— noapp/srcchangespnpm typecheck— no frontend changescargo test --lib memory_diff→ 19 passed;cargo check --libexit 0cargo fmt --checkclean on changed files;cargo check --libexit 0app/src-tauribuild blocked by missing system GTK libs (env, not diff)Validation Blocked
command:cargo check --manifest-path app/src-tauri/Cargo.tomlerror:glib-sysbuild fails (missing GTK/glib system libs)impact:environmental only — no changed code in theapp/src-tauriworldBehavior Changes
https://claude.ai/code/session_01X39btnEnHSTuPSYYvgyjrb
Summary by CodeRabbit