Skip to content

fix: remediate memory engine audit findings#74

Merged
senamakel merged 8 commits into
mainfrom
agent/spec-remediation
Jul 14, 2026
Merged

fix: remediate memory engine audit findings#74
senamakel merged 8 commits into
mainfrom
agent/spec-remediation

Conversation

@senamakel

@senamakel senamakel commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

  • closes the audited crash-safety, concurrency, parser, cleanup, and queue-settlement defects across storage, ingest, trees, conversations, sources, goals, and tool memory
  • makes retrieval bounds and scoring/ingestion/queue policies declarative, removes silent result cliffs, and implements the headline Memory contract for the reference store
  • removes empty rpc and providers-http feature surfaces, splits every Rust source file below 500 lines, and updates the spec index with an honest remediation record
  • adds a real ingest → durable queue drain → tree seal → retrieval smoke test plus concurrency, recovery, pagination, and atomicity regressions

Notable contract changes

  • the store-layer error is now StoreError, avoiding the duplicate public MemoryError name
  • MemoryConfig now includes scoring, ingestion, retrieval-limit, and queue policy roots with validation and partial TOML defaults
  • chat/email chunk identity is content-stable across overlapping deliveries
  • the shared SQLite database remains the documented atomic transaction boundary; the future remote backend remains specified but is not claimed as implemented

Verification

  • cargo fmt --all -- --check
  • cargo check --all-features
  • cargo clippy --all-targets --all-features -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps
  • cargo test --all-features: 1300 unit tests, 15 functional tests, 4 doctests passed; one credentialed live test and one example ignored
  • isolated test matrices: dependency-light core, tokio, git-diff, and sync
  • cargo llvm-cov --all-features --workspace --summary-only: 90.60% line coverage
  • cargo package --allow-dirty --no-verify

See docs/spec/README.md for the current status and the retained pre-remediation audit rationale.

Summary by CodeRabbit

  • New Features

    • Added configurable retrieval, scoring, queue, ingestion, and synchronization policies.
    • Added filtered, paginated memory listing and expanded in-memory store capabilities.
    • Added document-level tree sealing and safer archival behavior.
    • Added transaction-aware ingestion and queue processing.
    • Added collision-resistant storage and improved source update controls.
  • Bug Fixes

    • Improved recovery from corrupt databases and interrupted tree updates.
    • Prevented unsafe content, path, identifier, and prompt-boundary injection.
    • Corrected time-window filtering, recency ordering, duplicate goals, and stale indexes.
    • Strengthened vector, configuration, and stored-data validation.
  • Documentation

    • Updated README, feature guidance, configuration details, and remediation records.
  • Tests

    • Expanded coverage for concurrency, recovery, retrieval, synchronization, storage, and end-to-end workflows.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0108db59-92cf-4abc-b216-9ad740888a62

📥 Commits

Reviewing files that changed from the base of the PR and between e8ee978 and daf9a0e.

📒 Files selected for processing (8)
  • src/memory/chunks/mod.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/ingest/pipeline.rs
  • src/memory/ingest/pipeline_tests.rs
  • src/memory/retrieval/fast_tests.rs
  • src/memory/retrieval/source.rs
  • src/memory/store/memory_trait.rs
  • src/memory/store/memory_trait_tests.rs
📝 Walkthrough

Walkthrough

The PR hardens TinyCortex across configuration, storage, ingestion, retrieval, queue execution, synchronization, tree processing, public contracts, and CI. It adds transactional flows, corruption recovery, validation, collision-resistant paths, safer parsing, expanded feature testing, and extensive regression coverage.

Changes

Core correctness and infrastructure

Layer / File(s) Summary
Configuration, storage, and public contracts
Cargo.toml, src/memory/config*, src/memory/store/*, src/memory/types.rs
Adds validated policy configuration, stricter storage error handling, transactional entity indexing, vector validation, explicit enum serialization, and an in-memory Memory implementation.
Ingestion, chunking, and source safety
src/memory/ingest/*, src/memory/chunks/*, src/memory/sources/*
Makes ingestion persistence transactional, stabilizes message chunk boundaries, hardens canonicalization and path handling, and adds source patching and registry mutation synchronization.
Retrieval, scoring, and conversations
src/memory/retrieval/*, src/memory/score/*, src/memory/conversations/*
Moves limits and time filtering into configuration/SQL, adds graph fast paths, removes stale indexes on rescoring, and improves timestamp, thread identity, and index reconciliation behavior.
Queue, tree runtime, sync, and safety
src/memory/queue/*, src/memory/tree/*, src/memory/sync/*, src/memory/store/safety/*
Adds typed queue payloads and transactional follow-ups, crash-safe tree folding and swaps, sync parsing and validation coverage, and expanded PII validators/tests.
CI, scripts, and documentation
.github/workflows/*, scripts/*, README.md, docs/spec/*
Adds strict documentation and feature-matrix testing, setup/test scripts, and documentation describing the updated feature and remediation state.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

I’m a rabbit hopping through the queue,
Finding safer paths and fresher views.
Chunks line up, old ghosts depart,
Tests guard every careful part.
CI shines bright—what a clever start!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the broad remediation of audited memory-engine findings.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@senamakel senamakel marked this pull request as ready for review July 14, 2026 08:49

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95454a6995

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/memory/chunks/produce.rs Outdated
Comment thread src/memory/config/policy.rs
Comment thread src/memory/chunks/connection.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
src/memory/chunks/connection.rs (1)

263-300: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Breaker isn't re-checked after acquiring init_lock, risking a thundering-herd of wasted retries once cooldown expires.

The connection cache is double-checked under init_lock (Lines 275-280), but the breaker's is_open() is only checked once, before the lock (Lines 244-253). Once the cooldown lapses, every waiting caller that already passed that earlier check will proceed to call open_and_init in turn — even if an earlier queued attempt just re-tripped the breaker (Line 39-40 in connection_breaker.rs extends last_trip on each failure while tripped) while they were queued. Each such attempt pays the full connect/timeout cost against a DB that's still down, defeating the "don't busy-loop" purpose of the breaker under sustained failure.

Re-check breaker.is_open() right after acquiring _init_guard, mirroring the existing cache double-check.

🔒 Proposed fix: re-check breaker after acquiring the init lock
     // Re-check the cache once we hold the init lock.
     {
         let guard = conn_cache().connections.lock();
         if let Some(conn) = guard.get(&db_path) {
             return Ok(Arc::clone(conn));
         }
     }
+
+    // Re-check the breaker too — a queued attempt ahead of us may have
+    // just re-tripped it while we were waiting for the lock.
+    {
+        let breakers = conn_cache().breakers.lock();
+        if let Some(breaker) = breakers.get(&db_path) {
+            if breaker.is_open() {
+                anyhow::bail!(
+                    "[chunks] circuit breaker open for {}: too many consecutive init failures",
+                    db_path.display()
+                );
+            }
+        }
+    }
🤖 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/memory/chunks/connection.rs` around lines 263 - 300, After acquiring
_init_guard in the connection initialization flow, re-check the relevant
breaker’s is_open() before the cache re-check or any open_and_init call. Return
the same breaker-open result used by the earlier pre-lock check, while
preserving the existing cache double-check and retry behavior for closed
breakers.
src/memory/sync/dispatcher.rs (1)

122-328: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Tests are mixed into the implementation file instead of a sibling dispatcher_tests.rs.

This PR extends the existing inline mod tests { ... } block in dispatcher.rs with new coverage (init-failure propagation, blank-id rejection). As per coding guidelines, tests should live in a per-file sibling such as dispatcher_tests.rs rather than mixed into the implementation file. Moving this module out keeps dispatcher.rs focused and gives headroom against the 500-line file-size guideline as coverage keeps growing.

🤖 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/memory/sync/dispatcher.rs` around lines 122 - 328, Move the inline `mod
tests` block from `dispatcher.rs` into a sibling `dispatcher_tests.rs` module,
preserving all existing test helpers and cases including `FakePipeline`,
`NoopHost`, and the three test functions. Wire the external test module using
the project’s established module declaration pattern, and keep implementation
code in `dispatcher.rs` unchanged.

Source: Coding guidelines

src/memory/tool_memory/store.rs (2)

180-225: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

rules_for_prompt doc still describes the old hard-truncate/drop behavior.

Lines 182 and 186-189 say the result is "bounded by TOOL_MEMORY_PROMPT_CAP entries total" and that excess Critical rules are "silently dropped ... there is no overflow signal." The new logic at lines 221-225 computes critical_count and truncates to TOOL_MEMORY_PROMPT_CAP.max(critical_count), which guarantees every Critical rule survives — the opposite of what's documented. Please update the docstring to state that the cap only bounds High-priority overflow and that Critical rules are never dropped (and unbounded, which may be worth calling out as a caveat given Critical rules can originate from less-trusted capture paths per the docstring a few lines above).

🤖 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/memory/tool_memory/store.rs` around lines 180 - 225, Update the
documentation for rules_for_prompt to match the critical_count and truncate
behavior: state that all Critical rules are retained and never dropped, while
TOOL_MEMORY_PROMPT_CAP only bounds the retained High-priority rules. Remove the
outdated hard-cap and silent Critical-drop claims, and mention that the result
can exceed the cap when Critical rules are numerous.

68-104: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale put_rule and rules_for_prompt docs.

  • The put_rule NOTE about verbatim tool_name and newline-forgery is out of date: tool_name is lower-cased before storage, and the prompt renderer no longer treats embedded newlines as a section break.
  • The rules_for_prompt comment still says excess Critical rules are silently dropped, but the new truncation logic keeps all Critical entries and only limits the non-critical remainder.
  • If you keep the lower-casing note, call out that older mixed-case rows remain unchanged until rewritten.
🤖 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/memory/tool_memory/store.rs` around lines 68 - 104, The documentation
around put_rule is stale: remove or revise the claims that tool_name is stored
verbatim and that embedded newlines can forge prompt sections, and if retaining
the casing note, mention that only newly written values are lower-cased while
older mixed-case rows persist until rewritten. Update the rules_for_prompt
documentation to state that truncation preserves all Critical rules and limits
only the non-critical remainder.
src/memory/score/mod.rs (1)

371-398: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make persist_score atomic across score and entity-index writes persist_score still commits upsert_score, clear_entity_index_for_node, and index_entities separately, so a failure in the clear/reindex path can leave the score row and entity index out of sync. Use one transaction for the whole sequence, or route callers through persist_score_tx when atomicity matters.

🤖 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/memory/score/mod.rs` around lines 371 - 398, Make persist_score atomic by
executing upsert_score, clear_entity_index_for_node, and the conditional
index_entities operation within one database transaction. Reuse the existing
persist_score_tx transaction path if available, ensuring any failure rolls back
both the score row and entity-index changes together.
src/memory/conversations/store_index.rs (1)

158-197: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Avoid rescanning every thread file on each list/summary call
list_threads_unlocked now re-reads and deserializes every thread’s JSONL file on every call under CONVERSATION_STORE_LOCK, so a normal thread list becomes O(total messages) and one unreadable thread file fails the whole request. thread_summary_unlocked does the same per-thread reread on its path.

Gating this to cold/recovery cases or memoizing an “already reconciled” marker per workspace would avoid turning these hot reads into workspace-wide scans.

🤖 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/memory/conversations/store_index.rs` around lines 158 - 197, Change
list_threads_unlocked and the corresponding thread_summary_unlocked path so
normal reads do not call measure_messages_unlocked for every thread. Restrict
message-file reconciliation to cold-start/recovery cases or reuse a
workspace-level marker indicating reconciliation has already completed, while
preserving repair behavior when reconciliation is required and preventing one
unreadable thread file from failing routine list/summary requests.
src/memory/queue/store_settle.rs (1)

1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

mark_done_with_followups doesn't report whether it actually committed; worker.rs acts on follow_ups as if it always did.

mark_done_with_followups returns Ok(()) even when the optimistic claim-token check fails (updated == 0), silently skipping the follow-up inserts in that case. settle_planned_job's Done branch in worker.rs computes arms_reembed from the follow_ups slice and unconditionally calls set_backfill_in_progress(true) — it never checks whether the follow-up was actually persisted. Under a stale-claim race (lease already reclaimed elsewhere), the ReembedBackfill follow-up is silently dropped, yet the global flag is still set to true with no corresponding queue row ever able to clear it, leaving backfill_in_progress stuck. Contrast with the same function's own seal re-arm logic and the Err-branch's flag-clearing, both of which correctly re-verify via get_job before mutating state.

  • src/memory/queue/store_settle.rs#L32-62: change mark_done_with_followups to return Result<bool> (whether the row update actually matched), so callers can distinguish a real commit from a stale-claim no-op.
  • src/memory/queue/worker.rs#L136-148: gate set_backfill_in_progress(true) on that returned success flag, mirroring the guard already used in the seal re-arm and Err-branch cleanup.
🔧 Suggested fix
 pub(crate) fn mark_done_with_followups(
     config: &MemoryConfig,
     job: &Job,
     follow_ups: &[NewJob],
-) -> Result<()> {
+) -> Result<bool> {
     let job_id = &job.id;
     let claim_attempts = job.attempts as i64;
     let claim_started_at = job.started_at_ms;
     with_connection(config, |conn| {
         let now_ms = Utc::now().timestamp_millis();
         let tx = conn.unchecked_transaction()?;
         let updated = tx.execute(
             "UPDATE mem_tree_jobs ...",
             params![now_ms, job_id, claim_attempts, claim_started_at],
         )?;
         if updated != 0 {
             for follow_up in follow_ups {
                 crate::memory::queue::store::enqueue_tx(&tx, follow_up)?;
             }
         }
         tx.commit()?;
-        Ok(())
+        Ok(updated != 0)
     })
 }

 pub fn mark_done(config: &MemoryConfig, job: &Job) -> Result<()> {
-    mark_done_with_followups(config, job, &[])
+    mark_done_with_followups(config, job, &[]).map(|_| ())
 }
-            mark_done_with_followups(config, job, &follow_ups)?;
-            if arms_reembed {
+            let committed = mark_done_with_followups(config, job, &follow_ups)?;
+            if committed && arms_reembed {
                 set_backfill_in_progress(true);
             }
🤖 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/memory/queue/store_settle.rs` at line 1, Update mark_done_with_followups
to return Result<bool>, with the boolean indicating whether the claim-token
update matched a row; return false for stale-claim no-ops. In
settle_planned_job’s Done branch, capture this result and only call
set_backfill_in_progress(true) when the settlement succeeded, preventing
follow-up state from being updated after an uncommitted completion.
src/memory/store/vectors/store.rs (2)

198-218: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale doc on insert_with_vector — dimension check is now enforced.

The doc says the vector's length "is never checked against the store's configured dimensions" and a wrong-dimension vector is "stored as-is" — but the anyhow::ensure! added directly below now rejects mismatched lengths at insert time. Update the doc to describe the new fail-fast behavior instead of the old silent-corruption behavior.

📝 Proposed doc fix
-    /// NOTE: the vector's length is never checked against the store's
-    /// configured dimensions (`store_meta.embed_dims`). A wrong-dimension
-    /// vector is stored as-is; [`cosine_similarity`] then returns `0.0` for
-    /// any query against it (length mismatch), so the row silently never
-    /// surfaces in search results rather than erroring at insert time.
+    /// The vector's length is validated against the store's configured
+    /// dimensions (`store_meta.embed_dims`); a mismatch is rejected here
+    /// rather than being silently stored and later dropped from search
+    /// results.
🤖 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/memory/store/vectors/store.rs` around lines 198 - 218, Update the
documentation for insert_with_vector to reflect that it validates
embedding.len() against self.backend.dimensions() and rejects mismatched vectors
immediately with an error. Remove the outdated description of storing invalid
vectors and silently excluding them from search results.

88-91: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale "known gaps" documentation — the gaps described are already fixed by this diff.

The doc says the embed_dims read still "collapses to None via .ok()" and the dims string is still "parsed with .parse().unwrap_or(0)" — but the code below now uses .optional()? (Line 147) and .parse().with_context(...)? (Lines 164-168), which is exactly the fix for both described gaps. The same stale wording also appears in open()'s doc (Lines 88-91). Leaving this uncorrected risks misleading future readers into thinking the fail-closed guard is still broken.

📝 Proposed doc fix
-    /// NOTE: two related gaps, both currently present:
-    /// - A failed read of the `embed_dims` row (e.g. a transient I/O error)
-    ///   collapses to `None` via `.ok()` and is treated identically to a
-    ///   genuinely first-ever open, so it silently *overwrites*
-    ///   `embed_provider`/`embed_dims` with the current runtime backend
-    ///   instead of surfacing the read failure.
-    /// - The stored dims string is parsed with `.parse().unwrap_or(0)`; a
-    ///   corrupted value collapses to `0`, and the mismatch check is skipped
-    ///   whenever either side is `0` — so a corrupt row disables the guard
-    ///   entirely instead of failing closed.
+    /// A failed read of the `embed_dims` row now propagates as an error
+    /// (via `.optional()?`) instead of being treated as first-open, and an
+    /// unparsable stored dims string now fails closed via
+    /// `.parse().with_context(...)?` rather than silently defaulting to `0`.

Also update the corresponding note in open()'s doc comment (Lines 88-91).

Also applies to: 124-176

🤖 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/memory/store/vectors/store.rs` around lines 88 - 91, Update the
documentation for open() and the related check_or_store_meta helper to remove
references to the already-fixed gaps involving .ok() collapsing errors and
.parse().unwrap_or(0). Describe the current fail-closed behavior accurately,
without changing the implementation.
src/memory/conversations/inverted_index.rs (1)

337-346: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale doc: candidates_for_term's doc comment no longer matches the None-branch behavior.

This doc block still says the None case "routinely exceeds LARGE_CANDIDATE_LIMIT and trips the recency-only fallback," but per the updated search logic (lines 250-267), the pathological cap now only applies inside the Some(v) branch — the None branch always does an uncapped full-corpus scan and never triggers recency_fallback. The doc should be updated to describe the current, intentional behavior (avoiding fabricated score-0.0 hits for short/CJK terms at the cost of an uncapped scan) instead of the old one.

As per coding guidelines, "Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs."

🤖 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/memory/conversations/inverted_index.rs` around lines 337 - 346, Update
the doc comment for candidates_for_term to match the current search behavior:
explain that the None branch intentionally performs an uncapped full-corpus scan
and does not trigger recency_fallback, avoiding fabricated score-0.0 hits for
short or single-CJK terms at the cost of scanning all live documents. Remove the
obsolete LARGE_CANDIDATE_LIMIT and recency-only fallback claim while preserving
the existing explanation of why this branch occurs.

Source: Coding guidelines

src/memory/chunks/store.rs (1)

73-94: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Reclaim the previous staged blob on plain upsert
src/memory/chunks/store.rs:73-94 clears content_path/content_sha256, but it never removes the file referenced by the old content_path. store_delete.rs only reclaims files on explicit deletes, so a staged→plain transition can strand the staged blob on disk.

🤖 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/memory/chunks/store.rs` around lines 73 - 94, The UPSERT_SQL path clears
the previous content_path without reclaiming its staged blob. Update the plain
upsert flow around UPSERT_SQL to capture and remove the existing content_path
file before or as part of replacing the row, while preserving the current reset
of content_path and content_sha256; reuse the existing blob-reclamation
mechanism from the store deletion flow rather than introducing separate cleanup
behavior.
🧹 Nitpick comments (17)
src/memory/store/safety/pii/checks.rs (1)

1-232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add src/memory/store/safety/pii/checks_tests.rs for the checksum validators. pii_tests.rs only covers a couple of cases indirectly; these CPF/CNPJ/CUIT/Luhn/IBAN/Verhoeff/SSN/NINO rules deserve direct edge-case tests beside the module.

🤖 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/memory/store/safety/pii/checks.rs` around lines 1 - 232, Add a sibling
checks_tests.rs module containing direct edge-case tests for valid_cpf,
valid_cnpj, valid_cuit, valid_luhn, valid_iban, valid_verhoeff, valid_ssn,
valid_dni_es, valid_nie_es, and valid_nino. Cover representative valid inputs
plus invalid lengths, checksums, reserved values, prefixes, and boundary rules,
and structure the tests so the private validators are exercised through the
module’s test setup.

Source: Coding guidelines

src/memory/store/vectors/store_tests_more.rs (1)

31-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce schema coupling in the corrupt-metadata test.

The raw INSERT re-specifies every column of vectors. A normal insert followed by an UPDATE ... SET metadata = 'not-json' would isolate the test to only the behavior under test (invalid metadata handling) and survive future schema changes to unrelated columns.

♻️ Proposed simplification
 fn search_handles_invalid_metadata_json() {
     let store = fake_store(2);
+    store
+        .insert_with_vector("bad", "ns", "text", &[1.0, 0.0], json!({}))
+        .unwrap();
     {
         let conn = store.conn.lock();
         conn.execute(
-            "INSERT INTO vectors (id, namespace, text, embedding, metadata, created_at, updated_at)
-             VALUES ('bad', 'ns', 'text', ?1, 'not-json', 0.0, 0.0)",
-            rusqlite::params![vec_to_bytes(&[1.0, 0.0])],
+            "UPDATE vectors SET metadata = 'not-json' WHERE id = 'bad'",
+            [],
         )
         .unwrap();
     }
🤖 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/memory/store/vectors/store_tests_more.rs` around lines 31 - 45, Update
search_handles_invalid_metadata_json to create the vector using the store’s
normal insertion path, then execute a targeted UPDATE that sets only its
metadata to invalid JSON. Remove the raw INSERT’s unrelated column values while
preserving the existing search and null-metadata assertions.
src/memory/sources/registry.rs (1)

183-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deduplicate the lock-acquisition boilerplate.

All seven mutating methods repeat the identical REGISTRY_MUTATION_LOCK.lock().expect("source registry mutation lock poisoned") line. Extracting a small private helper centralizes the panic message and reduces the chance a future mutating method is added without acquiring the lock.

♻️ Proposed helper
+    fn mutation_guard(&self) -> std::sync::MutexGuard<'static, ()> {
+        REGISTRY_MUTATION_LOCK
+            .lock()
+            .expect("source registry mutation lock poisoned")
+    }
+
     pub fn add(&self, entry: MemorySourceEntry) -> Result<MemorySourceEntry> {
-        let _guard = REGISTRY_MUTATION_LOCK
-            .lock()
-            .expect("source registry mutation lock poisoned");
+        let _guard = self.mutation_guard();

(repeat for update, remove, remove_composio_source_by_connection_id, upsert_composio_source, upsert_composio_sources_batch, apply_all_in)

Also applies to: 199-208, 218-220, 235-237, 261-263, 276-278, 289-291

🤖 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/memory/sources/registry.rs` around lines 183 - 185, Extract a private
lock-acquisition helper for REGISTRY_MUTATION_LOCK that performs the existing
lock call and panic message, then replace the repeated boilerplate in all
mutating methods, including update, remove,
remove_composio_source_by_connection_id, upsert_composio_source,
upsert_composio_sources_batch, apply_all_in, and the method shown. Keep each
method’s guard held for its full mutation scope.
src/memory/chunks/connection_breaker.rs (2)

11-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing item-level docs on public breaker API.

new, record_success, record_failure, and is_open have no doc comments describing their contracts (e.g., what the bool return values mean, cooldown semantics). Only the module header documents CircuitBreaker at a high level.

As per path instructions, "Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs."

🤖 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/memory/chunks/connection_breaker.rs` around lines 11 - 49, Add Rust doc
comments to CircuitBreaker::new, record_success, record_failure, and is_open
describing initialization, the meaning of each bool return value,
failure-threshold behavior, and cooldown semantics. Keep the implementation
unchanged and align the item documentation with the existing module-level
CircuitBreaker contract.

Source: Path instructions


32-49: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow TOCTOU window between tripped and last_trip.

record_failure flips tripped to true (Line 34) before writing last_trip (Line 35). A concurrent is_open() call in that gap sees tripped == true but last_trip still None, so matches! fails the Some(..) pattern and is_open() wrongly reports false, letting a caller slip past the breaker mid-trip. Self-correcting on the next failure, but the state isn't updated as one atomic unit.

Consider consolidating tripped, consecutive_failures, and last_trip into a single Mutex-guarded struct so trip transitions are observed atomically.

🤖 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/memory/chunks/connection_breaker.rs` around lines 32 - 49, Make the
breaker state transition atomic by consolidating tripped, consecutive_failures,
and last_trip into one Mutex-guarded state structure. Update record_failure and
is_open to access and mutate this shared state under the same lock, ensuring
is_open cannot observe tripped without its corresponding trip timestamp.
src/memory/sources/readers/folder.rs (1)

117-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use DEFAULT_GLOB instead of duplicating the literal.

list_items derives its default via the DEFAULT_GLOB constant (Line 50); read_item hardcodes the same string literal instead. Values match today, but this duplication risks the exact list/read glob divergence this change is meant to close if DEFAULT_GLOB is ever updated in one place only.

♻️ Proposed fix
-        let pattern = source.glob.as_deref().unwrap_or("**/*.md");
+        let pattern = source.glob.as_deref().unwrap_or(DEFAULT_GLOB);
🤖 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/memory/sources/readers/folder.rs` around lines 117 - 125, Update the
default pattern used in read_item to reference the existing DEFAULT_GLOB
constant instead of hardcoding "**/*.md". Keep the explicit source.glob value
and subsequent glob matching behavior unchanged.
src/memory/sync/composio/providers/slack_parse.rs (1)

10-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add item docs for the extracted parsing helpers.

These pub(super) functions encode several non-obvious JSON-pointer fallback paths (cursor/pagination/mention/timestamp parsing) now shared across Slack sync code paths, but none carry item-level doc comments.

As per path instructions, "Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs."

🤖 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/memory/sync/composio/providers/slack_parse.rs` around lines 10 - 81, Add
item-level documentation comments to each pub(super) helper: mention_regex,
replace_mentions, next_cursor, search_matches, search_total_pages,
decode_cursors, and parse_ts. Document their purpose and the non-obvious
fallback paths, mention resolution behavior, cursor defaults, and timestamp
parsing contract without changing implementation behavior.

Source: Path instructions

src/memory/score/store_query.rs (1)

1-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a dedicated store_query_tests.rs sibling.

This new store_query.rs module's functions are currently tested from store_tests.rs rather than a co-located store_query_tests.rs. As per path instructions, src/**/*_tests.rs: "Add focused unit tests beside the module under src/." Transactional cross-module tests can stay in store_tests.rs, but plain diagnostics-query tests (e.g. lookup_entity_in_window, list_entity_ids_for_node) would fit the convention better in a sibling file.

🤖 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/memory/score/store_query.rs` around lines 1 - 92, Move focused unit tests
for the diagnostics-query functions in store_query, including
lookup_entity_in_window and list_entity_ids_for_node, from store_tests.rs into a
co-located store_query_tests.rs sibling under src. Keep transactional or
cross-module integration tests in store_tests.rs, and preserve the existing test
coverage and behavior.

Source: Path instructions

src/memory/queue/store_settle.rs (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the defer-age default instead of duplicating it.
src/memory/queue/store_settle.rs and src/memory/config/policy.rs both hardcode the same 7-day max_defer_age_ms, and src/memory/queue/store_settle_tests.rs backdates against the test-only copy. Pull the test bound from MemoryConfig::default() (or a shared const) so the boundary can’t drift.

🤖 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/memory/queue/store_settle.rs` at line 1, Share the 7-day max_defer_age_ms
default instead of duplicating it across settlement and policy code. Update the
defer-age boundary in store_settle.rs and store_settle_tests.rs to reuse
MemoryConfig::default() or a shared constant from policy.rs, while preserving
the existing settlement behavior and test boundary.
src/memory/diff/ledger_helpers.rs (1)

13-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add doc comments to the contract-defining helpers.

checkpoint_message/checkpoint_from_message define the JSON wire schema for checkpoint tag messages, and validate_source_id/build_commit_message encode invariants (blank/control-char rejection, trailer layout) relied on elsewhere — but none of these have doc comments, unlike sibling helpers in the same file (parse_trailers, sanitize_trailer, encode_item_id, etc.).

As per path instructions, src/**/*.rs should "Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs."

Also applies to: 22-42, 69-76, 78-122

🤖 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/memory/diff/ledger_helpers.rs` around lines 13 - 16, Add Rust doc
comments to checkpoint_message, checkpoint_from_message, validate_source_id, and
build_commit_message documenting their wire-schema or validation/trailer-layout
contracts, including relevant rejection and formatting behavior. Follow the
existing documentation style used by parse_trailers, sanitize_trailer, and
encode_item_id; do not change implementation behavior.

Source: Path instructions

src/memory/config/policy.rs (1)

52-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a dedicated policy_tests.rs for these validators.

RetrievalLimits::validate, ScoringPolicyConfig::validate, and QueueConfig::validate are only exercised indirectly today, through MemoryConfig::validate() in config_tests.rs. Per the path guideline for src/**/*.rs, tests should live in a <name>_tests.rs sibling of the module under test.

As per path instructions, "Keep tests in per-file <name>_tests.rs siblings, such as store.rs and store_tests.rs, rather than mixing tests into implementation files."

Also applies to: 106-119, 153-166

🤖 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/memory/config/policy.rs` around lines 52 - 78, Move direct validator
coverage for RetrievalLimits::validate, ScoringPolicyConfig::validate, and
QueueConfig::validate out of config_tests.rs into a dedicated policy_tests.rs
sibling of the policy module. Add focused tests for each validator’s accepted
and rejected bounds while retaining MemoryConfig::validate integration coverage
where appropriate.

Source: Path instructions

src/memory/store/entity_index/transaction.rs (1)

10-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doc comment omits the non-obvious identity-preserving behavior.

index_entities_tx silently uses NoSelfIdentity (always false) plus UPSERT_PRESERVE_USER_SQL, meaning on conflict the existing is_user value is preserved rather than being reset to false, but a brand-new row would be inserted with is_user = false. This distinction (preserve-on-conflict vs. insert-as-non-user) is exactly the kind of non-obvious behavior worth calling out inline so callers don't assume NoSelfIdentity simply "skips" identity classification. As per coding guidelines, "Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs."

📝 Proposed doc clarification
-/// Index canonical entities inside the caller's transaction.
+/// Index canonical entities inside the caller's transaction, without identity
+/// classification. Existing rows keep their prior `is_user` flag on conflict
+/// (via `UPSERT_PRESERVE_USER_SQL`); newly inserted rows are marked non-user.
 pub fn index_entities_tx(
🤖 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/memory/store/entity_index/transaction.rs` around lines 10 - 29, Expand
the doc comment for the public function index_entities_tx to document its
identity behavior: new rows are inserted with is_user = false, while conflicts
preserve the existing is_user value via UPSERT_PRESERVE_USER_SQL; clarify that
NoSelfIdentity does not merely skip identity classification.

Source: Coding guidelines

src/memory/store/content/atomic.rs (1)

40-69: 🗄️ Data Integrity & Integration | 🔵 Trivial

Verify workspace filesystem supports hard links.

The publish path now requires hard-link support; filesystems without it (e.g., FAT/exFAT) will make every write_if_new publish fail. Worth confirming the workspace is never expected to live on such a filesystem, since rename (still portable) no longer backs this path.

🤖 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/memory/store/content/atomic.rs` around lines 40 - 69, Verify and enforce
that the workspace filesystem supports hard links before relying on
std::fs::hard_link in the publish path used by write_if_new. Add an appropriate
capability check or documented validation at workspace initialization, and
ensure unsupported filesystems produce a clear error rather than causing
unexplained publish failures.
src/memory/archivist/store.rs (1)

44-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared "sanitize name with collision-resistant digest suffix" helper. Both sites independently reimplement the same char-replace + conditional SHA-256-suffix pattern, and they've already diverged on the empty-input guard, risking further silent divergence as either evolves.

  • src/memory/archivist/store.rs#L44-L67: extract sanitize_session's logic (char whitelist + digest suffix + empty guard) into a shared helper (e.g. in src/memory/fsutil.rs) parameterized by the allowed-character predicate.
  • src/memory/tree/runtime/store/paths.rs#L28-L44: replace sanitize with a call to the same shared helper, picking up the missing !sanitized.is_empty() guard in the process so an empty-after-trim namespace also gets a digest suffix rather than collapsing to "".
🤖 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/memory/archivist/store.rs` around lines 44 - 67, The session and
namespace sanitizers duplicate collision-resistant digest-suffix logic and
diverge on empty results. In src/memory/archivist/store.rs:44-67, extract
sanitize_session’s character replacement, empty guard, and SHA-256 suffix
behavior into a shared helper in src/memory/fsutil.rs parameterized by the
allowed-character predicate; in src/memory/tree/runtime/store/paths.rs:28-44,
replace sanitize with that helper so empty-after-trim values also receive a
digest suffix.
src/memory/store/kv.rs (1)

229-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared JSON-parsing helper for the three row-iteration methods.

list_namespace, records_namespace, and records_global now all parse value_json and format an identical-shaped error (only the function name differs) on failure. Consider extracting a small private helper to avoid three copies of the same pattern.

♻️ Proposed helper extraction
+    fn parse_stored_value(raw: &str, key: &str, context: &str) -> Result<Value, String> {
+        serde_json::from_str(raw).map_err(|e| format!("{context} JSON for key '{key}': {e}"))
+    }
+
     pub fn list_namespace(&self, namespace: &str) -> Result<Vec<Value>, String> {
         ...
-            let value = serde_json::from_str::<Value>(&value_raw)
-                .map_err(|e| format!("list_namespace JSON for key '{key}': {e}"))?;
+            let value = Self::parse_stored_value(&value_raw, &key, "list_namespace")?;

Apply analogously to records_namespace and records_global.

Also applies to: 274-304, 308-334

🤖 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/memory/store/kv.rs` around lines 229 - 257, Extract the repeated
value_json deserialization and contextual error formatting from list_namespace,
records_namespace, and records_global into a shared private helper. Have each
row-iteration method call the helper with the raw JSON and its function context,
preserving the existing error messages and successful Value results.
src/memory/store/store.rs (1)

83-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Term-matching scoring logic duplicated between MemoryStore::search and Memory::recall. Both independently tokenize the query into lowercase whitespace-separated terms, count substring matches against lowercased content, and compute matched/total (returning 1.0/None for empty query / zero matches) — same algorithm, two copies, drifting types (f32 vs f64).

  • src/memory/store/store.rs#L83-L127: extract the term-tokenize + score computation into a shared helper (e.g. in a small scoring module) usable by both search and recall.
  • src/memory/store/memory_trait.rs#L45-L59: replace query_score with a call to the shared helper instead of re-implementing the same matching logic.
  • src/memory/store/memory_trait.rs#L134-L179: update recall's call site to use the shared helper's result instead of the local query_score.
🤖 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/memory/store/store.rs` around lines 83 - 127, Extract the duplicated
lowercase whitespace-term tokenization and matched/total scoring into a shared
helper, using one consistent score type and preserving 1.0 for empty queries and
no result for zero matches. In src/memory/store/store.rs lines 83-127, update
MemoryStore::search to use the helper; in src/memory/store/memory_trait.rs lines
45-59, replace query_score with the helper; and in
src/memory/store/memory_trait.rs lines 134-179, update recall to consume the
helper’s result.
src/memory/chunks/store.rs (1)

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a named constant instead of the bare 'admitted' literal.

CHUNK_STATUS_DROPPED is already a named constant reused across modules (e.g. store_list.rs); hardcoding 'admitted' directly in this SQL string risks silent drift/typos in a lifecycle-critical status value.

♻️ Suggested fix
-        created_at_ms = excluded.created_at_ms,
-        content_path = NULL,
-        content_sha256 = NULL,
-        lifecycle_status = 'admitted'";
+        created_at_ms = excluded.created_at_ms,
+        content_path = NULL,
+        content_sha256 = NULL,
+        lifecycle_status = '" + CHUNK_STATUS_ADMITTED + "'"; // build via format!/const concat
🤖 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/memory/chunks/store.rs` around lines 88 - 94, Replace the hardcoded
'admitted' lifecycle status in the SQL assignment within the chunk store upsert
flow with the existing named status constant, reusing the same symbol as
CHUNK_STATUS_DROPPED and its usage in store_list.rs. Keep the SQL behavior
unchanged while preventing the status value from being duplicated as a string
literal.
🤖 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/memory/chunks/migrations.rs`:
- Around line 166-168: Reorder the global/topic purge migration so filesystem
cleanup of wiki/summaries/global* and topic-* occurs before updating
user_version and committing the transaction. Ensure startup retries that cleanup
when it was interrupted, rather than skipping it solely because the migration
version is already current; apply the same fix to the corresponding second
completion path.

In `@src/memory/chunks/store_list.rs`:
- Around line 92-114: Update the allowlist SQL built in the surrounding
chunk-filtering logic so the prefixed source comparison is case-sensitive,
replacing the SQLite LIKE-based condition while preserving the existing
escaped-prefix matching and exact source_id equality checks. Anchor the change
to the SQL fragment containing source_id LIKE and keep the allowed-source
iteration and bindings behavior unchanged.

In `@src/memory/conversations/bus.rs`:
- Around line 352-375: Update persisted_channel_thread_id and its read path to
preserve compatibility with existing underscore-containing thread IDs: first
look up the legacy unsuffixed base key, then use the hashed key for new or
non-legacy entries, or provide an equivalent migration. Ensure future turns
append to an existing legacy conversation rather than creating a new one, while
retaining collision-safe IDs for newly persisted conversations.

In `@src/memory/queue/runtime/mod.rs`:
- Around line 11-14: Update the module documentation bullet for run_worker to
state that corruption is handled by recover_corrupt_db and the worker continues
polling, replacing the inaccurate claim that corruption is fatal and returned to
the host.

In `@src/memory/queue/worker_tests.rs`:
- Around line 114-133: Serialize
terminal_reembed_failure_clears_process_backfill_flag with the other queue tests
that access the process-wide backfill_in_progress AtomicBool. Reuse the existing
test synchronization mechanism rather than adding a new one, ensuring the flag
is not concurrently modified during this test.

In `@src/memory/retrieval/global.rs`:
- Around line 123-141: Update the documentation comment above resolve_topic_hits
to remove the claim that dropped leaf chunks can surface and state that chunks
are filtered using the fetched dropped_chunk_ids tombstone set. Keep the
existing notes about soft-deleted summaries and stale index entries accurate.
- Around line 78-81: Correct the duplicated “the” in the documentation comment
describing the entity-index timestamp window and configured topic lookup cap,
leaving the surrounding wording unchanged.

In `@src/memory/retrieval/source.rs`:
- Around line 64-68: Update the time_window_days mapping in the retrieval flow
to avoid unchecked subtraction for oversized u32 values. Use checked_sub_signed
(or the existing documented maximum, if one is defined) and propagate an
appropriate error through the function’s Result instead of allowing the
calculation to panic; preserve the current timestamp window behavior for valid
durations.

In `@src/memory/sources/types.rs`:
- Around line 233-271: Update MemorySourcePatch::validate_for_kind to reject
since_days unless kind is SourceKind::TwitterQuery and reject max_items unless
kind is SourceKind::RssFeed, using the existing reject helper and preserving all
current validations.

In `@src/memory/store/vectors/store.rs`:
- Around line 440-455: Update bytes_to_vec to replace usize::is_multiple_of with
the MSRV-compatible remainder check, preserving the existing error behavior for
byte lengths not divisible by four; do not change the decoding logic or require
a Rust version bump.

In `@src/memory/tree/document_seal.rs`:
- Around line 86-104: Update both document-seal transaction paths around
with_connection to query and require the tree status to be "active" before
calling store::update_tree_after_seal_tx. Apply the same archived-tree guard to
the transaction near the existing current_root/current_max check and the second
transaction near lines 265-296, preserving the existing commit and update
behavior for active trees.

In `@src/memory/tree/summarise.rs`:
- Around line 99-107: Update the configuration validation in the relevant
config-validation function in config.rs to require that output_token_budget plus
summary_overhead_reserve_tokens remains below input_token_budget, preserving
positive input headroom for per_input_cap. Keep the existing overhead-reserve
validation and ensure invalid configurations are rejected before summarisation
reaches prepare_summary_prompt.

In `@src/memory/types.rs`:
- Around line 129-178: Update MemoryCategory::from_str to retain the current
built-in and custom: parsing while also treating non-empty unrecognized bare
strings as MemoryCategory::Custom, preserving compatibility with legacy
serialized values. Keep Display and Serialize emitting the new custom:{name}
wire format.

---

Outside diff comments:
In `@src/memory/chunks/connection.rs`:
- Around line 263-300: After acquiring _init_guard in the connection
initialization flow, re-check the relevant breaker’s is_open() before the cache
re-check or any open_and_init call. Return the same breaker-open result used by
the earlier pre-lock check, while preserving the existing cache double-check and
retry behavior for closed breakers.

In `@src/memory/chunks/store.rs`:
- Around line 73-94: The UPSERT_SQL path clears the previous content_path
without reclaiming its staged blob. Update the plain upsert flow around
UPSERT_SQL to capture and remove the existing content_path file before or as
part of replacing the row, while preserving the current reset of content_path
and content_sha256; reuse the existing blob-reclamation mechanism from the store
deletion flow rather than introducing separate cleanup behavior.

In `@src/memory/conversations/inverted_index.rs`:
- Around line 337-346: Update the doc comment for candidates_for_term to match
the current search behavior: explain that the None branch intentionally performs
an uncapped full-corpus scan and does not trigger recency_fallback, avoiding
fabricated score-0.0 hits for short or single-CJK terms at the cost of scanning
all live documents. Remove the obsolete LARGE_CANDIDATE_LIMIT and recency-only
fallback claim while preserving the existing explanation of why this branch
occurs.

In `@src/memory/conversations/store_index.rs`:
- Around line 158-197: Change list_threads_unlocked and the corresponding
thread_summary_unlocked path so normal reads do not call
measure_messages_unlocked for every thread. Restrict message-file reconciliation
to cold-start/recovery cases or reuse a workspace-level marker indicating
reconciliation has already completed, while preserving repair behavior when
reconciliation is required and preventing one unreadable thread file from
failing routine list/summary requests.

In `@src/memory/queue/store_settle.rs`:
- Line 1: Update mark_done_with_followups to return Result<bool>, with the
boolean indicating whether the claim-token update matched a row; return false
for stale-claim no-ops. In settle_planned_job’s Done branch, capture this result
and only call set_backfill_in_progress(true) when the settlement succeeded,
preventing follow-up state from being updated after an uncommitted completion.

In `@src/memory/score/mod.rs`:
- Around line 371-398: Make persist_score atomic by executing upsert_score,
clear_entity_index_for_node, and the conditional index_entities operation within
one database transaction. Reuse the existing persist_score_tx transaction path
if available, ensuring any failure rolls back both the score row and
entity-index changes together.

In `@src/memory/store/vectors/store.rs`:
- Around line 198-218: Update the documentation for insert_with_vector to
reflect that it validates embedding.len() against self.backend.dimensions() and
rejects mismatched vectors immediately with an error. Remove the outdated
description of storing invalid vectors and silently excluding them from search
results.
- Around line 88-91: Update the documentation for open() and the related
check_or_store_meta helper to remove references to the already-fixed gaps
involving .ok() collapsing errors and .parse().unwrap_or(0). Describe the
current fail-closed behavior accurately, without changing the implementation.

In `@src/memory/sync/dispatcher.rs`:
- Around line 122-328: Move the inline `mod tests` block from `dispatcher.rs`
into a sibling `dispatcher_tests.rs` module, preserving all existing test
helpers and cases including `FakePipeline`, `NoopHost`, and the three test
functions. Wire the external test module using the project’s established module
declaration pattern, and keep implementation code in `dispatcher.rs` unchanged.

In `@src/memory/tool_memory/store.rs`:
- Around line 180-225: Update the documentation for rules_for_prompt to match
the critical_count and truncate behavior: state that all Critical rules are
retained and never dropped, while TOOL_MEMORY_PROMPT_CAP only bounds the
retained High-priority rules. Remove the outdated hard-cap and silent
Critical-drop claims, and mention that the result can exceed the cap when
Critical rules are numerous.
- Around line 68-104: The documentation around put_rule is stale: remove or
revise the claims that tool_name is stored verbatim and that embedded newlines
can forge prompt sections, and if retaining the casing note, mention that only
newly written values are lower-cased while older mixed-case rows persist until
rewritten. Update the rules_for_prompt documentation to state that truncation
preserves all Critical rules and limits only the non-critical remainder.

---

Nitpick comments:
In `@src/memory/archivist/store.rs`:
- Around line 44-67: The session and namespace sanitizers duplicate
collision-resistant digest-suffix logic and diverge on empty results. In
src/memory/archivist/store.rs:44-67, extract sanitize_session’s character
replacement, empty guard, and SHA-256 suffix behavior into a shared helper in
src/memory/fsutil.rs parameterized by the allowed-character predicate; in
src/memory/tree/runtime/store/paths.rs:28-44, replace sanitize with that helper
so empty-after-trim values also receive a digest suffix.

In `@src/memory/chunks/connection_breaker.rs`:
- Around line 11-49: Add Rust doc comments to CircuitBreaker::new,
record_success, record_failure, and is_open describing initialization, the
meaning of each bool return value, failure-threshold behavior, and cooldown
semantics. Keep the implementation unchanged and align the item documentation
with the existing module-level CircuitBreaker contract.
- Around line 32-49: Make the breaker state transition atomic by consolidating
tripped, consecutive_failures, and last_trip into one Mutex-guarded state
structure. Update record_failure and is_open to access and mutate this shared
state under the same lock, ensuring is_open cannot observe tripped without its
corresponding trip timestamp.

In `@src/memory/chunks/store.rs`:
- Around line 88-94: Replace the hardcoded 'admitted' lifecycle status in the
SQL assignment within the chunk store upsert flow with the existing named status
constant, reusing the same symbol as CHUNK_STATUS_DROPPED and its usage in
store_list.rs. Keep the SQL behavior unchanged while preventing the status value
from being duplicated as a string literal.

In `@src/memory/config/policy.rs`:
- Around line 52-78: Move direct validator coverage for
RetrievalLimits::validate, ScoringPolicyConfig::validate, and
QueueConfig::validate out of config_tests.rs into a dedicated policy_tests.rs
sibling of the policy module. Add focused tests for each validator’s accepted
and rejected bounds while retaining MemoryConfig::validate integration coverage
where appropriate.

In `@src/memory/diff/ledger_helpers.rs`:
- Around line 13-16: Add Rust doc comments to checkpoint_message,
checkpoint_from_message, validate_source_id, and build_commit_message
documenting their wire-schema or validation/trailer-layout contracts, including
relevant rejection and formatting behavior. Follow the existing documentation
style used by parse_trailers, sanitize_trailer, and encode_item_id; do not
change implementation behavior.

In `@src/memory/queue/store_settle.rs`:
- Line 1: Share the 7-day max_defer_age_ms default instead of duplicating it
across settlement and policy code. Update the defer-age boundary in
store_settle.rs and store_settle_tests.rs to reuse MemoryConfig::default() or a
shared constant from policy.rs, while preserving the existing settlement
behavior and test boundary.

In `@src/memory/score/store_query.rs`:
- Around line 1-92: Move focused unit tests for the diagnostics-query functions
in store_query, including lookup_entity_in_window and list_entity_ids_for_node,
from store_tests.rs into a co-located store_query_tests.rs sibling under src.
Keep transactional or cross-module integration tests in store_tests.rs, and
preserve the existing test coverage and behavior.

In `@src/memory/sources/readers/folder.rs`:
- Around line 117-125: Update the default pattern used in read_item to reference
the existing DEFAULT_GLOB constant instead of hardcoding "**/*.md". Keep the
explicit source.glob value and subsequent glob matching behavior unchanged.

In `@src/memory/sources/registry.rs`:
- Around line 183-185: Extract a private lock-acquisition helper for
REGISTRY_MUTATION_LOCK that performs the existing lock call and panic message,
then replace the repeated boilerplate in all mutating methods, including update,
remove, remove_composio_source_by_connection_id, upsert_composio_source,
upsert_composio_sources_batch, apply_all_in, and the method shown. Keep each
method’s guard held for its full mutation scope.

In `@src/memory/store/content/atomic.rs`:
- Around line 40-69: Verify and enforce that the workspace filesystem supports
hard links before relying on std::fs::hard_link in the publish path used by
write_if_new. Add an appropriate capability check or documented validation at
workspace initialization, and ensure unsupported filesystems produce a clear
error rather than causing unexplained publish failures.

In `@src/memory/store/entity_index/transaction.rs`:
- Around line 10-29: Expand the doc comment for the public function
index_entities_tx to document its identity behavior: new rows are inserted with
is_user = false, while conflicts preserve the existing is_user value via
UPSERT_PRESERVE_USER_SQL; clarify that NoSelfIdentity does not merely skip
identity classification.

In `@src/memory/store/kv.rs`:
- Around line 229-257: Extract the repeated value_json deserialization and
contextual error formatting from list_namespace, records_namespace, and
records_global into a shared private helper. Have each row-iteration method call
the helper with the raw JSON and its function context, preserving the existing
error messages and successful Value results.

In `@src/memory/store/safety/pii/checks.rs`:
- Around line 1-232: Add a sibling checks_tests.rs module containing direct
edge-case tests for valid_cpf, valid_cnpj, valid_cuit, valid_luhn, valid_iban,
valid_verhoeff, valid_ssn, valid_dni_es, valid_nie_es, and valid_nino. Cover
representative valid inputs plus invalid lengths, checksums, reserved values,
prefixes, and boundary rules, and structure the tests so the private validators
are exercised through the module’s test setup.

In `@src/memory/store/store.rs`:
- Around line 83-127: Extract the duplicated lowercase whitespace-term
tokenization and matched/total scoring into a shared helper, using one
consistent score type and preserving 1.0 for empty queries and no result for
zero matches. In src/memory/store/store.rs lines 83-127, update
MemoryStore::search to use the helper; in src/memory/store/memory_trait.rs lines
45-59, replace query_score with the helper; and in
src/memory/store/memory_trait.rs lines 134-179, update recall to consume the
helper’s result.

In `@src/memory/store/vectors/store_tests_more.rs`:
- Around line 31-45: Update search_handles_invalid_metadata_json to create the
vector using the store’s normal insertion path, then execute a targeted UPDATE
that sets only its metadata to invalid JSON. Remove the raw INSERT’s unrelated
column values while preserving the existing search and null-metadata assertions.

In `@src/memory/sync/composio/providers/slack_parse.rs`:
- Around line 10-81: Add item-level documentation comments to each pub(super)
helper: mention_regex, replace_mentions, next_cursor, search_matches,
search_total_pages, decode_cursors, and parse_ts. Document their purpose and the
non-obvious fallback paths, mention resolution behavior, cursor defaults, and
timestamp parsing contract without changing implementation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Comment thread src/memory/chunks/migrations.rs Outdated
Comment thread src/memory/chunks/store_list.rs
Comment thread src/memory/conversations/bus.rs
Comment thread src/memory/queue/runtime/mod.rs Outdated
Comment thread src/memory/queue/worker_tests.rs
Comment thread src/memory/sources/types.rs
Comment thread src/memory/store/vectors/store.rs
Comment thread src/memory/tree/document_seal.rs
Comment thread src/memory/tree/summarise.rs
Comment thread src/memory/types.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e8ee978680

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/memory/store/memory_trait.rs
Comment thread src/memory/retrieval/source.rs
Comment thread src/memory/ingest/pipeline.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 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/memory/mod.rs`:
- Around line 84-91: Update the documentation comment above the feature-gated
providers module in memory::providers to state that the providers-http feature
currently exposes the concrete OpenRouterProvider, removing the stale claim that
concrete providers will arrive with C3/M3. Preserve the existing feature-gating
and dependency descriptions.

In `@src/memory/persona/types.rs`:
- Around line 298-307: Update Persona’s source_id() to use an unambiguous
canonical encoding of kind, scope, session_id, and path, such as length-prefixed
fields or structured serialization, instead of joining values with "|". Preserve
deterministic output and ensure distinct provenance values remain distinct when
consumed by evidence_id().
- Around line 329-345: Update PersonaEvidence deserialization to route input
through PersonaEvidence::new, ensuring the excerpt is redacted and the
content-addressed id is recomputed from the source and excerpt rather than
trusting serialized id/excerpt values. Replace the derived Deserialize
implementation with a custom one or remove deserialization support if no longer
required, while preserving valid serialized fields such as timestamp, tier, and
facets.

In `@src/memory/providers/openrouter.rs`:
- Around line 312-330: Replace the shared usage snapshot subtraction around the
summary call with request-local usage returned by chat() or its accounting path.
Update the relevant chat() result and SummaryCall construction so input_tokens,
output_tokens, and charged_amount_usd reflect only this request, while
preserving the existing summary output handling.
- Around line 137-180: Make request accounting in post_json count and admit
every retry attempt atomically, independently of whether the response includes
optional usage data; adjust check_budget and the attempt-accounting flow so
limits are enforced per attempt without relying on record_usage. In
src/memory/providers/openrouter.rs lines 137-180, update the relevant budget and
usage methods while preserving token and cost accumulation. In
src/memory/providers/openrouter_tests.rs lines 81-85, change the
429-then-success expectation to count both attempts and add coverage for
responses without usage data.
- Around line 200-206: Update the response handling in post_json so
r.text().await propagates body-read errors instead of converting them to an
empty string with unwrap_or_default(). Ensure the propagated error exits through
the existing retry path, while successful body reads continue to use the current
status and JSON-decoding logic.
- Around line 369-395: Update the embeddings response handling around the `out`
vector and item loop to validate the payload strictly: require each embedding
value to be numeric, require every embedding to match the expected dimension,
reject duplicate or out-of-range `index` values, and ensure all expected input
positions are populated so short responses cannot succeed. Add wiremock
regression coverage for short responses, duplicate/out-of-range indices,
non-numeric values, and incorrect embedding lengths.

In `@src/memory/store/content/atomic.rs`:
- Around line 62-66: Update the hard-link error handling in the atomic publish
flow to preserve the original error for failures unrelated to unsupported
filesystem semantics. Only wrap the error as a capability issue when the failure
specifically indicates an unsupported hard-link operation; otherwise propagate
or retain the original `e`, including permission, ENOSPC, and invalid-path
errors.

In `@src/memory/tree/runtime/store/paths.rs`:
- Around line 33-38: The path resolution logic using
sanitize_component_with_digest in src/memory/tree/runtime/store/paths.rs lines
31-38 and the corresponding logic in src/memory/archivist/store.rs lines 44-46
must probe the legacy digest-suffixed directory before using the transformed
namespace/session location, preserving access to data written by older versions;
update both sites consistently, or migrate the legacy directory when
encountered.
🪄 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: 3d94bb28-23b0-40f4-9d08-28f1ba5c1fb6

📥 Commits

Reviewing files that changed from the base of the PR and between 95454a6 and e8ee978.

📒 Files selected for processing (61)
  • Cargo.toml
  • src/memory/archivist/store.rs
  • src/memory/chunks/connection.rs
  • src/memory/chunks/connection_breaker.rs
  • src/memory/chunks/migrations.rs
  • src/memory/chunks/produce.rs
  • src/memory/chunks/store.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/chunks/store_list.rs
  • src/memory/config.rs
  • src/memory/config/policy.rs
  • src/memory/config/policy_tests.rs
  • src/memory/conversations/bus.rs
  • src/memory/conversations/inverted_index.rs
  • src/memory/conversations/store_index.rs
  • src/memory/conversations/store_tests_more.rs
  • src/memory/diff/ledger_helpers.rs
  • src/memory/fsutil.rs
  • src/memory/ingest/pipeline.rs
  • src/memory/ingest/pipeline_tests.rs
  • src/memory/ingest/types.rs
  • src/memory/mod.rs
  • src/memory/persona/mod.rs
  • src/memory/persona/types.rs
  • src/memory/providers/openrouter.rs
  • src/memory/providers/openrouter_tests.rs
  • src/memory/queue/ops_tests.rs
  • src/memory/queue/runtime/mod.rs
  • src/memory/queue/store.rs
  • src/memory/queue/store_settle.rs
  • src/memory/queue/store_settle_tests.rs
  • src/memory/queue/test_support.rs
  • src/memory/queue/worker.rs
  • src/memory/queue/worker_tests.rs
  • src/memory/retrieval/global.rs
  • src/memory/retrieval/source.rs
  • src/memory/score/mod.rs
  • src/memory/score/store_query.rs
  • src/memory/score/store_query_tests.rs
  • src/memory/sources/readers/folder.rs
  • src/memory/sources/registry.rs
  • src/memory/sources/types.rs
  • src/memory/store/content/atomic.rs
  • src/memory/store/entity_index/transaction.rs
  • src/memory/store/kv.rs
  • src/memory/store/memory_trait.rs
  • src/memory/store/mod.rs
  • src/memory/store/safety/pii/checks.rs
  • src/memory/store/safety/pii/checks_tests.rs
  • src/memory/store/store.rs
  • src/memory/store/vectors/store.rs
  • src/memory/store/vectors/store_tests_more.rs
  • src/memory/sync/composio/providers/slack_parse.rs
  • src/memory/sync/dispatcher.rs
  • src/memory/sync/dispatcher_tests.rs
  • src/memory/tool_memory/store.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/tree/document_seal.rs
  • src/memory/tree/runtime/store/paths.rs
  • src/memory/types.rs
  • src/memory/types_tests.rs
🚧 Files skipped from review as they are similar to previous changes (34)
  • src/memory/queue/store_settle_tests.rs
  • src/memory/ingest/types.rs
  • src/memory/queue/worker_tests.rs
  • src/memory/store/kv.rs
  • src/memory/config/policy.rs
  • src/memory/sync/composio/providers/slack_parse.rs
  • src/memory/tool_memory/store.rs
  • src/memory/queue/worker.rs
  • src/memory/chunks/produce.rs
  • src/memory/retrieval/source.rs
  • src/memory/chunks/store_list.rs
  • src/memory/types_tests.rs
  • src/memory/score/store_query.rs
  • src/memory/store/vectors/store_tests_more.rs
  • src/memory/tree/document_seal.rs
  • src/memory/store/entity_index/transaction.rs
  • src/memory/conversations/store_index.rs
  • src/memory/ingest/pipeline_tests.rs
  • src/memory/store/safety/pii/checks.rs
  • src/memory/score/mod.rs
  • src/memory/store/vectors/store.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/sources/readers/folder.rs
  • src/memory/types.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/ingest/pipeline.rs
  • src/memory/retrieval/global.rs
  • src/memory/conversations/store_tests_more.rs
  • src/memory/sources/registry.rs
  • Cargo.toml
  • src/memory/config.rs
  • src/memory/conversations/inverted_index.rs
  • src/memory/diff/ledger_helpers.rs
  • src/memory/chunks/connection.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

🤖 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/memory/mod.rs`:
- Around line 84-91: Update the documentation comment above the feature-gated
providers module in memory::providers to state that the providers-http feature
currently exposes the concrete OpenRouterProvider, removing the stale claim that
concrete providers will arrive with C3/M3. Preserve the existing feature-gating
and dependency descriptions.

In `@src/memory/persona/types.rs`:
- Around line 298-307: Update Persona’s source_id() to use an unambiguous
canonical encoding of kind, scope, session_id, and path, such as length-prefixed
fields or structured serialization, instead of joining values with "|". Preserve
deterministic output and ensure distinct provenance values remain distinct when
consumed by evidence_id().
- Around line 329-345: Update PersonaEvidence deserialization to route input
through PersonaEvidence::new, ensuring the excerpt is redacted and the
content-addressed id is recomputed from the source and excerpt rather than
trusting serialized id/excerpt values. Replace the derived Deserialize
implementation with a custom one or remove deserialization support if no longer
required, while preserving valid serialized fields such as timestamp, tier, and
facets.

In `@src/memory/providers/openrouter.rs`:
- Around line 312-330: Replace the shared usage snapshot subtraction around the
summary call with request-local usage returned by chat() or its accounting path.
Update the relevant chat() result and SummaryCall construction so input_tokens,
output_tokens, and charged_amount_usd reflect only this request, while
preserving the existing summary output handling.
- Around line 137-180: Make request accounting in post_json count and admit
every retry attempt atomically, independently of whether the response includes
optional usage data; adjust check_budget and the attempt-accounting flow so
limits are enforced per attempt without relying on record_usage. In
src/memory/providers/openrouter.rs lines 137-180, update the relevant budget and
usage methods while preserving token and cost accumulation. In
src/memory/providers/openrouter_tests.rs lines 81-85, change the
429-then-success expectation to count both attempts and add coverage for
responses without usage data.
- Around line 200-206: Update the response handling in post_json so
r.text().await propagates body-read errors instead of converting them to an
empty string with unwrap_or_default(). Ensure the propagated error exits through
the existing retry path, while successful body reads continue to use the current
status and JSON-decoding logic.
- Around line 369-395: Update the embeddings response handling around the `out`
vector and item loop to validate the payload strictly: require each embedding
value to be numeric, require every embedding to match the expected dimension,
reject duplicate or out-of-range `index` values, and ensure all expected input
positions are populated so short responses cannot succeed. Add wiremock
regression coverage for short responses, duplicate/out-of-range indices,
non-numeric values, and incorrect embedding lengths.

In `@src/memory/store/content/atomic.rs`:
- Around line 62-66: Update the hard-link error handling in the atomic publish
flow to preserve the original error for failures unrelated to unsupported
filesystem semantics. Only wrap the error as a capability issue when the failure
specifically indicates an unsupported hard-link operation; otherwise propagate
or retain the original `e`, including permission, ENOSPC, and invalid-path
errors.

In `@src/memory/tree/runtime/store/paths.rs`:
- Around line 33-38: The path resolution logic using
sanitize_component_with_digest in src/memory/tree/runtime/store/paths.rs lines
31-38 and the corresponding logic in src/memory/archivist/store.rs lines 44-46
must probe the legacy digest-suffixed directory before using the transformed
namespace/session location, preserving access to data written by older versions;
update both sites consistently, or migrate the legacy directory when
encountered.
🪄 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: 3d94bb28-23b0-40f4-9d08-28f1ba5c1fb6

📥 Commits

Reviewing files that changed from the base of the PR and between 95454a6 and e8ee978.

📒 Files selected for processing (61)
  • Cargo.toml
  • src/memory/archivist/store.rs
  • src/memory/chunks/connection.rs
  • src/memory/chunks/connection_breaker.rs
  • src/memory/chunks/migrations.rs
  • src/memory/chunks/produce.rs
  • src/memory/chunks/store.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/chunks/store_list.rs
  • src/memory/config.rs
  • src/memory/config/policy.rs
  • src/memory/config/policy_tests.rs
  • src/memory/conversations/bus.rs
  • src/memory/conversations/inverted_index.rs
  • src/memory/conversations/store_index.rs
  • src/memory/conversations/store_tests_more.rs
  • src/memory/diff/ledger_helpers.rs
  • src/memory/fsutil.rs
  • src/memory/ingest/pipeline.rs
  • src/memory/ingest/pipeline_tests.rs
  • src/memory/ingest/types.rs
  • src/memory/mod.rs
  • src/memory/persona/mod.rs
  • src/memory/persona/types.rs
  • src/memory/providers/openrouter.rs
  • src/memory/providers/openrouter_tests.rs
  • src/memory/queue/ops_tests.rs
  • src/memory/queue/runtime/mod.rs
  • src/memory/queue/store.rs
  • src/memory/queue/store_settle.rs
  • src/memory/queue/store_settle_tests.rs
  • src/memory/queue/test_support.rs
  • src/memory/queue/worker.rs
  • src/memory/queue/worker_tests.rs
  • src/memory/retrieval/global.rs
  • src/memory/retrieval/source.rs
  • src/memory/score/mod.rs
  • src/memory/score/store_query.rs
  • src/memory/score/store_query_tests.rs
  • src/memory/sources/readers/folder.rs
  • src/memory/sources/registry.rs
  • src/memory/sources/types.rs
  • src/memory/store/content/atomic.rs
  • src/memory/store/entity_index/transaction.rs
  • src/memory/store/kv.rs
  • src/memory/store/memory_trait.rs
  • src/memory/store/mod.rs
  • src/memory/store/safety/pii/checks.rs
  • src/memory/store/safety/pii/checks_tests.rs
  • src/memory/store/store.rs
  • src/memory/store/vectors/store.rs
  • src/memory/store/vectors/store_tests_more.rs
  • src/memory/sync/composio/providers/slack_parse.rs
  • src/memory/sync/dispatcher.rs
  • src/memory/sync/dispatcher_tests.rs
  • src/memory/tool_memory/store.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/tree/document_seal.rs
  • src/memory/tree/runtime/store/paths.rs
  • src/memory/types.rs
  • src/memory/types_tests.rs
🚧 Files skipped from review as they are similar to previous changes (34)
  • src/memory/queue/store_settle_tests.rs
  • src/memory/ingest/types.rs
  • src/memory/queue/worker_tests.rs
  • src/memory/store/kv.rs
  • src/memory/config/policy.rs
  • src/memory/sync/composio/providers/slack_parse.rs
  • src/memory/tool_memory/store.rs
  • src/memory/queue/worker.rs
  • src/memory/chunks/produce.rs
  • src/memory/retrieval/source.rs
  • src/memory/chunks/store_list.rs
  • src/memory/types_tests.rs
  • src/memory/score/store_query.rs
  • src/memory/store/vectors/store_tests_more.rs
  • src/memory/tree/document_seal.rs
  • src/memory/store/entity_index/transaction.rs
  • src/memory/conversations/store_index.rs
  • src/memory/ingest/pipeline_tests.rs
  • src/memory/store/safety/pii/checks.rs
  • src/memory/score/mod.rs
  • src/memory/store/vectors/store.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/sources/readers/folder.rs
  • src/memory/types.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/ingest/pipeline.rs
  • src/memory/retrieval/global.rs
  • src/memory/conversations/store_tests_more.rs
  • src/memory/sources/registry.rs
  • Cargo.toml
  • src/memory/config.rs
  • src/memory/conversations/inverted_index.rs
  • src/memory/diff/ledger_helpers.rs
  • src/memory/chunks/connection.rs
🛑 Comments failed to post (9)
src/memory/mod.rs (1)

84-91: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the providers feature documentation.

OpenRouterProvider is now a concrete provider under memory::providers, but this text says concrete providers will land with C3/M3. Replace the stale wording so users understand what the feature currently provides.

🤖 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/memory/mod.rs` around lines 84 - 91, Update the documentation comment
above the feature-gated providers module in memory::providers to state that the
providers-http feature currently exposes the concrete OpenRouterProvider,
removing the stale claim that concrete providers will arrive with C3/M3.
Preserve the existing feature-gating and dependency descriptions.
src/memory/persona/types.rs (2)

298-307: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make source_id() unambiguous.

Joining provenance fields with | allows different (scope, session_id, path) values containing | to produce the same source ID. Because this value feeds evidence_id(), unrelated evidence can collide and deduplicate incorrectly. Use length-prefixed or canonical structured encoding.

🤖 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/memory/persona/types.rs` around lines 298 - 307, Update Persona’s
source_id() to use an unambiguous canonical encoding of kind, scope, session_id,
and path, such as length-prefixed fields or structured serialization, instead of
joining values with "|". Preserve deterministic output and ensure distinct
provenance values remain distinct when consumed by evidence_id().

329-345: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and locate PersonaEvidence-related items.
ast-grep outline src/memory/persona/types.rs --view expanded || true

printf '\n--- rg hits ---\n'
rg -n "PersonaEvidence|redact_pii|Deserialize|serde|new\\(" src/memory/persona/types.rs

Repository: tinyhumansai/tinycortex

Length of output: 4186


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '320,390p' src/memory/persona/types.rs | cat -n

Repository: tinyhumansai/tinycortex

Length of output: 3092


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,40p' src/memory/persona/types.rs | cat -n

Repository: tinyhumansai/tinycortex

Length of output: 2390


Make PersonaEvidence deserialization go through the redacting constructor

#[derive(Deserialize)] lets serde populate the private excerpt and arbitrary id directly from JSON/on-disk data, bypassing PersonaEvidence::new and the redaction/content-addressing invariant. Deserialize through new (or remove Deserialize), and keep id/source from drifting out of sync with the stored excerpt.

🤖 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/memory/persona/types.rs` around lines 329 - 345, Update PersonaEvidence
deserialization to route input through PersonaEvidence::new, ensuring the
excerpt is redacted and the content-addressed id is recomputed from the source
and excerpt rather than trusting serialized id/excerpt values. Replace the
derived Deserialize implementation with a custom one or remove deserialization
support if no longer required, while preserving valid serialized fields such as
timestamp, tier, and facets.
src/memory/providers/openrouter.rs (4)

137-180: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use one consistent per-attempt request-accounting contract.

  • src/memory/providers/openrouter.rs#L137-L180: count and admit each retry attempt atomically, independently of optional token-usage data.
  • src/memory/providers/openrouter_tests.rs#L81-L85: update the 429-then-success expectation to include both attempts and cover usage-less responses.
📍 Affects 2 files
  • src/memory/providers/openrouter.rs#L137-L180 (this comment)
  • src/memory/providers/openrouter_tests.rs#L81-L85
🤖 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/memory/providers/openrouter.rs` around lines 137 - 180, Make request
accounting in post_json count and admit every retry attempt atomically,
independently of whether the response includes optional usage data; adjust
check_budget and the attempt-accounting flow so limits are enforced per attempt
without relying on record_usage. In src/memory/providers/openrouter.rs lines
137-180, update the relevant budget and usage methods while preserving token and
cost accumulation. In src/memory/providers/openrouter_tests.rs lines 81-85,
change the 429-then-success expectation to count both attempts and add coverage
for responses without usage data.

200-206: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail
wc -l src/memory/providers/openrouter.rs
sed -n '1,280p' src/memory/providers/openrouter.rs
rg -n "retry|retri|transport|body-read|unwrap_or_default|text\(\)\.await|with_context" src/memory/providers/openrouter.rs

Repository: tinyhumansai/tinycortex

Length of output: 12124


🏁 Script executed:

set -euo pipefail
# Inspect the surrounding retry logic and any helper wrappers used by OpenRouter requests.
ast-grep outline src/memory/providers/openrouter.rs --view expanded

Repository: tinyhumansai/tinycortex

Length of output: 1973


Retry body-read failures. r.text().await.unwrap_or_default() turns a transient response-body read error into an empty payload, so a 200 can fail at decode time and bypass the retry loop. Propagate that error through post_json instead of defaulting to "".

🤖 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/memory/providers/openrouter.rs` around lines 200 - 206, Update the
response handling in post_json so r.text().await propagates body-read errors
instead of converting them to an empty string with unwrap_or_default(). Ensure
the propagated error exits through the existing retry path, while successful
body reads continue to use the current status and JSON-decoding logic.

312-330: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not derive per-call usage from shared snapshots.

Concurrent summaries can overlap between before and after, causing input_tokens, output_tokens, and charged_amount_usd to include other calls. Return request-local usage from chat()/accounting instead of subtracting global snapshots.

🤖 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/memory/providers/openrouter.rs` around lines 312 - 330, Replace the
shared usage snapshot subtraction around the summary call with request-local
usage returned by chat() or its accounting path. Update the relevant chat()
result and SummaryCall construction so input_tokens, output_tokens, and
charged_amount_usd reflect only this request, while preserving the existing
summary output handling.

369-395: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILE INFO\n'
wc -l src/memory/providers/openrouter.rs
printf '\nRELEVANT LINES\n'
sed -n '330,430p' src/memory/providers/openrouter.rs
printf '\nTEST SEARCH\n'
rg -n "OpenRouter embeddings|embedding.*dimension|missing data\[\]|returned fewer vectors|index" src/memory -g '*rs'

Repository: tinyhumansai/tinycortex

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILE INFO\n'
wc -l src/memory/providers/openrouter.rs
printf '\nRELEVANT LINES\n'
sed -n '330,430p' src/memory/providers/openrouter.rs
printf '\nTEST SEARCH\n'
rg -n "OpenRouter embeddings|embedding.*dimension|missing data\\[\\]|returned fewer vectors|index" src/memory -g '*rs'

Repository: tinyhumansai/tinycortex

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' src/memory/providers/openrouter_tests.rs

Repository: tinyhumansai/tinycortex

Length of output: 6843


Validate the embeddings payload shape. out is sized from data.len(), so a short response can return Ok with fewer vectors than requested. Reject duplicate/out-of-range indices, non-numeric values, and wrong-length embeddings instead of silently accepting them; add wiremock regressions for those cases.

🤖 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/memory/providers/openrouter.rs` around lines 369 - 395, Update the
embeddings response handling around the `out` vector and item loop to validate
the payload strictly: require each embedding value to be numeric, require every
embedding to match the expected dimension, reject duplicate or out-of-range
`index` values, and ensure all expected input positions are populated so short
responses cannot succeed. Add wiremock regression coverage for short responses,
duplicate/out-of-range indices, non-numeric values, and incorrect embedding
lengths.
src/memory/store/content/atomic.rs (1)

62-66: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file shape first, then read the relevant slice.
ast-grep outline src/memory/store/content/atomic.rs --view expanded || true
printf '\n--- file excerpt ---\n'
cat -n src/memory/store/content/atomic.rs | sed -n '1,220p'

Repository: tinyhumansai/tinycortex

Length of output: 8386


Preserve the original hard-link error

hard_link can fail for reasons other than unsupported filesystem semantics (for example permissions, ENOSPC, or invalid paths). If abs_path does not exist, this branch still rewrites every failure as a filesystem-capability issue, so keep the original e or only special-case the unsupported-operation case.

🤖 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/memory/store/content/atomic.rs` around lines 62 - 66, Update the
hard-link error handling in the atomic publish flow to preserve the original
error for failures unrelated to unsupported filesystem semantics. Only wrap the
error as a capability issue when the failure specifically indicates an
unsupported hard-link operation; otherwise propagate or retain the original `e`,
including permission, ENOSPC, and invalid-path errors.
src/memory/tree/runtime/store/paths.rs (1)

33-38: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files src/memory/tree/runtime/store/paths.rs src/memory/archivist/store.rs src/memory/fsutil.rs

printf '\n== Outlines ==\n'
ast-grep outline src/memory/tree/runtime/store/paths.rs --view expanded || true
ast-grep outline src/memory/archivist/store.rs --view expanded || true
ast-grep outline src/memory/fsutil.rs --view expanded || true

printf '\n== Relevant excerpts ==\n'
for f in src/memory/tree/runtime/store/paths.rs src/memory/archivist/store.rs src/memory/fsutil.rs; do
  echo "--- $f ---"
  nl -ba "$f" | sed -n '1,220p'
done

Repository: tinyhumansai/tinycortex

Length of output: 1676


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Search for legacy/compatibility/migration around these stores ==\n'
rg -n "legacy|migrat|compat|fallback|sanitize_component_with_digest|digest-suffixed|namespace directory|session directory" src/memory -S

printf '\n== Search for filesystem key/path helpers used by store paths ==\n'
rg -n "sanitize_component_with_digest|sanitize_component|fsutil" src/memory -S

Repository: tinyhumansai/tinycortex

Length of output: 31974


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Check surrounding tests or docs for persistence semantics ==\n'
rg -n "tree/runtime/store/paths|archivist/store|sanitize_component_with_digest|path.*compat|legacy.*path|digest" src tests -S || true

Repository: tinyhumansai/tinycortex

Length of output: 18690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the two target files with line numbers, plus the shared helper.
for f in src/memory/tree/runtime/store/paths.rs src/memory/archivist/store.rs src/memory/fsutil.rs; do
  echo "--- $f ---"
  nl -ba "$f" | sed -n '1,220p'
done

Repository: tinyhumansai/tinycortex

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/memory/tree/runtime/store/paths.rs ==="
nl -ba src/memory/tree/runtime/store/paths.rs | sed -n '1,140p'
echo
echo "=== src/memory/archivist/store.rs ==="
nl -ba src/memory/archivist/store.rs | sed -n '1,140p'
echo
echo "=== src/memory/fsutil.rs ==="
nl -ba src/memory/fsutil.rs | sed -n '1,220p'
echo
echo "=== references to sanitize_component_with_digest ==="
rg -n "sanitize_component_with_digest" src/memory -S

Repository: tinyhumansai/tinycortex

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show() {
  f="$1"
  start="$2"
  end="$3"
  echo "--- $f:$start-$end ---"
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e { printf "%4d  %s\n", NR, $0 }' "$f"
  echo
}

show src/memory/tree/runtime/store/paths.rs 1 120
show src/memory/archivist/store.rs 1 220
show src/memory/fsutil.rs 1 140

printf '\n== search for alternative legacy path handling in nearby modules ==\n'
rg -n "tree_dir\\(|buffer_dir\\(|node_file_path\\(|session_dir\\(|sanitize_session\\(|sanitize\\(" src/memory/tree src/memory/archivist -S

printf '\n== search for migration/fallback around these stores ==\n'
rg -n "legacy.*(path|dir)|fallback.*(path|dir)|migrat.*(path|dir)|compat.*(path|dir)" src/memory/tree src/memory/archivist -S

Repository: tinyhumansai/tinycortex

Length of output: 20258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== tests referencing tree/store paths or archivist sessions ==\n'
rg -n "tree_dir|buffer_dir|node_file_path|session_dir|session_entries|record_turn|sanitize_component_with_digest" src/memory/tree src/memory/archivist tests -S

printf '\n== file inventory for relevant tests ==\n'
git ls-files 'src/memory/tree/**/*tests*.rs' 'src/memory/archivist/**/*tests*.rs' 'tests/**/*.rs'

Repository: tinyhumansai/tinycortex

Length of output: 7116


Add a legacy fallback for digest-suffixed directories. Transformed namespaces/sessions now map to a different on-disk directory, so previously written data can disappear on upgrade unless you probe or migrate the legacy location first.

  • src/memory/tree/runtime/store/paths.rs#L31-L38
  • src/memory/archivist/store.rs#L43-L46
📍 Affects 2 files
  • src/memory/tree/runtime/store/paths.rs#L33-L38 (this comment)
  • src/memory/archivist/store.rs#L44-L46
🤖 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/memory/tree/runtime/store/paths.rs` around lines 33 - 38, The path
resolution logic using sanitize_component_with_digest in
src/memory/tree/runtime/store/paths.rs lines 31-38 and the corresponding logic
in src/memory/archivist/store.rs lines 44-46 must probe the legacy
digest-suffixed directory before using the transformed namespace/session
location, preserving access to data written by older versions; update both sites
consistently, or migrate the legacy directory when encountered.

@senamakel senamakel merged commit b7d51e4 into main Jul 14, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant