Skip to content

Implement: Phase 3 — MCP surface integration for compact-replacement - #192

Merged
getappz merged 6 commits into
masterfrom
task/69
Jul 15, 2026
Merged

Implement: Phase 3 — MCP surface integration for compact-replacement#192
getappz merged 6 commits into
masterfrom
task/69

Conversation

@getappz

@getappz getappz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Auto-opened on item done for 019f6440-8014-7fc0-9f79-008aa4cd390e.

Summary by CodeRabbit

  • New Features
    • Added a new memory tool action compact that ranks transcript lines by relevance (FTS5/BM25) and selects which lines to keep.
    • Added compression ratio and preserve recent options; responses include per-line keep decisions (and optional scores) plus kept/total.
    • Introduced a PreCompact hook that can analyze transcripts ahead of compaction and emit scored results as JSON; it’s wired into Claude Code idempotently.
    • Added scorer validation (accepts fts5 only).
  • Tests
    • Expanded unit coverage for PreCompact parsing/relevance query logic and compact scoring/validation.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c5af399f-4236-490e-b67a-2f326445da98

📥 Commits

Reviewing files that changed from the base of the PR and between ae66ca0 and 7b0c214.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • src/mcp_server.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/mcp_server.rs

📝 Walkthrough

Walkthrough

Adds FTS5/BM25 transcript scoring, a Claude Code PreCompact hook, and an MCP memory compaction action that selects relevant and recent lines.

Changes

Transcript compaction

Layer / File(s) Summary
BM25 scoring engine
Cargo.toml, src/main.rs, src/compact.rs
Adds the local search dependency and an FTS5/BM25 scorer that ranks indexed transcript lines, with tests for empty queries, special characters, no matches, and deterministic ordering.
PreCompact hook integration
src/cli/hook.rs, src/hook.rs, src/init.rs
Adds the PreCompact CLI event, parses hook input, scores transcript contents, emits JSON, and wires the hook into new and existing Claude Code installations.
MCP compact action
src/mcp_server.rs, src/memory/mcp.rs
Adds compact request fields and dispatch, validates queries and scorers, scores lines, preserves recent lines, applies compression limits, and returns per-line keep metadata with aggregate counts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeCode
  participant HookArgs
  participant pre_compact
  participant score_lines
  ClaudeCode->>HookArgs: invoke hook pre-compact
  HookArgs->>pre_compact: dispatch resolved agent
  pre_compact->>score_lines: score transcript lines by derived query
  score_lines-->>pre_compact: return ranked lines
  pre_compact-->>ClaudeCode: print scored JSON
Loading
sequenceDiagram
  participant MCPClient
  participant memory_tool
  participant handle_compact
  participant score_lines
  MCPClient->>memory_tool: submit compact action
  memory_tool->>handle_compact: pass lines and options
  handle_compact->>score_lines: rank lines by query
  score_lines-->>handle_compact: return scored lines
  handle_compact-->>MCPClient: return keep flags and counts
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is only an auto-opened note and omits the required Summary, Test plan, and Notes for reviewers sections. Add the template sections: Summary, Test plan with the checkbox list, and Notes for reviewers covering risks and backward compatibility.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and accurately reflects the main MCP compact-replacement integration work.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/69

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

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

🤖 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/compact.rs`:
- Around line 62-67: Update the SQL constructed in the compact query around
weights.sql_args() so ORDER BY uses a deterministic secondary key, such as
rowid, after score. Preserve score as the primary ordering and ensure
equal-score rows follow the intended insertion-order behavior.

In `@src/hook.rs`:
- Around line 239-247: Update the scoring flow around score_lines to derive an
actual content-relevance query from the active task or recent user intent,
rather than passing parsed.session_id. Ensure the derived query is available
before scoring and preserves meaningful ranking of the transcript entries.

In `@src/memory/mcp.rs`:
- Around line 248-277: Update handle_compact to consume CompactInput.scorer
according to the advertised backend options: use the selected keyword or FTS5
scoring behavior, and return an error for any unsupported value. If keyword
scoring is not available, remove scorer from CompactInput and the MCP schema
instead of silently ignoring it.
- Around line 295-308: Update the by_relevance construction in the score_lines
selection flow to preserve scored’s BM25 relevance order: deduplicate indices
without sorting them by transcript position, then iterate in the original scored
order while filling keep up to keep_count. Retain the existing duplicate
handling and keep[idx] checks.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2cfc0b4f-6a33-4d8d-a030-faf80f81dec6

📥 Commits

Reviewing files that changed from the base of the PR and between f65c582 and e7f1f52.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • src/cli/hook.rs
  • src/compact.rs
  • src/hook.rs
  • src/init.rs
  • src/main.rs
  • src/mcp_server.rs
  • src/memory/mcp.rs

Comment thread src/compact.rs
Comment thread src/hook.rs Outdated
Comment thread src/memory/mcp.rs
Comment thread src/memory/mcp.rs Outdated
@getappz

getappz commented Jul 15, 2026

Copy link
Copy Markdown
Owner Author

Reviewed (first time — this is new work, not something I looked at as a pre-PR worktree). CI is currently red (`fmt` and `clippy` both fail) — needs fixing regardless of the below, but I found two real correctness bugs worth addressing in the same pass:

1. `handle_compact` sorts away the relevance ranking it just computed (src/memory/mcp.rs)

`score_lines` returns `scored` already ordered by BM25 relevance (best match first, per `ORDER BY score` in the SQL). The fill loop is supposed to use that order — the comment says so directly:

```rust
// Fill remaining keep quota with highest-scored (lowest BM25 score).
let mut by_relevance: Vec = scored.iter().map(|s| s.index).collect();
// Deduplicate — a line might be both recent and high-scored.
by_relevance.sort();
by_relevance.dedup();
```

`by_relevance.sort()` sorts the line indices, i.e. puts them back into transcript order, throwing away the relevance ordering `scored` was built to provide. The subsequent `for idx in by_relevance` loop then fills the keep-quota with whichever matched lines happen to come earliest in the transcript, not the most relevant ones — the opposite of what the comment and the whole point of BM25-scoring says it should do. This only doesn't matter when `scored.len() <= ` the remaining quota (nothing gets dropped either way); it silently misbehaves any time there are more matches than room to keep them, which is exactly the case compaction exists for.

Also, the `dedup()` is solving a problem that can't occur here — `scored` is one row per transcript line from a single FTS5 query, so indices can't repeat. The actual "line might be both recent and high-scored" case is already handled correctly by the `if !keep[idx]` check further down. Suggest just dropping the `.sort()`/`.dedup()` and iterating `scored` (or `scored.iter().map(|s| s.index)`) directly in its original relevance order.

2. The PreCompact hook scores lines against the session ID, not a real query (src/hook.rs)

```rust
let scored = crate::compact::score_lines(&entries, &parsed.session_id);
```

`parsed.session_id` is the session UUID from the hook payload — not natural-language text. FTS5-tokenizing a UUID and matching it against transcript line content will essentially never produce a meaningful hit (a UUID won't appear as a word in the conversation). As written, the automatic `PreCompact` hook path will almost always score everything as non-matching and return an empty/near-empty result — i.e. the hook-triggered compaction is effectively a no-op in real use, even though `score_lines` itself and the MCP `memory(action=compact)` path (which takes an explicit, required `query` param) both work correctly in isolation. This needs a real query — e.g. derived from the most recent user turn(s) in the transcript, or whatever signal is meant to drive "what's relevant to keep."

3. (minor) `score_lines` panics instead of failing soft

Every SQLite step in `score_lines` (`crates/.../src/compact.rs`, called from both the hook and the MCP action) is `.expect(...)`'d, including once per transcript line in the insert loop, and inserts aren't wrapped in a transaction (autocommit per row). One bad line anywhere in a real transcript kills scoring for the whole call, which is inconsistent with the rest of `hook.rs`'s fail-soft handling (e.g. the transcript-read error path a few lines above just logs and returns). Given this runs on every compaction of potentially large transcripts, worth converting to soft failures and batching the inserts in one transaction.

Please fix fmt/clippy and #1/#2 before merge; #3 is a robustness/perf nit, not blocking.

getappz added 2 commits July 15, 2026 13:39
- handle_compact: stop re-sorting the relevance-scored line indices back
  into transcript order before filling the keep quota. That sort discarded
  the BM25 ranking score_lines had just computed, so whenever there were
  more matches than room to keep, the earliest matches won instead of the
  most relevant ones -- the opposite of what compaction is for. Now fills
  from `scored` in its native relevance order.
- pre_compact: stop scoring transcript lines against the session UUID.
  A UUID isn't natural-language text, so FTS5-matching it against
  transcript content essentially never hit, making the automatic
  PreCompact hook path a near no-op. Extracted a testable
  relevance_query() helper that falls back to the transcript's own last
  non-empty line (the most recent turn) as a stand-in for "what the user
  is currently focused on".
- score_lines: batch the per-line FTS5 inserts into one transaction
  instead of one implicit commit per row.
- Fix clippy (needless_range_loop, useless_vec, dead_code on the
  not-yet-wired CompactInput.scorer field) and fmt.
- compact::score_lines: add "ix" as a secondary ORDER BY key so equal-
  score ties resolve deterministically by transcript position instead of
  being left to SQLite's unspecified tie order.
- handle_compact: reject any CompactInput.scorer other than "fts5" instead
  of silently ignoring it -- the MCP schema advertises fts5|keyword but
  keyword scoring was never implemented, so a caller selecting it would
  have gotten FTS5 behavior with no indication anything was wrong.

@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.

♻️ Duplicate comments (1)
src/memory/mcp.rs (1)

261-267: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove keyword from the advertised MCP contract or implement it.

src/mcp_server.rs:355-364 still advertises scorer as fts5|keyword, while this handler rejects keyword. Schema-generated clients can therefore submit a documented value that always fails.

Possible contract fix
-#[schemars(description = "Scorer backend: fts5|keyword (compact)")]
+#[schemars(description = "Scorer backend: fts5 (compact)")]
🤖 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/mcp.rs` around lines 261 - 267, Align the advertised MCP scorer
contract with the validation in the handler: update the scorer schema in the MCP
server’s tool definition around the scorer advertisement to remove “keyword,”
leaving only “fts5” as supported. Keep the existing validation in the memory
handler unchanged.
🧹 Nitpick comments (2)
src/memory/mcp.rs (2)

284-284: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Keep scoring failures inside the MCP error path.

The handler returns Result, but score_lines is invoked as infallible code; the scorer’s reported SQLite expect calls can panic on database or FTS5 failures. Make score_lines fallible and map its error to Err here rather than aborting the MCP handler.

🤖 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/mcp.rs` at line 284, Update score_lines and its caller in the MCP
handler to return and propagate scoring errors instead of panicking on SQLite or
FTS5 failures. In the handler around the scored assignment, map the fallible
result into the existing MCP Result error path, preserving normal scoring
behavior on success.

316-325: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid the quadratic score lookup.

scored.iter().find(...) rescans the scored lines for every transcript entry. Build an index-to-score map once, then perform constant-time lookups.

Proposed refactor
+    let scores: std::collections::HashMap<usize, _> =
+        scored.iter().map(|s| (s.index, s.score)).collect();
+
     let output: Vec<serde_json::Value> = entries
         .iter()
         .enumerate()
         .map(|(i, entry)| {
-            let score = scored.iter().find(|s| s.index == i).map(|s| s.score);
+            let score = scores.get(&i).copied();
🤖 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/mcp.rs` around lines 316 - 325, Update the output construction in
the entries iterator to build an index-to-score map from scored once before the
map operation, then use constant-time lookups for each entry instead of calling
scored.iter().find(...). Preserve the existing optional score behavior when no
matching index exists.
🤖 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.

Duplicate comments:
In `@src/memory/mcp.rs`:
- Around line 261-267: Align the advertised MCP scorer contract with the
validation in the handler: update the scorer schema in the MCP server’s tool
definition around the scorer advertisement to remove “keyword,” leaving only
“fts5” as supported. Keep the existing validation in the memory handler
unchanged.

---

Nitpick comments:
In `@src/memory/mcp.rs`:
- Line 284: Update score_lines and its caller in the MCP handler to return and
propagate scoring errors instead of panicking on SQLite or FTS5 failures. In the
handler around the scored assignment, map the fallible result into the existing
MCP Result error path, preserving normal scoring behavior on success.
- Around line 316-325: Update the output construction in the entries iterator to
build an index-to-score map from scored once before the map operation, then use
constant-time lookups for each entry instead of calling scored.iter().find(...).
Preserve the existing optional score behavior when no matching index exists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb294c5a-63db-46dd-a44a-190937dc5a73

📥 Commits

Reviewing files that changed from the base of the PR and between 605162c and df36493.

📒 Files selected for processing (2)
  • src/compact.rs
  • src/memory/mcp.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/compact.rs

getappz added 2 commits July 15, 2026 14:20
…P schema

CompactInput.scorer only accepts fts5 (handle_compact rejects anything
else), but the schemars description still told callers fts5|keyword was
valid -- a schema-generated client could submit a documented value that
always errors.
@getappz
getappz merged commit 91c2595 into master Jul 15, 2026
15 checks passed
@getappz
getappz deleted the task/69 branch July 15, 2026 09:12
getappz added a commit that referenced this pull request Jul 15, 2026
- Backfill items_fts for items that already existed before this
  migration ran -- the AFTER INSERT/UPDATE/DELETE triggers only keep
  the index in sync going forward, so without a backfill every item
  created before this migration would be permanently invisible to
  item(search) until it happened to be touched again.
- Clamp item::search's limit via flare_search_kit::clamped_limit instead
  of a raw `limit as i64` cast -- usize::MAX (or any caller-supplied
  value that wraps negative on cast) turns into SQL "LIMIT -1", which
  SQLite treats as unlimited, defeating the cap. Same class of bug this
  codebase already guards against in gateway-registry/skill-registry's
  own search() via the same helper.
- Fix the limit field's schema description, which said "omit for no
  limit" -- true for item(list), but item(search) defaults to 20
  (clamped to 1000) when omitted, not unlimited.
- Resolve merge conflicts against master's already-fixed compact.rs/
  memory/mcp.rs (this branch predates #192/#195 and carried duplicate,
  pre-fix copies of that code) by taking master's versions.
getappz added a commit that referenced this pull request Aug 27, 2026
…ning free text (#624)

Item #170's false-positive class hit twice more in one session (items
#192, #173): a description that merely mentions "design-spec" (e.g.
referencing another item's spec) forces the review-only prompt even for
a genuine implementation task, because nothing ever set the structured
metadata.task_type signal detect_review_only already knows how to trust.

handoff now accepts an optional task_type and merges it into the
item's existing metadata (without clobbering other keys) both when
targeting an existing item_id and when creating a new one.

Agentflare-Agent: claude-code
Agentflare-Branch: task/task-type-metadata-review-only-fix
Agentflare-Session: e77fc32e-33d0-4884-ab55-fdda48fe45fd

Co-authored-by: shiva <shiva@gosysinfo.tech>
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