Skip to content

feat: session-family retrieval scope (cherry-pick of upstream PR #338) + extend to v0.9.4 tools#63

Merged
100yenadmin merged 2 commits into
mainfrom
feat/session-family-scope-from-pr-338
May 4, 2026
Merged

feat: session-family retrieval scope (cherry-pick of upstream PR #338) + extend to v0.9.4 tools#63
100yenadmin merged 2 commits into
mainfrom
feat/session-family-scope-from-pr-338

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 4, 2026

Copy link
Copy Markdown
Member

Cherry-picks Martian-Engineering#338. Extends the upstream session-family scope work to cover the v0.9.4 stack tools that the upstream PR didn't know about.

Why this matters

PR Martian-Engineering#592 (auto-rotate oversized session files), which we landed in v0.9.5, can split a long-running session into multiple physical conversation rows. Without session-family scope, retrieval against the post-rotate session only sees the active segment — silently missing context from archived segments.

This PR closes that correctness gap.

What this includes

From upstream PR Martian-Engineering#338

  • `getConversationFamilyIds(sessionId, sessionKey?)` lookup in `ConversationStore`
  • `LcmConversationScope.conversationIds` returned from `resolveLcmConversationScope`
  • Family scope wired into: `lcm_grep`, `lcm_describe`, `lcm_expand_query`, `retrieval.ts`

Net-new (v0.9.4 stack tool extensions)

  • `lcm_work_density` — `ObservedWorkDensityQuery.conversationIds?: number[]`
  • `lcm_event_search` — `listObservations` + `listEpisodes` accept `conversationIds?`
  • `lcm_task_suggestions` — pipes through `getDensity`
  • `lcm_recent` — already family-aware via `relatedConversationIds`; verified intact
  • `lcm_task_suggestion_review` — already family-aware via ownership check

Conflict resolution

Tests

Concerns flagged for review (audit will probe these)

  1. Two parallel field names (`conversationIds` + `relatedConversationIds`) — future contributor could set one without the other
  2. `listEpisodes` family extension done for symmetry but no current tool wires it
  3. Pre-existing TS errors in `searchByContent`-style code (PR sessions: add session-family retrieval scope Martian-Engineering/lossless-claw#338's import); not introduced here
  4. `getConversationFamilyIds` ORDER BY differs from `listConversationsBySessionKey` (active-first vs not)

Summary by CodeRabbit

  • New Features

    • Search/grep, summary search, event observation listing, and density/suggestion queries now support session-family (multi-conversation) scoping and show when multiple segments are included.
    • Conversation listing now prioritizes active conversations and caps results to the most recent 256.
  • Bug Fixes / Reliability

    • Freshness and rollup gating now evaluate rebuild state across the conversation family for correct fallbacks.
  • Tests

    • Added coverage for multi-conversation/session-family behaviors and recent-tool rollup cases.

…ian-Engineering#338)

Cherry-picks Martian-Engineering#338. Treats multiple physical
conversation segments that share a stable session identity as one logical
retrieval family.

Beyond the upstream scope (which only covered lcm_grep / lcm_describe /
lcm_expand_query), this commit also extends family scope to the v0.9.4 stack
tools that are conversation-scoped:
- lcm_recent (already family-aware via relatedConversationIds — verified)
- lcm_work_density (observed-work density spans family)
- lcm_event_search (events span family)
- lcm_task_suggestions (suggestions derived from family-wide getDensity)

This closes the correctness gap that PR Martian-Engineering#592 (auto-rotate session files)
introduced: without family scope, retrieval after auto-rotate silently misses
data from archived segments.

Conflict resolution:
- src/store/conversation-store.ts: union of v0.9.4 listConversationsBySessionKey
  (no-cap) + PR Martian-Engineering#338 getConversationFamilyIds (works for both session_key and
  session_id paths). Both retained.
- src/tools/lcm-conversation-scope.ts: LcmConversationScope now carries BOTH
  conversationIds (PR Martian-Engineering#338-staged grep/describe/expand-query consumers) and
  relatedConversationIds (v0.9.4 recent/task-suggestions consumers). Both
  populated from a single family lookup that prefers the new
  getConversationFamilyIds shim and falls back to the older
  listConversationsBySessionKey for test mocks. allConversations rejection +
  family resolution kept together.

Store extensions:
- ObservedWorkStore.getDensity: accept conversationIds[] via the shared
  appendConversationScopeConstraint helper. Falls back to conversationId when
  no list is supplied.
- EventObservationStore.listObservations / listEpisodes: same pattern.
Copilot AI review requested due to automatic review settings May 4, 2026 17:20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75338ecf-ba72-46bc-80b8-ea61b2737b77

📥 Commits

Reviewing files that changed from the base of the PR and between 7f61622 and 68159c5.

📒 Files selected for processing (7)
  • src/store/conversation-scope.ts
  • src/store/conversation-store.ts
  • src/tools/lcm-conversation-scope.ts
  • src/tools/lcm-recent-tool.ts
  • test/lcm-expand-query-tool.test.ts
  • test/lcm-tools.test.ts
  • test/rollup-store-builder.test.ts

Cache: Disabled due to Reviews > Disable Cache setting

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

Adds optional multi-conversation scoping (conversationIds?: number[]) across retrieval, stores, and LCM tools; introduces appendConversationScopeConstraint to normalize/apply conversation-family SQL filters; wires conversation-family IDs from the scope resolver into grep, summary search, density, event queries, and related tooling.

Changes

Session Family Scoping Across Retrieval and Storage

Layer / File(s) Summary
Data Shape
src/retrieval.ts, src/store/summary-store.ts, src/store/observed-work-store.ts, src/tools/lcm-conversation-scope.ts
Add optional conversationIds?: number[] to GrepInput, SummarySearchInput, ObservedWorkDensityQuery, and LcmConversationScope.
Constraint Utility
src/store/conversation-scope.ts
New appendConversationScopeConstraint normalizes conversationIds (filter finite, trunc, dedup) and appends = ? or IN (...) SQL predicates, falling back to conversationId when appropriate.
Storage Layer Integration
src/store/event-observation-store.ts, src/store/observed-work-store.ts, src/store/summary-store.ts
Replace ad-hoc conversation_id = ? clauses with appendConversationScopeConstraint calls in listObservations, listEpisodes, getDensity, and multiple summary search implementations; thread conversationIds through private search helpers.
Retrieval Layer
src/retrieval.ts
RetrievalEngine.grep forwards conversationIds from input into underlying searchMessages/searchSummaries.
Conversation Scope Resolution
src/tools/lcm-conversation-scope.ts
Resolve and return conversationIds (family) via new collectFamilyConversationIds; when explicit single conversationId is used, return conversationIds: [id]; relatedConversationIds set to the family as well.
LCM Tool Integration
src/tools/lcm-grep-tool.ts, src/tools/lcm-event-search-tool.ts, src/tools/lcm-work-density-tool.ts, src/tools/lcm-task-suggestions-tool.ts
Tools now pass conversationIds: scope.conversationIds into store/retrieval calls; grep output displays “session family rooted at … (N segments)” when applicable.
Multi-Conversation Scoping Logic
src/tools/lcm-expand-query-tool.ts
resolveSourceConversationId accepts allowedConversationIds? and enforces membership; resolveSummaryCandidates accepts/forwards conversationIds, and fallback message grep spans multiple conversations, mapping message IDs back to their originating conversation leaf summaries.
Session-Scoped Authorization
src/tools/lcm-describe-tool.ts
Scope check uses allowed conversation set (conversationScope.conversationIds or fallback single id) and returns “Not found in this session scope” when item is outside scope.
Conversation Listing Ordering/Cap
src/store/conversation-store.ts
listConversationsBySessionKey now orders by active DESC, created_at DESC, conversation_id DESC and caps results with LIMIT 256.
Recent/ Rollup Family Logic
src/tools/lcm-recent-tool.ts
Freshness/rollup gating evaluates union of family rollupStore states (OR semantics for pending_rebuild and pending days), and live-fallback keys are chosen from the family pending-day union.
Tests / Test Helpers
test/lcm-tools.test.ts, test/lcm-expand-query-tool.test.ts, test/rollup-store-builder.test.ts
Mocks updated to support conversationFamilyIds; added tests for family-scoped grep, expand-query single-source selection, rejection of out-of-scope summaryIds, multi-conversation fallback mapping, and OR’ing pending_rebuild across family conversations.

Sequence Diagram

sequenceDiagram
  participant Tool as LCM Tool
  participant Retrieval as RetrievalEngine
  participant Store as Summary/Work/Event Store
  participant DB as Database

  Tool->>Retrieval: grep/search with conversationIds[]
  Retrieval->>Store: searchMessages/searchSummaries(conversationIds[])
  Store->>DB: SQL query with WHERE ... IN (conversationIds) / appended constraint
  DB-->>Store: rows
  Store-->>Retrieval: shaped results (messages/summaries)
  Retrieval-->>Tool: aggregated results (including family metadata)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: cherry-picking upstream PR #338 for session-family retrieval scope and extending it to v0.9.4 tools.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/session-family-scope-from-pr-338

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

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

Copilot AI 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.

Pull request overview

This PR extends “session-family” scoping so retrieval-style reads can span multiple physical conversation rows created by session-file rotation (/new, /reset), avoiding silent context loss when a session is split into archived segments.

Changes:

  • Adds a conversationIds (session-family) scope alongside the existing single conversationId, and threads it through multiple tools.
  • Extends stores/queries (summaries, observed work density, event search) to accept conversationIds and apply a shared SQL scoping helper.
  • Updates/extends tests to cover session-family scoping behavior for lcm_grep and lcm_expand_query.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/lcm-tools.test.ts Adds test coverage for lcm_grep passing session-family conversationIds and updated describe error wording.
test/lcm-expand-query-tool.test.ts Adds family-scope tests for lcm_expand_query selection and out-of-scope rejection.
src/tools/lcm-work-density-tool.ts Passes session-family conversationIds into observed-work density queries.
src/tools/lcm-task-suggestions-tool.ts Threads session-family conversationIds into density reads used by task suggestions.
src/tools/lcm-grep-tool.ts Passes conversationIds into retrieval grep and updates rendered scope string for families.
src/tools/lcm-expand-query-tool.ts Updates expand-query candidate resolution to consider family scopes and adds scope enforcement.
src/tools/lcm-event-search-tool.ts Passes conversationIds to event observation listing for family-aware event search.
src/tools/lcm-describe-tool.ts Enforces describe scoping against the allowed conversation family and updates the error message.
src/tools/lcm-conversation-scope.ts Introduces conversationIds on the scope object and resolves family IDs via store helpers.
src/store/summary-store.ts Adds conversationIds support to summary search paths (FTS/LIKE/regex/CJK variants).
src/store/observed-work-store.ts Adds conversationIds support for density scoping (family-aware reads).
src/store/event-observation-store.ts Adds conversationIds scoping for listing observations/episodes.
src/store/conversation-scope.ts Adds appendConversationScopeConstraint() helper to apply (conversationId vs conversationIds) SQL constraints consistently.
src/retrieval.ts Extends grep input to carry conversationIds through to store search calls.
Comments suppressed due to low confidence (1)

src/retrieval.ts:244

  • conversationIds is added to GrepInput, but RetrievalEngine.grep() passes the combined searchInput into conversationStore.searchMessages(), whose MessageSearchInput/implementation only honors conversationId. In session-family mode (where callers may set conversationId to undefined and rely on conversationIds), message searches become effectively unscoped (all conversations) and can leak/return results outside the intended session family. Fix by extending ConversationStore.searchMessages (and MessageSearchInput) to accept conversationIds and apply the same scope constraint logic as SummaryStore, or by explicitly fan-out searching per conversationId when conversationIds is provided.
  async grep(input: GrepInput): Promise<GrepResult> {
    const { query, mode, scope, conversationId, conversationIds, since, before, limit, sort } = input;

    const searchInput = { query, mode, conversationId, conversationIds, since, before, limit, sort };

    let messages: MessageSearchResult[] = [];
    let summaries: SummarySearchResult[] = [];

    if (scope === "messages") {
      messages = await this.conversationStore.searchMessages(searchInput);
    } else if (scope === "summaries") {
      summaries = await this.summaryStore.searchSummaries(searchInput);
    } else {
      // scope === "both" — run in parallel
      [messages, summaries] = await Promise.all([
        this.conversationStore.searchMessages(searchInput),
        this.summaryStore.searchSummaries(searchInput),
      ]);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1579 to 1588
const familyScopedConversationId =
(conversationScope.conversationIds?.length ?? 0) > 1
? undefined
: conversationScope.conversationId;
let scopedConversationId = familyScopedConversationId;
if (
!conversationScope.allConversations &&
scopedConversationId == null &&
(conversationScope.conversationIds?.length ?? 0) <= 1 &&
callerSessionKey
Comment on lines +1084 to +1089
const firstAllowed = params.candidates.find((candidate) =>
allowedConversationIds.has(candidate.conversationId),
);
if (firstAllowed) {
return firstAllowed.conversationId;
}
Comment on lines 112 to 122
const explicitConversationId =
typeof params.conversationId === "number" && Number.isFinite(params.conversationId)
? Math.trunc(params.conversationId)
: undefined;
if (explicitConversationId != null) {
return {
conversationId: explicitConversationId,
conversationIds: [explicitConversationId],
allConversations: false,
relatedConversationIds: [],
};

@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

Caution

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

⚠️ Outside diff range comments (3)
src/tools/lcm-work-density-tool.ts (1)

389-409: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Report the effective family scope in the payload.

The query is family-wide when scope.conversationIds has multiple entries, but conversationScope still serializes only scope.conversationId. That makes the response metadata disagree with the counts and items it contains.

Possible fix
-        conversationScope: scope.allConversations ? "all" : scope.conversationId,
+        conversationScope:
+          scope.allConversations
+            ? "all"
+            : scope.conversationIds && scope.conversationIds.length > 1
+              ? scope.conversationIds
+              : scope.conversationId,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/lcm-work-density-tool.ts` around lines 389 - 409, The payload's
conversationScope is incorrect for family-wide queries: when store.getDensity
was called with scope.conversationIds containing multiple IDs the metadata still
serialized scope.conversationId. Update the conversationScope argument passed
into applyOutputBudget (where density is set) to reflect the effective scope —
keep "all" when scope.allConversations is true, return "family" (or another
explicit family label) when scope.conversationIds exists and has length > 1, and
otherwise return scope.conversationId so the response metadata matches the
actual query made by store.getDensity.
src/retrieval.ts (1)

228-243: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Family-scoped grep still drops message hits.

  • searchInput now carries conversationIds, but src/store/conversation-store.ts:778-816 still scopes searchMessages(...) with only conversationId.
  • That means scope === "messages" and scope === "both" stay single-conversation for message hits while summaries span the whole family.

Please mirror the conversationIds support into ConversationStore.searchMessages(...), or keep grep family-scoped for summaries only until message search is updated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/retrieval.ts` around lines 228 - 243, The message-search path is still
only honoring conversationId while searchInput now includes conversationIds, so
update ConversationStore.searchMessages (the method and its internal use of
conversationId) to accept and handle conversationIds the same way
SummaryStore.searchSummaries does: change the signature to accept
conversationIds, apply family-scoped filtering/grep over the provided
conversationIds (falling back to conversationId or all conversations as before),
and ensure callers (where searchMessages is invoked with searchInput) pass
through the conversationIds field; alternatively, if you prefer not to implement
family-scoped message search yet, explicitly restrict the top-level retrieval
logic to not pass conversationIds into searchMessages and only support
family-scoped searches for summaries until message search is updated.
src/tools/lcm-event-search-tool.ts (1)

112-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return family scope metadata when results span multiple conversations.

listObservations(...) now reads across scope.conversationIds, but the payload still reports only scope.conversationId. For family-scoped searches, that makes the top-level metadata misleading even though each observation carries its own conversationId.

Possible fix
-        conversationScope: scope.allConversations ? "all" : scope.conversationId,
+        conversationScope:
+          scope.allConversations
+            ? "all"
+            : scope.conversationIds && scope.conversationIds.length > 1
+              ? scope.conversationIds
+              : scope.conversationId,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/lcm-event-search-tool.ts` around lines 112 - 126, The response
metadata currently always uses scope.conversationId even when listObservations
(via lcm.getEventObservationStore().listObservations) was run across
scope.conversationIds; update the jsonResult payload to compute
conversationScope from the actual observations: after calling listObservations,
collect distinct observation.conversationId values and if more than one distinct
id is present (or if scope.conversationIds.length > 1) set conversationScope to
a family/aggregate value (e.g. "family" or "all") otherwise set it to
scope.conversationId; change the assignment where jsonResult is built (the
conversationScope key) to use this computed value so top-level metadata reflects
multi-conversation results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tools/lcm-conversation-scope.ts`:
- Around line 204-234: collectFamilyConversationIds currently returns
getConversationFamilyIds results verbatim while listConversationsBySessionKey
results are mapped newest-first, causing unstable ordering for
relatedConversationIds; normalize ordering in collectFamilyConversationIds by
applying a consistent sort before returning: call
store.getConversationFamilyIds(...) (or map records from
listConversationsBySessionKey) and then sort the resulting number[] into the
documented newest-first order (use conversation timestamp or inferred ordering
field from the returned records if available), ensuring both paths
(getConversationFamilyIds and listConversationsBySessionKey) produce the same
stable newest-first sequence for relatedConversationIds.

---

Outside diff comments:
In `@src/retrieval.ts`:
- Around line 228-243: The message-search path is still only honoring
conversationId while searchInput now includes conversationIds, so update
ConversationStore.searchMessages (the method and its internal use of
conversationId) to accept and handle conversationIds the same way
SummaryStore.searchSummaries does: change the signature to accept
conversationIds, apply family-scoped filtering/grep over the provided
conversationIds (falling back to conversationId or all conversations as before),
and ensure callers (where searchMessages is invoked with searchInput) pass
through the conversationIds field; alternatively, if you prefer not to implement
family-scoped message search yet, explicitly restrict the top-level retrieval
logic to not pass conversationIds into searchMessages and only support
family-scoped searches for summaries until message search is updated.

In `@src/tools/lcm-event-search-tool.ts`:
- Around line 112-126: The response metadata currently always uses
scope.conversationId even when listObservations (via
lcm.getEventObservationStore().listObservations) was run across
scope.conversationIds; update the jsonResult payload to compute
conversationScope from the actual observations: after calling listObservations,
collect distinct observation.conversationId values and if more than one distinct
id is present (or if scope.conversationIds.length > 1) set conversationScope to
a family/aggregate value (e.g. "family" or "all") otherwise set it to
scope.conversationId; change the assignment where jsonResult is built (the
conversationScope key) to use this computed value so top-level metadata reflects
multi-conversation results.

In `@src/tools/lcm-work-density-tool.ts`:
- Around line 389-409: The payload's conversationScope is incorrect for
family-wide queries: when store.getDensity was called with scope.conversationIds
containing multiple IDs the metadata still serialized scope.conversationId.
Update the conversationScope argument passed into applyOutputBudget (where
density is set) to reflect the effective scope — keep "all" when
scope.allConversations is true, return "family" (or another explicit family
label) when scope.conversationIds exists and has length > 1, and otherwise
return scope.conversationId so the response metadata matches the actual query
made by store.getDensity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e7f35339-1d8a-4e35-911e-170d7bb9c5ec

📥 Commits

Reviewing files that changed from the base of the PR and between cf28b6e and 7f61622.

📒 Files selected for processing (14)
  • src/retrieval.ts
  • src/store/conversation-scope.ts
  • src/store/event-observation-store.ts
  • src/store/observed-work-store.ts
  • src/store/summary-store.ts
  • src/tools/lcm-conversation-scope.ts
  • src/tools/lcm-describe-tool.ts
  • src/tools/lcm-event-search-tool.ts
  • src/tools/lcm-expand-query-tool.ts
  • src/tools/lcm-grep-tool.ts
  • src/tools/lcm-task-suggestions-tool.ts
  • src/tools/lcm-work-density-tool.ts
  • test/lcm-expand-query-tool.test.ts
  • test/lcm-tools.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: smoke-latest-openclaw
  • GitHub Check: Agent
🔇 Additional comments (13)
src/tools/lcm-task-suggestions-tool.ts (1)

202-215: LGTM!

The addition of conversationIds: scope.conversationIds correctly extends density queries to span the session family. The dual-field approach (conversationId + conversationIds) aligns with the PR's stated conflict resolution strategy, and the underlying appendConversationScopeConstraint safely handles undefined/empty arrays.

src/tools/lcm-grep-tool.ts (2)

150-160: LGTM!

Correctly forwards conversationIds to the retrieval layer, enabling family-scoped grep. The dual-field pattern is consistent with other tools in this PR.


168-175: LGTM!

The output header logic correctly handles:

  • undefined/empty arrays → falls back to single conversationId display
  • Multiple segments → shows family root + segment count

Clean user feedback for the new scoping behavior.

src/tools/lcm-describe-tool.ts (1)

105-120: LGTM!

The scope validation correctly:

  • Prioritizes conversationIds array when populated
  • Falls back to single conversationId when array is empty/undefined
  • Uses Set membership for O(1) lookup
  • Updates error messaging to reflect session-scoped semantics

The guard at line 105 ensures we only build the allowed set when a valid scope exists.

src/tools/lcm-expand-query-tool.ts (3)

1051-1104: LGTM!

resolveSourceConversationId correctly extended:

  • Validates all candidates against allowedConversationIds when provided
  • Selects first allowed candidate when multiple family IDs exist
  • Falls through to existing single-conversation logic otherwise

The validation at lines 1073-1082 ensures we never reach the multi-select logic with out-of-scope candidates.


1140-1254: LGTM!

The resolveSummaryCandidates fallback path correctly handles session-family scoping:

  • Computes fallbackConversationIds from array or single ID
  • Checks max summary depth per conversation before allowing message fallback
  • Groups message hits by conversationId for per-conversation leaf link lookups
  • Maps results back to correct conversationId via messageConversationById

The "all must be shallow" check at lines 1200-1202 is appropriately conservative.


1579-1614: LGTM!

The tool wrapper correctly integrates family scoping:

  • familyScopedConversationId only set when family is singular (avoids ambiguity)
  • Falls back to requester resolution only when family doesn't provide scope
  • Error path guards against missing scope when family is singular
  • Threads conversationIds through to downstream functions
test/lcm-expand-query-tool.test.ts (3)

136-168: LGTM!

The test engine mock correctly extended to support conversationFamilyIds:

  • Returns explicit family IDs when provided
  • Falls back to wrapping single conversationId in array
  • Returns empty array when no conversation context exists

This accurately mirrors the production getConversationFamilyIds behavior.


303-423: LGTM!

Good coverage for session-family scoping in expand-query:

  • "resolves a single source conversation from a session family query" verifies grep receives family IDs and the tool correctly routes to the first allowed conversation
  • "rejects explicit summaryIds that fall outside the allowed session family scope" confirms the scope validation throws the expected error

Both tests validate the assertions against the specific error messages and call signatures.


2005-2125: LGTM!

Comprehensive test for the multi-segment fallback path:

  • Verifies grep is called twice (summaries then messages) with full family scope
  • Confirms per-conversation depth checks via getConversationMaxSummaryDepth calls
  • Validates per-conversation leaf link lookups with correct message ID grouping
  • Asserts final result routes to the expected family segment

Well-structured mock setup with distinct conversation segments.

test/lcm-tools.test.ts (3)

86-141: LGTM!

buildLcmEngine mock correctly extended:

  • Accepts optional conversationFamilyIds parameter
  • Mocks getConversationFamilyIds with proper fallback chain
  • Consistent with the expand-query test file pattern

307-347: LGTM!

Good test coverage for session-family grep:

  • Line 310: Updated assertion to include conversationIds: [42] alongside existing single ID check
  • Lines 315-347: New test validates multi-segment family search, verifying both the call signature and user-facing output format

Clear and targeted assertions.


399-401: LGTM!

Error message assertion correctly updated to match the new session-scope wording in lcm-describe-tool.ts.

Comment thread src/tools/lcm-conversation-scope.ts
Adversarial audits 1-4 found these issues. Addressed:

- MAJOR-1: lcm_recent freshness gate now ORs pending_rebuild across all family
  conversations. Without this, an auto-rotated session with conv A pending and
  conv B clean could serve A's stale rollup when queried via B.
- MAJOR-2: collectFamilyConversationIds normalizes ordering across both lookup
  paths (getConversationFamilyIds + listConversationsBySessionKey fallback).
  User-visible 'session family rooted at X' string is now stable.
- MAJOR-3: appendConversationScopeConstraint singleton path now Number.isFinite
  validates + Math.trunc, matching the array path.
- MAJOR-4: listConversationsBySessionKey now caps at 256 most-recent. Defends
  against IN-list overflow if a session_key accumulates 1000+ archived rows.
- MINOR: dropped unimplemented getConversationFamilyIds optional shape;
  collectFamilyConversationIds standardizes on listConversationsBySessionKey.

Added regression test: family pending_rebuild OR'd correctly.
@100yenadmin 100yenadmin merged commit 916ca58 into main May 4, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants