Skip to content

feat(memory): port OpenHuman engine gaps#66

Merged
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:feat/port-openhuman-gaps
Jul 12, 2026
Merged

feat(memory): port OpenHuman engine gaps#66
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:feat/port-openhuman-gaps

Conversation

@senamakel

@senamakel senamakel commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Complete the reusable memory-engine ports required by OpenHuman: shared storage/config primitives, source registry/local readers, deterministic ingestion, queue and sync engines, tree sealing/runtime/summarisation, graph and fast retrieval, and persistence-safe hydration.

The crate now owns the engine behavior while host applications inject credentials, providers, event sinks, scheduling, and product policy through explicit seams.

Depends on tinyagents PR #58 for the expanded embedding model API and providers. Until that merges/releases, Cargo pins the exact reviewed fork revision so this branch is independently reproducible.

API Or Behavior Changes

  • Adds optional sync support and Composio pipeline seams.
  • Extends MemoryConfig with content-root and sync configuration.
  • Adds full-body content hydration, source-scoped retrieval, graph routing, direct summary ingestion, sealing services/observers, source registry batch upsert, and deterministic namespace extraction.
  • Preserves existing SQLite schemas and on-disk artifact paths used by OpenHuman.
  • Adds has_likely_email separately from generic PII boundary detection so scanner identifiers remain compatible.

Tests

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings - not run locally
  • cargo build --all-targets - covered by library tests and host all-target compile
  • cargo test - cargo test --lib --quiet: 1,118 passed

Additional validation:

  • cargo test --features sync --test composio_sync_mock: 12 passed
  • OpenHuman GGML_NATIVE=OFF cargo check --tests: passed across all host test targets
  • Focused safety, source, retrieval, tree, queue, goals, and connection-cache suites passed

Documentation

Public APIs and ownership seams are documented inline. The corresponding migration ownership and integration documentation is maintained in the OpenHuman PR.

Summary by CodeRabbit

  • New Features
    • Added live synchronization for Gmail, GitHub, Slack, Linear, Notion, and ClickUp, including pagination, retries, budgets, scheduling, and audit history.
    • Added faster graph-based retrieval with source-scoped filtering.
    • Added workspace syncing, raw-content rebuilding, direct summary ingestion, and synchronization status reporting.
    • Added multilingual personal-information detection and redaction.
  • Bug Fixes
    • Improved full-content retrieval, stale file replacement, database recovery, and cascading cleanup.
    • Strengthened protection against storing email addresses and other personal information.
  • Reliability
    • Added progress observers, deterministic synchronization results, and extensive integration coverage.

Replace the lightweight PII screen (4 generic patterns → `[REDACTED_PII]`)
with OpenHuman's exhaustive checksum-gated module, so `sanitize_text` and the
`has_likely_pii` boundary match the host contract byte-for-byte (a prerequisite
for OpenHuman flipping its `memory_store::safety` onto this crate — the host's
parity guard rejects the lightweight screen).

- `store/safety.rs` → `store/safety/{mod,safety_tests}.rs`; add `store/safety/pii.rs`.
- `pii.rs`: formatted + checksum-gated national IDs (CPF/CNPJ Luhn-style, CUIT,
  RFC, JP My Number, SSN range filter, credit-card Luhn, IBAN mod-97, Aadhaar
  Verhoeff, India PAN, UK NINO, Spain DNI/NIE, Korea RRN) + E.164/NANP phone,
  with a Unicode normalization pre-pass (zero-width strip, fullwidth/Arabic-Indic
  digit folding) mapped back to original offsets, and overlap-safe priority
  redaction. `once_cell::Lazy` → `std::LazyLock` to match crate style.
- `sanitize_text` now runs `pii::redact_pii` after secret redaction; `has_likely_pii`
  is re-exported from `pii` (strict boundary set: formatted IDs only, bare-numeric
  and phone excluded from write-rejection but still content-scrubbed).
- Updated the two mod-level PII tests to the strict-boundary + national-ID
  contract (email is out of scope by design).

cargo test --lib safety: 56 passed.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

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: 54 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: 9dd84e66-3cf1-4ff4-b588-aa3b1a2d5e34

📥 Commits

Reviewing files that changed from the base of the PR and between 64a276c and e259125.

📒 Files selected for processing (37)
  • src/memory/chunks/connection.rs
  • src/memory/chunks/mod.rs
  • src/memory/chunks/produce.rs
  • src/memory/chunks/produce_split.rs
  • src/memory/chunks/raw_refs.rs
  • src/memory/chunks/store_conn_tests.rs
  • src/memory/chunks/store_embed_tests.rs
  • src/memory/chunks/store_tests.rs
  • src/memory/chunks/types.rs
  • src/memory/config.rs
  • src/memory/conversations/inverted_index.rs
  • src/memory/retrieval/fetch_tests.rs
  • src/memory/retrieval/global_tests.rs
  • src/memory/retrieval/test_support.rs
  • src/memory/score/mod.rs
  • src/memory/score/mod_tests.rs
  • src/memory/score/store.rs
  • src/memory/score/store_tests.rs
  • src/memory/store/content/raw.rs
  • src/memory/store/content/read_tests.rs
  • src/memory/store/mod.rs
  • src/memory/store/store.rs
  • src/memory/store/vectors/store_tests.rs
  • src/memory/sync/composio/providers/slack.rs
  • src/memory/sync/github.rs
  • src/memory/sync/workspace.rs
  • src/memory/tool_memory/store.rs
  • src/memory/tool_memory/test_helpers.rs
  • src/memory/tool_memory/types.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/tree/bucket_seal_label_tests.rs
  • src/memory/tree/bucket_seal_tests.rs
  • src/memory/tree/factory_tests.rs
  • src/memory/tree/flush_tests.rs
  • src/memory/tree/store/store_tests.rs
  • src/memory/tree/store/types_tests.rs
  • tests/composio_sync_mock.rs
📝 Walkthrough

Walkthrough

This change adds a feature-gated synchronization engine with Composio providers, persistent sync state, audit logging, graph retrieval, content hydration, expanded storage APIs, multilingual PII handling, and observable tree sealing.

Changes

Memory storage and retrieval

Layer / File(s) Summary
Storage contracts and shared connections
Cargo.toml, src/memory/chunks/*, src/memory/store/*, src/memory/sources/*
Shared connections, transactional helpers, source batching, lifecycle APIs, staged content pointers, cascade deletion, and public re-exports are added.
Safety detection and graph retrieval
src/memory/store/safety/*, src/memory/graph/*, src/memory/goals/types.rs
PII detection gains normalization and checksum validation; entity edges and bounded graph traversal support fast retrieval and expanded goal validation.
Content hydration and retrieval paths
src/memory/store/content/*, src/memory/retrieval/*
Stale bodies can be replaced, chunk and summary bodies can be read and checksum-repaired, and retrieval results can use hydrated content with source scoping.

Synchronization and tree processing

Layer / File(s) Summary
Synchronization runtime and state
src/memory/sync/{traits,state,audit,dispatcher,periodic,rebuild,status,workspace,github}.rs
Sync contracts, state persistence, budgets, audits, scheduling, workspace ingestion, raw rebuilds, status reporting, and dispatcher failure isolation are introduced.
Composio client and provider pipelines
src/memory/sync/composio/*
Direct and proxied Composio execution, retry handling, incremental orchestration, and Gmail, GitHub, Linear, Notion, ClickUp, and Slack pipelines are added.
Tree sealing and staged summaries
src/memory/tree/*
Service-aware sealing, observer callbacks, staged summary persistence, direct summary ingestion, buffer helpers, and cascade deletion are added.
Validation and integration coverage
src/**/*tests*, tests/composio_sync_*
Tests cover storage, safety, retrieval, tree processing, sync state, provider pagination, retries, authentication, budgeting, and live Composio execution.

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

Sequence Diagram(s)

sequenceDiagram
  participant SyncDispatcher
  participant ComposioClient
  participant IncrementalSource
  participant SyncStateStore
  participant SkillDocSink
  SyncDispatcher->>ComposioClient: execute provider action
  ComposioClient->>IncrementalSource: return paginated response
  IncrementalSource->>SyncStateStore: load and update cursor/budget
  IncrementalSource->>SkillDocSink: store converted SkillDocument
  IncrementalSource->>SyncStateStore: persist sync state
  SyncDispatcher-->>SyncDispatcher: return SyncOutcome
Loading

Poem

I’m a rabbit with a tidy new trail,
Where sync hops through pages without fail.
Cursors tuck in, secrets stay shy,
PII gets scrubbed as clouds drift by.
Trees seal softly, graphs leap far—
Carrots applaud each changed avatar!

🚥 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 broadly matches the PR’s main goal of porting OpenHuman memory-engine functionality.
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.

@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: d93452acc7

ℹ️ 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/tree/store/summaries.rs
Comment thread src/memory/tree/bucket_seal.rs
Comment thread src/memory/sync/composio/orchestrator.rs Outdated

@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: 874e20afd9

ℹ️ 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/sync/workspace.rs
Comment thread src/memory/sync/rebuild.rs Outdated
Comment thread src/memory/chunks/store_delete.rs Outdated

@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: ccc26809c6

ℹ️ 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/retrieval/cover.rs
Comment thread src/memory/tree/bucket_seal.rs Outdated

@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

let loser = v < max || !winners_seen.insert(doc);

P1 Badge Keep all latest-version document summaries

When a document version seals into more than one summary node (for example, several L1 batches plus an L2 root), those nodes all share the same doc_id and version_ms. This line treats every same-version node after the first as superseded; because list_summaries_in_window returns lower levels before the root, the later root is marked as a loser and collect_subtree_ids removes the whole current-version subtree and suppresses its chunks, so cover_window_scoped can return no current content for larger documents. Dedupe superseded versions without dropping sibling/ancestor nodes from the winning version.


if !emitted_docs.insert(doc_id.to_string()) {
continue;

P2 Badge Do not dedupe same-version drill-down branches

When drilling into a document subtree with multiple child summaries for the same current doc_id/version_ms (or when max_depth > 1 reaches descendants of an already-emitted document root), this global emitted_docs set skips every later sibling/descendant from that same version. The result is that drill_down returns only one branch of a multi-batch document, or refuses to expand a document root to its leaves. Limit the dedupe to competing revision roots rather than all nodes sharing the document id.

ℹ️ 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/sync/github.rs Outdated

@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: 64a276cbdd

ℹ️ 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/sync/workspace.rs Outdated
Comment thread src/memory/retrieval/fast.rs
Comment thread src/memory/sync/composio/providers/slack.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: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
src/memory/tree/runtime/engine.rs (1)

204-213: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Notify observers when a rebuild completes as a no-op.

Both early returns bypass rebuild_completed, so hosts waiting for a terminal notification can remain pending for empty trees or trees without hour leaves.

Proposed correction
     let status = store::get_tree_status(config, namespace)?;
     if status.total_nodes == 0 {
+        observer.rebuild_completed(namespace, status.total_nodes);
         return Ok(status);
     }
 
     let base = store::tree_dir(config, namespace);
     let mut hour_leaves: Vec<TreeNode> = Vec::new();
     collect_hour_leaves_recursive(&base, namespace, "", &mut hour_leaves)?;
     if hour_leaves.is_empty() {
-        return store::get_tree_status(config, namespace);
+        let status = store::get_tree_status(config, namespace)?;
+        observer.rebuild_completed(namespace, status.total_nodes);
+        return Ok(status);
     }
🤖 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/engine.rs` around lines 204 - 213, Update the rebuild
flow around collect_hour_leaves_recursive so both no-op paths—status.total_nodes
== 0 and hour_leaves.is_empty()—invoke rebuild_completed before returning.
Preserve each path’s existing status result while ensuring observers receive the
terminal notification.
src/memory/tree/store/buffers.rs (1)

17-57: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document transaction and fallback semantics.

The newly public adapters should state that missing buffers return an unsaved empty value, identify transaction expectations, and describe timestamp/upsert behavior.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

🤖 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/store/buffers.rs` around lines 17 - 57, Document the public
APIs get_buffer_conn and upsert_buffer_tx with their fallback and transaction
semantics: state that get_buffer_conn returns an unsaved empty Buffer when no
row exists, and that upsert_buffer_tx must be called within the caller-managed
transaction. Also describe that upsert_buffer_tx writes updated_at_ms using the
current timestamp while preserving the buffer’s oldest_at value.

Source: Coding guidelines

src/memory/retrieval/cover.rs (1)

143-155: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Apply source_scope with the same key as chunk_tree_scope. ListChunksQuery.source_scope filters on metadata.source_id, but this cover path groups and resolves trees by chunk_tree_scope() (path_scope when present). For path-scoped chunks, that mismatch can silently drop allowed rows before the cover is built. Filter after chunk_tree_scope here, or translate the allowlist before calling list_chunks.

🤖 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/retrieval/cover.rs` around lines 143 - 155, Align the source_scope
filtering in the cover retrieval flow with chunk_tree_scope(), which uses
path_scope when present. Update the list_chunks query or subsequent filtering so
path-scoped chunks are evaluated against the same scope key before building the
cover, without dropping allowed rows due to metadata.source_id mismatch.
🟠 Major comments (34)
src/memory/store/safety/pii.rs-103-106 (1)

103-106: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Match lowercase IBANs.

Both the detailed regex and pre-screen require uppercase country and account letters, so a lowercase IBAN bypasses financial-identifier redaction.

Proposed fix
-static IBAN_RE: LazyLock<Regex> =
-    LazyLock::new(|| Regex::new(r"\b[A-Z]{2}\d{2}(?:[\s]?[A-Z0-9]){11,30}\b").expect("iban"));
+static IBAN_RE: LazyLock<Regex> =
+    LazyLock::new(|| Regex::new(r"(?i)\b[A-Z]{2}\d{2}(?:[\s]?[A-Z0-9]){11,30}\b").expect("iban"));
-        r"\b[A-Z]{2}\d{2}[A-Z0-9]",               // IBAN prefix
+        r"(?i)\b[A-Z]{2}\d{2}[A-Z0-9]",           // IBAN prefix

Also applies to: 150-150

🤖 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.rs` around lines 103 - 106, Update the IBAN
detection in IBAN_RE and its associated pre-screen to accept lowercase country
and account letters, while preserving the existing optional-spacing and length
rules so lowercase IBANs are redacted identically to uppercase values.
src/memory/store/safety/pii.rs-139-158 (1)

139-158: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not preserve the bare ten-digit NANP bypass.

PHONE_NANP_RE can redact 2025551234, but SCREEN returns early before that regex runs. This exposes a common phone-number form. Adding it to SCREEN does not broaden has_likely_pii, because strict collection already excludes phone patterns.

Proposed fix
         r"\(?[2-9]\d{2}\)?[\s.\-]\d{3}[\s.\-]\d{4}", // Formatted NANP
+        r"\b[2-9]\d{2}[2-9]\d{6}\b",                  // Bare NANP

Replace the sentinel test with an assertion that the number is redacted.

Also applies to: 174-190, 1090-1107

🤖 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.rs` around lines 139 - 158, Update the
SCREEN-based safety tests to assert that the bare ten-digit NANP number is
redacted instead of treating it as an intentional bypass. Apply this change
consistently to the related cases at the other referenced test locations, while
preserving strict collection behavior that excludes phone-only patterns.
src/memory/store/safety/pii.rs-134-159 (1)

134-159: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Include email addresses in content redaction.

EMAIL_RE is only used by has_likely_email; it is absent from SCREEN and collect_redactions. Consequently, sanitize_text leaves email addresses intact despite its documented contract.

Proposed fix
+const PII_EMAIL: &str = "[REDACTED_PII_EMAIL]";
         r"\d{6}-[1-4]\d{6}",                      // RRN
+        r"(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", // Email
     if include_bare_numeric {
+        // Content-only PII; strict key boundaries continue using
+        // `has_likely_email` according to caller policy.
+        push_simple(&mut hits, norm, &EMAIL_RE, PII_EMAIL);
+
         // Phones: E.164 first (more specific), then NANP.

Also applies to: 248-309

🤖 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.rs` around lines 134 - 159, Update the PII
screening and redaction flow so email addresses detected by EMAIL_RE are
included in SCREEN or otherwise trigger collect_redactions and sanitize_text
redaction. Reuse the existing EMAIL_RE matching logic and ensure
has_likely_email, collect_redactions, and sanitize_text consistently redact
detected email addresses.
src/memory/store/kv.rs-118-118 (1)

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

Do not reject scanner-owned JIDs as email addresses.

has_likely_email matches identifiers such as 12025551234@g.us, so these checks make set_namespace reject the WhatsApp keys that has_likely_pii explicitly permits. This breaks scanner and sync-state persistence. Keep the generic KV boundary on strict PII, or add an explicit policy for user-authored keys.

Proposed fix
-        if safety::has_likely_email(key) || safety::has_likely_pii(key) {
+        if safety::has_likely_pii(key) {
             return Err("kv key cannot contain personal identifiers".to_string());
         }
-        if safety::has_likely_email(namespace)
-            || safety::has_likely_email(key)
-            || safety::has_likely_pii(namespace)
+        if safety::has_likely_pii(namespace)
             || safety::has_likely_pii(key)

As per coding guidelines, preserve machine-readable ids and enum wire strings when porting contracts from OpenHuman.

Also applies to: 158-162

🤖 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` at line 118, Update the validation in set_namespace
to stop treating scanner-owned JIDs as email addresses: use the strict PII
policy for generic KV keys, or explicitly allow the machine-readable JID formats
permitted by has_likely_pii before applying email detection. Apply the same
policy to the corresponding checks around the additional referenced location,
preserving scanner and sync-state persistence while retaining rejection of
user-authored PII.

Source: Coding guidelines

src/memory/store/safety/pii.rs-1-1113 (1)

1-1113: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split this module before merge.

At 1,113 lines, this file exceeds the repository’s 500-line limit. Separate patterns, normalization, checksum validators, and tests into focused sibling modules.

As per coding guidelines, do not let any source file grow beyond 500 lines; split behavior into focused modules before reaching that point.

🤖 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.rs` around lines 1 - 1113, Split the PII
implementation into focused sibling modules so no source file exceeds 500 lines.
Move regex/constants and match collection from collect_redactions_inner, Unicode
handling from NormalizedView, checksum helpers such as valid_cpf and valid_luhn,
and the test module into appropriate files; keep redact_pii, has_likely_pii, and
has_likely_email behavior and module visibility intact through re-exports or
qualified references.

Source: Coding guidelines

src/memory/chunks/embeddings.rs-377-400 (1)

377-400: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Filter chunk rows by lifecycle status here — the chunk branch has no equivalent to s.deleted = 0. Pending/dropped chunks can keep this helper true even though they’ll never get an embedding or skip tombstone, which can keep reembed_backfill armed indefinitely.

🤖 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/embeddings.rs` around lines 377 - 400, Update
has_uncovered_reembed_work so the mem_tree_chunks branch filters out non-live
chunks using the existing chunk lifecycle-status field, matching the summaries
branch’s s.deleted = 0 condition. Keep the embedding and reembed-skipped checks
unchanged, and ensure pending or dropped chunks cannot make the helper report
uncovered work.
src/memory/sync/github.rs-14-29 (1)

14-29: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the new pipeline type.

GithubRepoSyncPipeline and its constructor have no doc comments describing the source requirements, generated id format, or the validation performed in new.

As per coding guidelines, "Document public APIs, module contracts, and non-obvious behavior thoroughly; prefer clear module-level docs and item docs over relying on implementation details."

🤖 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/github.rs` around lines 14 - 29, Add documentation for the
public `GithubRepoSyncPipeline` struct and its `new` constructor, describing the
required `GithubRepo` source, the generated `workspace:github_repo:{source.id}`
identifier format, and the validation performed before construction.

Source: Coding guidelines

src/memory/sync/dispatcher.rs-11-118 (1)

11-118: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the new public dispatcher API.

SyncRunResult, SyncDispatcher, and its public methods (new, register, ids, init_all, tick, tick_all) have no item-level doc comments. Per the PR objectives this is an explicit integration seam host applications are meant to build on top of (registering SyncPipelines, reading SyncRunResult), so documenting expected semantics (e.g., what id normalization/uniqueness guarantees register gives, what outcome: None vs Some means for init_all vs tick_all) would materially help integrators.

As per coding guidelines, "Document public APIs, module contracts, and non-obvious behavior thoroughly; prefer clear module-level docs and item docs over relying on implementation details."

🤖 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 11 - 118, Document the public API
symbols SyncRunResult, SyncDispatcher, and methods new, register, ids, init_all,
tick, and tick_all with item-level Rust doc comments. Describe registration’s
trimmed, non-empty, unique pipeline ID guarantees, deterministic ID access, and
each operation’s result semantics, including that init_all returns a default
outcome on success while tick_all returns the pipeline’s tick outcome, with
errors represented separately.

Source: Coding guidelines

src/memory/sync/github.rs-59-108 (1)

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

Use the shared content_root helper here.
config.workspace.join("memory_tree/content") bypasses MemoryConfig.content_root, so a custom override will write GitHub archives under a different tree than the rest of the memory pipeline reads from.

🐛 Proposed fix
-        let content_root = config.workspace.join("memory_tree/content");
+        let content_root = crate::memory::chunks::content_root(config);
🤖 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/github.rs` around lines 59 - 108, Update the content root
initialization in tick to use the shared MemoryConfig::content_root helper
instead of joining "memory_tree/content" directly. Keep the existing
write_raw_items flow unchanged so GitHub archives honor custom content-root
overrides.
src/memory/retrieval/fast.rs-81-121 (1)

81-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply time_window_days to graph-local results.

The option reaches only dense/global fallback paths. When graph candidates exist, nodes outside the requested window are returned. Pass a cutoff into local candidate resolution and filter occurrence timestamps before ranking.

🤖 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/retrieval/fast.rs` around lines 81 - 121, Update the graph-local
retrieval flow around local_candidates and resolve_local to apply
options.time_window_days. Derive and pass the requested cutoff into local
candidate resolution, then filter out occurrences older than that cutoff before
ranking and limiting results; preserve the existing global fallback behavior.
src/memory/sync/rebuild.rs-105-111 (1)

105-111: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not treat coverage failures as “no rebuild needed.”

An I/O or SQLite failure returns false, making stale archives appear complete. Return Result<bool> and let callers surface or retry the failure.

🤖 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/rebuild.rs` around lines 105 - 111, Update needs_rebuild to
return Result<bool> instead of bool, propagate raw_coverage errors rather than
logging and returning false, and preserve the pending-coverage check on success.
Update all callers to handle or propagate the new Result so coverage failures
can be surfaced or retried.
src/memory/retrieval/fast.rs-232-240 (1)

232-240: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound the dense fallback input at src/memory/retrieval/fast.rs:232.

query_source gathers and reranks the full match set before truncation, so passing usize::MAX here still scans every summary before the scope filter runs. Use a bounded/paginated limit instead of relying on the final 100-result cap.

🤖 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/retrieval/fast.rs` around lines 232 - 240, Update the query_source
call in the dense fallback path to use a bounded, paginated limit instead of
usize::MAX. Choose the appropriate limit that caps the match set before scope
filtering while preserving the existing final 100-result output cap.
src/memory/sync/rebuild.rs-249-270 (1)

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

Make the batch handoff replay-safe. ingest_summary mints a new summary_id on every attempt, so if mark_raw_paths_ingested fails or the process crashes after the summary commit, rerunning this batch writes a second summary for the same raw files. Move coverage marking into the same transaction or add a batch idempotency key.

🤖 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/rebuild.rs` around lines 249 - 270, Make the batch flow
around ingest_summary and mark_raw_paths_ingested replay-safe by ensuring
summary creation and raw-path coverage marking commit atomically, or by applying
a stable batch idempotency key that reuses the existing summary on retries.
Preserve the current child path coverage and prevent reruns after partial
failure from minting duplicate summaries.
src/memory/sync/composio/providers/slack.rs-113-115 (1)

113-115: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Persist a resume point before truncating search pagination.

Clamping total_pages to max_pages makes more_pending false after page 50 even when the provider advertised more pages. Since every tick restarts at page 1, later pages are never ingested. Preserve the provider total and persist the next search page or time-window cursor.

Also applies to: 156-167

🤖 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.rs` around lines 113 - 115, Update
the pagination flow around the page-1 total calculation and the later
`more_pending` handling to preserve the provider-reported total pages instead of
clamping `total_pages` to `self.max_pages`. Before stopping at the configured
page limit, persist a resume point for the next search page or time-window
cursor so subsequent ticks continue ingestion beyond the limit.
src/memory/tree/direct_ingest.rs-95-143 (1)

95-143: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make post-commit cascade failures retry-safe.

The summary transaction commits before cascade_all_from(...).await?. If cascading fails, callers receive an error although the L1 summary already exists; retrying creates a new summary ID and duplicates persisted data. Use an idempotency key or durable pending-cascade state, or return the committed ingest separately from cascade status.

🤖 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/direct_ingest.rs` around lines 95 - 143, The direct-ingest
flow must make failures after the transaction commit retry-safe: update the
logic around the committed insert and `cascade_all_from` so a cascade error
cannot cause a retry to create duplicate persisted summaries. Use an idempotency
key or durable pending-cascade state, or return the committed
`SummaryIngestOutcome` separately from cascade status, while preserving the
existing transaction and successful-cascade behavior.
src/memory/tree/direct_ingest.rs-18-43 (1)

18-43: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document direct-ingestion invariants.

Add item docs for the public inputs, outcome, and function, especially time-range validity, child label/basename correspondence, persistence semantics, and cascade behavior.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

🤖 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/direct_ingest.rs` around lines 18 - 43, Add Rust
documentation comments for the public SummaryIngestInput, SummaryIngestOutcome,
and ingest_summary APIs. Document valid time-range expectations, the one-to-one
correspondence between child_labels and child_basenames, persistence semantics,
and cascade behavior, including what the returned sealed_ids represent.

Source: Coding guidelines

src/memory/sync/composio/providers/slack.rs-68-163 (1)

68-163: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Persist request accounting on failure paths.

Every ? after provider calls can return before state.save, losing consumed-budget accounting and causing retries to repeat work. Structure the tick so mutated state is saved on both success and failure.

🤖 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.rs` around lines 68 - 163, Update
the sync tick surrounding Slack provider calls, especially
SlackSyncPipeline::scopes and checked_execute, so any mutated SyncState is
persisted with state.save before returning an error. Preserve normal success
behavior and ensure failures from provider requests or subsequent processing do
not bypass request-budget accounting.
src/memory/sync/composio/providers/slack.rs-23-50 (1)

23-50: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the public pipeline contracts.

Add item docs for both public pipeline types and constructors, including backfill boundaries, pagination caps, state sharing, and expected credentials.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

Also applies to: 176-185

🤖 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.rs` around lines 23 - 50, Add
documentation comments for the public SlackSyncPipeline and
SlackSearchBackfillPipeline structs and their public constructors, covering
backfill-day minimum/limits, pagination caps, shared synchronization state, and
required Slack/Composio credentials. Apply the same contract documentation to
the constructor around SlackSyncPipeline as well, using only behavior and
configuration symbols exposed by these pipelines.

Source: Coding guidelines

src/memory/sync/composio/providers/slack.rs-1-520 (1)

1-520: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split this source file before merge.

At 520 lines, the new file exceeds the repository limit. Move the search-backfill pipeline or directory/search helpers into focused Slack submodules.

As per coding guidelines, “Do not let any source file grow beyond 500 lines; split behavior into focused modules before reaching that point.”

🤖 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.rs` around lines 1 - 520, Split the
520-line Slack implementation before merge by moving either
SlackSearchBackfillPipeline with its search helpers, or the directory/history
helpers, into focused Slack submodules. Update module declarations and imports
so SlackSyncPipeline, IncrementalSource, and shared helpers continue working,
and ensure the original slack.rs remains under 500 lines without changing
behavior.

Source: Coding guidelines

src/memory/sync/state.rs-9-177 (1)

9-177: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the public persistence and budget contracts.

The public constants, store trait, state models, and mutation methods need item docs covering key semantics, UTC reset behavior, serialization, and request accounting.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

🤖 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/state.rs` around lines 9 - 177, Add Rust documentation
comments to the public constants, SyncStateStore, DailyBudget, and SyncState
APIs. Document the namespace/key semantics, persistence through JSON
serialization, UTC-based daily budget reset behavior, and the distinction
between persisted daily accounting and per-run request/cost accounting. Cover
mutation methods such as remaining, record_requests, record_action, load, and
save, including their observable state changes.

Source: Coding guidelines

src/memory/sync/state.rs-154-162 (1)

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

Canonicalize identity after hydrating keyed state.

A value loaded under one key can contain different toolkit or connection_id fields; the next save then writes it under that other identity, crossing connection boundaries. Reject mismatches or overwrite both fields from the load arguments.

Proposed fix
             Some(value) => {
                 let mut state: Self = serde_json::from_value(value)?;
+                state.toolkit = toolkit.to_owned();
+                state.connection_id = connection_id.to_owned();
                 if state.daily_budget.date != today() {
🤖 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/state.rs` around lines 154 - 162, Canonicalize the hydrated
state in the state-loading method after serde_json::from_value and before
returning it: overwrite its toolkit and connection_id fields with the toolkit
and connection_id arguments used to build the key, or reject mismatches. Ensure
subsequent save operations cannot write the loaded state under a different
identity.
src/memory/tree/flush.rs-41-45 (1)

41-45: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the services-aware flush contract.

Specify observer error behavior, embedding behavior, stale-buffer selection, and whether this function schedules follow-up work.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

🤖 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/flush.rs` around lines 41 - 45, Add thorough API
documentation to the public function flush_stale_buffers_with_services,
describing observer error handling, embedding behavior, stale-buffer selection
criteria, and whether the function schedules follow-up work.

Source: Coding guidelines

src/memory/retrieval/source.rs-40-40 (1)

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

Hydrate only the final selected hits.

collect_source_hits now reads every full summary body before time filtering, ranking, and limit truncation. Large source trees therefore incur O(all summaries) content-store I/O even when returning ten hits. Keep previews during scoring and hydrate only the final result set, preferably in a batch.

Also applies to: 131-131

🤖 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/retrieval/source.rs` at line 40, Update collect_source_hits to
retain lightweight previews while applying time filtering, ranking, and limit
truncation, rather than hydrating every summary body upfront. Hydrate only the
final selected RetrievalHit set, using a batch content-store fetch if available,
and preserve hydrated_summary_hit for constructing the returned hits.
src/memory/tree/bucket_seal.rs-257-264 (1)

257-264: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the new public sealing contracts.

Add item documentation covering force behavior, follow-up enqueue semantics, persistence boundaries, observer ordering, and document-version behavior.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

Also applies to: 299-328, 561-569

🤖 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/bucket_seal.rs` around lines 257 - 264, Add comprehensive
documentation to the public sealing APIs, especially
cascade_all_from_with_services and the related public items near the referenced
sections. Document force behavior, enqueue_follow_ups semantics, persistence
boundaries, observer ordering, and document-version handling, using Rust doc
comments without changing implementation behavior.

Source: Coding guidelines

src/memory/tree/summarise.rs-71-84 (1)

71-84: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document the provider-facing summary API.

Add field docs for usage units and charge semantics, explain effective_budget, document finalization behavior, and state that the default summarise_with_usage reports zero usage.

As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly.”

Also applies to: 137-145, 165-174

🤖 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/summarise.rs` around lines 71 - 84, Document the public
summary API around SummaryCall, PreparedSummaryPrompt, and the related
summarise_with_usage flow: add field-level docs defining input_tokens and
output_tokens as provider-reported usage units, charged_amount_usd as an
optional provider charge, explain effective_budget, describe finalization
behavior, and state that the default summarise_with_usage implementation reports
zero usage.

Source: Coding guidelines

src/memory/tree/bucket_seal.rs-554-832 (1)

554-832: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split document-subtree sealing into a focused module.

This addition grows bucket_seal.rs beyond 800 lines. Move document batching and seal_explicit_children into a dedicated module while retaining the public façade here.

As per coding guidelines, “Do not let any source file grow beyond 500 lines; split behavior into focused modules before reaching that point.”

🤖 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/bucket_seal.rs` around lines 554 - 832, Split the
document-subtree sealing implementation out of bucket_seal.rs into a focused
module: move batch_leaves_by_token_budget, batch_by_count, and
seal_explicit_children there, along with their private constants and required
imports. Keep seal_document_subtree_with_services as the public façade in
bucket_seal.rs, delegating to the new module while preserving its existing
behavior and API.

Source: Coding guidelines

src/memory/tree/bucket_seal.rs-50-60 (1)

50-60: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not report a committed seal as failed when the observer fails.

Both callbacks run after durable commits but propagate errors with ?. Callers can retry an already-committed seal, producing stale snapshots or duplicate staged artifacts. Make notifications best-effort, or use a durable outbox with a result that explicitly distinguishes commit success from notification failure.

Also applies to: 547-549, 824-829

🤖 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/bucket_seal.rs` around lines 50 - 60, Update the seal flow
around SealObserver::progress and SealObserver::summary_committed so observer
errors no longer propagate as seal failures after durable commits. Make
notifications best-effort by handling and recording/logging callback errors
while preserving the committed success result; apply the same behavior at the
additional call sites noted in the review.
src/memory/tree/bucket_seal.rs-586-597 (1)

586-597: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent partially committed document subtrees.

Each concurrent batch commits independently before try_collect completes. If a later batch fails, earlier summaries and backlinks remain persisted without being attached to the merge buffer. A retry creates new summary IDs and can re-parent level-zero chunks, leaving duplicate retrievable nodes. Persist the subtree atomically, or add idempotent recovery/cleanup before returning an error.

Also applies to: 792-823

🤖 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/bucket_seal.rs` around lines 586 - 597, Update the subtree
sealing flow around seal_explicit_children and the buffered try_collect so all
batch summaries and backlinks are persisted atomically, or are fully cleaned up
when any batch fails. Ensure a failed batch cannot leave earlier batches
committed without attaching them to the merge buffer, and retries do not create
duplicate summary nodes or re-parent level-zero chunks.
src/memory/sync/composio/client.rs-1-3 (1)

1-3: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

No unit tests colocated with this module.

This file has no #[cfg(test)] module; retry/backoff decision helpers (retryable_provider_error, retryable_transport_error) are pure functions that would benefit from direct unit tests rather than relying solely on HTTP-mocked integration coverage in tests/composio_sync_mock.rs.

As per coding guidelines, "Add focused unit tests beside the module under src/."

🤖 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/client.rs` around lines 1 - 3, Add a colocated
#[cfg(test)] module in the Composio client module with focused unit tests for
the pure retryable_provider_error and retryable_transport_error helpers. Cover
representative retryable and non-retryable provider and transport errors,
without replacing or expanding the existing integration coverage.

Source: Coding guidelines

src/memory/sync/audit.rs-12-43 (1)

12-43: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Public API lacks doc comments for non-obvious behavior.

SyncAuditEntry and RealCostAccumulator (and their public methods, e.g. combined_cost_usd/effective_cost_usd) have no doc comments explaining the newest-first ordering contract, the real-vs-estimated cost fallback semantics, or malformed-line handling.

As per coding guidelines, "Document public APIs, module contracts, and non-obvious behavior thoroughly; prefer clear module-level docs and item docs over relying on implementation details."

🤖 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/audit.rs` around lines 12 - 43, Add module-level and
item-level Rust doc comments for SyncAuditEntry, RealCostAccumulator, and their
public methods, including combined_cost_usd and effective_cost_usd. Document the
newest-first ordering contract, that actual charged cost falls back to estimated
cost before adding Composio cost, and how malformed audit lines are handled.
Keep the implementation unchanged.

Source: Coding guidelines

src/memory/sync/audit.rs-45-56 (1)

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

Non-atomic two-step write risks interleaved/corrupted JSONL under concurrent callers.

serde_json::to_writer and writeln! are two separate write() calls on an O_APPEND file. If append_audit_entry is invoked concurrently (e.g. by multiple sync pipelines ticking in parallel), the writes can interleave, producing a malformed line that read_audit_log silently drops — quietly losing audit/cost data.

🔒 Proposed fix: single atomic write
-    serde_json::to_writer(&mut file, entry)?;
-    writeln!(file)?;
+    let mut payload = serde_json::to_vec(entry)?;
+    payload.push(b'\n');
+    file.write_all(&payload)?;
🤖 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/audit.rs` around lines 45 - 56, Update append_audit_entry to
serialize the complete JSONL record, including its trailing newline, into one
buffer before performing a single append write. Replace the separate
serde_json::to_writer and writeln! calls while preserving the existing
file-opening, logging, and error propagation behavior.
src/memory/sync/composio/gmail.rs-90-118 (1)

90-118: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid falling back to after:0 for an unparseable cursor
If cursor_to_seconds(cursor) returns None, this turns the Gmail query into after:0, which removes the incremental cutoff and can rescan the whole mailbox. Fall back to the depth-based cutoff or skip setting query in that 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/sync/composio/gmail.rs` around lines 90 - 118, Update the cursor
branch in arguments so an unparseable cursor does not use unwrap_or_default or
produce an after:0 query. Only set the cursor-based query when cursor_to_seconds
succeeds; otherwise fall through to the configured sync_depth_days cutoff or
leave query unset, while preserving query_override precedence.
src/memory/store/content/read.rs-133-185 (1)

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

Do not silently return partial raw-backed content.

Each failed reference is skipped, and all failures return Ok(""); because raw refs take precedence, the staged body is never attempted. Retrieval and summarization can therefore consume truncated content without an error.

Make raw hydration all-or-nothing, or fall back to the staged pointer only when a complete raw body cannot be produced.

🤖 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/read.rs` around lines 133 - 185, The raw-reference
path in read_chunk_body_from_raw must not silently return partial content. Track
rejected, unreadable, invalid-UTF-8, and unusable references, then either return
an error for any failure or make read_chunk_body fall back to the staged pointer
via get_chunk_content_pointers when complete raw hydration is unavailable;
preserve raw precedence only when every reference succeeds.
src/memory/sync/composio/orchestrator.rs-378-400 (1)

378-400: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Persist pagination progress before reporting more_pending.

When the page cap is reached, more_pending prevents cursor advancement, but page_token is discarded. The next run restarts at page one, consumes the same page limit while skipping deduplicated items, and can never reach the remaining pages.

Persist a continuation token/query window in SyncState, and add a regression test where the source exposes more than max_pages() pages across multiple runs.

🤖 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/orchestrator.rs` around lines 378 - 400, Update the
pagination flow around page_token, more_pending, and SyncState so reaching the
max-pages cap persists a continuation token or query window before setting
more_pending. Resume from that persisted continuation on the next run instead of
restarting at page one, while preserving existing cursor advancement when
pagination completes. Add a regression test covering a source with more than
max_pages() pages across multiple runs.
🟡 Minor comments (8)
src/memory/chunks/store.rs-214-240 (1)

214-240: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail fast when the checksum row is missing. conn.execute(...) drops the affected-row count, so a stale chunk_id/summary_id is treated as success and the checksum-repair path silently does nothing. set_chunk_lifecycle_status_conn already bails on changed == 0; these helpers should do the same so a lost row surfaces instead of being swallowed.

🤖 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 214 - 240, Update
update_chunk_content_sha256 and update_summary_content_sha256 to retain the
conn.execute affected-row count and return an error when changed == 0, matching
the fail-fast behavior of set_chunk_lifecycle_status_conn; preserve successful
updates for existing rows.
src/memory/chunks/store.rs-242-258 (1)

242-258: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate query_map errors in list_source_ids_with_prefix
filter_map(Result::ok) can silently truncate the list on a row decode failure; collect the iterator as rusqlite::Result<Vec<_>> so the error bubbles up like the other store helpers. If this set can grow, push the prefix predicate into SQL instead of filtering every distinct source_id in Rust.

🤖 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 242 - 258, Update
list_source_ids_with_prefix to propagate row-decoding errors from query_map by
collecting into a rusqlite::Result<Vec<String>> instead of discarding failures
with filter_map(Result::ok). Apply the prefix constraint in SQL with the
appropriate LIKE parameter so filtering occurs in the database, while preserving
DISTINCT ordering and the existing Result return behavior.
src/memory/chunks/store_embed_tests.rs-85-148 (1)

85-148: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test never verifies the tombstone was actually cleared.

The test calls clear_summary_reembed_skipped twice (lines 145-146) but, unlike the sibling clear_chunk_reembed_skipped_is_idempotent test (lines 65-83) which re-queries and asserts count == 0, this test has no final assertion after the clear calls. The line-range change summary states the test "verifies it has been removed (count goes to zero)", but the code as written only checks .unwrap() on the Result — it would pass even if clear_summary_reembed_skipped silently failed to delete the row.

🐛 Proposed fix to actually verify removal
     clear_summary_reembed_skipped(&cfg, "summary-1", "model@3").unwrap();
     clear_summary_reembed_skipped(&cfg, "summary-1", "model@3").unwrap();
+    let count: i64 = with_connection(&cfg, |conn| {
+        Ok(conn.query_row(
+            "SELECT COUNT(*) FROM mem_tree_summary_reembed_skipped
+             WHERE summary_id='summary-1' AND model_signature='model@3'",
+            [],
+            |row| row.get(0),
+        )?)
+    })
+    .unwrap();
+    assert_eq!(count, 0);
 }
🤖 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_embed_tests.rs` around lines 85 - 148, Update the
test summary_reembed_tombstone_roundtrips_and_clears to query
mem_tree_summary_reembed_skipped after both clear_summary_reembed_skipped calls
and assert the matching row count is zero, mirroring
clear_chunk_reembed_skipped_is_idempotent while retaining the existing pre-clear
count assertion.
src/memory/sync/rebuild.rs-183-187 (1)

183-187: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not count unreadable files as successfully read.

This branch is reached only when no file produced an input, yet reports every pending file in files_read.

Proposed correction
     if inputs.is_empty() {
         return Ok(RebuildOutcome {
-            files_read: coverage.pending.len(),
+            files_read: 0,
             ..RebuildOutcome::default()
         });
     }
🤖 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/rebuild.rs` around lines 183 - 187, Update the empty-input
branch in the rebuild flow to report zero successfully read files instead of
using coverage.pending.len(). Preserve the existing RebuildOutcome default
behavior and early return for inputs.is_empty().
src/memory/config_tests.rs-18-20 (1)

18-20: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert that secret fields are omitted, not merely redacted.

These assertions still pass if api_key and bearer_token begin serializing as "[REDACTED]", despite the #[serde(skip)] contract in src/memory/config.rs Lines 301-313.

Proposed test
-    let json = serde_json::to_string(&config).unwrap();
+    let value = serde_json::to_value(&config).unwrap();
+    assert!(value.get("api_key").is_none());
+    assert!(value.get("bearer_token").is_none());
+    let json = value.to_string();
     assert!(!json.contains("super-secret"));
     assert!(!json.contains("bearer-secret"));
🤖 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_tests.rs` around lines 18 - 20, Strengthen the
serialization assertions in the config test around serde serialization so they
verify the secret field names and redacted placeholder values are absent, not
just the original secret contents. Preserve the existing #[serde(skip)] contract
for api_key and bearer_token defined in the configuration type.
src/memory/tree/bucket_seal.rs-282-290 (1)

282-290: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid enqueueing work that this cascade immediately completes.

With enqueue_follow_ups = true, sealing enqueues a ready parent, then the cascade advances to that same level and seals it synchronously. The queued job is stale by construction. Either stop cascading after enqueueing or enqueue only when the synchronous cascade stops.

Also applies to: 528-535

🤖 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/bucket_seal.rs` around lines 282 - 290, Update the cascade
flow around seal_one_level_with_services so it does not enqueue follow-up work
for a parent level that the same synchronous cascade will immediately seal. When
enqueue_follow_ups is enabled, either stop the cascade after enqueueing or defer
enqueueing until the cascade terminates; apply the same correction to the
corresponding flow near the other seal_one_level_with_services call.
src/memory/tree/summarise.rs-137-145 (1)

137-145: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply the canonical output cap during finalization.

prepare_summary_prompt limits output to 5,000 tokens, but finish_provider_summary can return more whenever budget is larger. Clamp against both limits.

Proposed fix
 pub fn finish_provider_summary(text: &str, budget: u32) -> SummaryOutput {
-    let (content, token_count) = clamp_to_budget(text.trim(), budget);
+    let effective_budget = budget.min(MAX_SUMMARY_OUTPUT_TOKENS);
+    let (content, token_count) = clamp_to_budget(text.trim(), effective_budget);
🤖 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/summarise.rs` around lines 137 - 145, Update
finish_provider_summary to cap the effective budget at the canonical 5,000-token
output limit before calling clamp_to_budget, ensuring larger budget values
cannot produce more than 5,000 tokens while preserving lower caller-provided
budgets.
src/memory/sync/composio/orchestrator.rs-118-131 (1)

118-131: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Emit a terminal event when the budget is already exhausted.

Line 118 emits Fetching, but the early return sends neither Completed nor Failed, leaving event-driven status consumers stuck in a fetching state.

Proposed fix
     if state.budget_exhausted() {
+        emit(
+            context,
+            toolkit,
+            connection_id,
+            SyncStage::Completed,
+            Some("daily request budget exhausted".into()),
+        )
+        .await;
         return Ok(SyncOutcome {
🤖 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/orchestrator.rs` around lines 118 - 131, Update the
budget-exhausted early-return branch in the sync orchestrator after
SyncState::load to emit a terminal SyncStage event, preferably Completed, before
returning the existing SyncOutcome. Preserve the current note and default
outcome behavior while ensuring consumers do not remain in Fetching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5ef8677f-8725-4b1d-b38d-1a3d10a5992c

📥 Commits

Reviewing files that changed from the base of the PR and between 4b9ac86 and 64a276c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (89)
  • Cargo.toml
  • src/memory/chunks/connection.rs
  • src/memory/chunks/embeddings.rs
  • src/memory/chunks/mod.rs
  • src/memory/chunks/recovery.rs
  • src/memory/chunks/store.rs
  • src/memory/chunks/store_delete.rs
  • src/memory/chunks/store_embed_tests.rs
  • src/memory/chunks/store_sources.rs
  • src/memory/chunks/store_tests.rs
  • src/memory/config.rs
  • src/memory/config_tests.rs
  • src/memory/goals/types.rs
  • src/memory/graph/bfs.rs
  • src/memory/graph/edge_store.rs
  • src/memory/graph/mod.rs
  • src/memory/ingest/extract/mod.rs
  • src/memory/ingest/extract/types.rs
  • src/memory/ingest/mod.rs
  • src/memory/ingest/types.rs
  • src/memory/mod.rs
  • src/memory/retrieval/cover.rs
  • src/memory/retrieval/cover_tests.rs
  • src/memory/retrieval/drill_down.rs
  • src/memory/retrieval/fast.rs
  • src/memory/retrieval/fetch.rs
  • src/memory/retrieval/fetch_tests.rs
  • src/memory/retrieval/mod.rs
  • src/memory/retrieval/source.rs
  • src/memory/retrieval/types.rs
  • src/memory/sources/mod.rs
  • src/memory/sources/registry.rs
  • src/memory/sources/types.rs
  • src/memory/store/content/atomic.rs
  • src/memory/store/content/content_tests.rs
  • src/memory/store/content/mod.rs
  • src/memory/store/content/raw.rs
  • src/memory/store/content/read.rs
  • src/memory/store/content/read_tests.rs
  • src/memory/store/entity_index/mod.rs
  • src/memory/store/entity_index/store.rs
  • src/memory/store/entity_index/store_tests.rs
  • src/memory/store/entity_index/types.rs
  • src/memory/store/kv.rs
  • src/memory/store/kv_tests.rs
  • src/memory/store/safety/mod.rs
  • src/memory/store/safety/pii.rs
  • src/memory/store/safety/safety_tests.rs
  • src/memory/store/vectors/embedding.rs
  • src/memory/sync/audit.rs
  • src/memory/sync/composio/client.rs
  • src/memory/sync/composio/gmail.rs
  • src/memory/sync/composio/mod.rs
  • src/memory/sync/composio/orchestrator.rs
  • src/memory/sync/composio/providers/clickup.rs
  • src/memory/sync/composio/providers/common.rs
  • src/memory/sync/composio/providers/github.rs
  • src/memory/sync/composio/providers/linear.rs
  • src/memory/sync/composio/providers/mod.rs
  • src/memory/sync/composio/providers/notion.rs
  • src/memory/sync/composio/providers/slack.rs
  • src/memory/sync/dispatcher.rs
  • src/memory/sync/github.rs
  • src/memory/sync/mod.rs
  • src/memory/sync/periodic.rs
  • src/memory/sync/rebuild.rs
  • src/memory/sync/state.rs
  • src/memory/sync/status.rs
  • src/memory/sync/traits.rs
  • src/memory/sync/workspace.rs
  • src/memory/tree/bucket_seal.rs
  • src/memory/tree/bucket_seal_label_tests.rs
  • src/memory/tree/bucket_seal_tests.rs
  • src/memory/tree/direct_ingest.rs
  • src/memory/tree/flush.rs
  • src/memory/tree/hydrate.rs
  • src/memory/tree/mod.rs
  • src/memory/tree/runtime/engine.rs
  • src/memory/tree/runtime/engine_tests.rs
  • src/memory/tree/runtime/mod.rs
  • src/memory/tree/store/buffers.rs
  • src/memory/tree/store/mod.rs
  • src/memory/tree/store/store_tests.rs
  • src/memory/tree/store/summaries.rs
  • src/memory/tree/store/trees.rs
  • src/memory/tree/summarise.rs
  • src/memory/tree/summarise_tests.rs
  • tests/composio_sync_live.rs
  • tests/composio_sync_mock.rs

Comment thread src/memory/sync/workspace.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

let file_path = Path::new(base_path).join(item_id);

P2 Badge Reapply the folder glob in direct reads

When a caller invokes SourceReader::read_item directly with an item id, this path only verifies containment under the folder root and never checks the source glob used by list_items. A folder source scoped to something like docs/**/*.md can therefore be made to read another contained file such as docs/.env that would never be listed; reject item ids that do not match the configured glob before reading the file.

ℹ️ 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 on lines +275 to +279
if config
.sync
.budget
.max_items
.is_some_and(|limit| ingested >= limit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor per-source sync caps

When Composio sources are created through SourceRegistry, the default max_items/sync_depth_days caps live on the MemorySourceEntry, but this limit check only consults config.sync.budget. If the host registers a source with the registry defaults and leaves the global budget unset, the per-toolkit caps are ignored and the provider can fetch up to the pipeline page limits instead of the configured source cap; resolve the effective budget from the source entry before running the incremental loop.

Useful? React with 👍 / 👎.

#[async_trait]
impl SyncPipeline for GmailSyncPipeline {
fn id(&self) -> &str {
"composio:gmail"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make Composio pipeline IDs connection-specific

For users with two Gmail Composio connections, both GmailSyncPipeline instances return the same id, and SyncDispatcher::register rejects duplicate pipeline ids even though the source registry keys Composio sources by connection_id. This prevents registering/syncing a second Gmail account; include the connection id in the pipeline id, as the workspace pipelines already do.

Useful? React with 👍 / 👎.

)
.await;
}
resolve_local(config, candidates, source_scope, limit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply time-window filtering to graph-local hits

When query entity ids produce graph pairs, this local branch bypasses the dense/global paths that pass options.time_window_days into query_source; resolve_local receives only scope and limit, so a request for a recent window can still return old co-occurrence summaries or leaves. Filter the resolved local hits by the requested window before computing totals/truncating.

Useful? React with 👍 / 👎.

@senamakel
senamakel merged commit cdc8978 into tinyhumansai:main Jul 12, 2026
8 checks passed

@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: e259125402

ℹ️ 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".

[&tree.id],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
if current_root.as_deref() != Some(root_id.as_str()) || root_level > current_max {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not downgrade merge roots on document edits

Fresh evidence after the previous root-refresh thread: this condition still takes the update path whenever the new document root id differs, even if current_max is already a cross-document merge level. After a source has sealed a merge root at MERGE_LEVEL_BASE + 1, a later edit to one document seals a low-level document root, this branch calls update_tree_after_seal_tx with that lower root_level, downgrading max_level and root_id; query_source then only scans up to the downgraded level and skips the existing merge summaries.

Useful? React with 👍 / 👎.

Comment on lines +214 to +216
let pii = pii::redact_pii(&out);
report = report.merge(pii.report);
out = pii.value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Redact emails when sanitizing stored values

For KV/JSON values that contain ordinary email addresses, sanitize_json delegates string values to sanitize_text, but this now only runs the national-ID/phone scrubber and never applies the separate has_likely_email detector. A value such as { "contact": "alice@example.com" } is therefore stored unchanged even though the previous sanitizer redacted email PII and this module still documents content PII scrubbing.

Useful? React with 👍 / 👎.

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