Skip to content

feat: add daily lcm_recent rollups#13

Closed
100yenadmin wants to merge 14 commits into
mainfrom
feat/lcm-daily-rollup-mvp
Closed

feat: add daily lcm_recent rollups#13
100yenadmin wants to merge 14 commits into
mainfrom
feat/lcm-daily-rollup-mvp

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented Apr 27, 2026

Copy link
Copy Markdown
Member

Summary

Adds the daily LCM temporal-rollup foundation and the first lcm_recent implementation. This PR is the lowest implementation slice: daily rollups, source provenance, compatibility views, strict date handling, and bounded direct SQL fallback over leaf summaries.

lcm_recent is intentionally a recap/window-entry tool. It helps locate likely time windows and source IDs; exact claims still require lcm_describe or lcm_expand_query.

What Changed

  • Added lcm_rollups, lcm_rollup_sources, lcm_rollup_state, compatibility views, and rollup indexes.
  • Added daily rollup builder with content-sensitive fingerprints, meaningful source-message counts, coverage bounds from source intervals, and transaction-safe source replacement.
  • Added lcm_recent for today, yesterday, date:YYYY-MM-DD, and rolling 7d/30d recap windows.
  • Kept missing-rollup behavior honest: fallback is a direct bounded SQL query over leaf summaries, not lcm_grep.
  • Hardened mixed timestamp comparisons with julianday(...), canonical UTC timestamp outputs, and expression indexes aligned to fallback queries.
  • Added timezone to rollup uniqueness and stored-rollup reads so a period key cannot silently cross timezone semantics.
  • Made rollup-state updates preserve omitted fields instead of read/merge/write clobbering them.
  • Removed dead retrieval/since/before code, gated drilldown hints and details summaryIds when sources are omitted, and capped multi-day combined rollup output.

Out Of Scope

Validation

  • npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 18 tests passed.
  • npm run build — passed.
  • git diff --check — clean.

Reviewer Notes

Known earlier review concerns addressed here: strict invalid-date rejection, SQL fallback wording, rollup-miss fallback coverage, compatibility views, content-change fingerprint invalidation, source-message counts, transaction propagation, TOCTOU lookup inside BEGIN IMMEDIATE, full-scan pending-state clearing, mixed timestamp comparisons, expression indexes, timezone-safe stored rollup lookup, UTC+13 date-key preservation, race-safe pending-state clearing, and deletion of stale direct day rollups and empty-day daily sweep rollups, day-scoped empty cleanup errors, and monotonic daily sweep last_rollup_check_at updates, final sweep-state failure reporting, resolved engine timezone wiring, source-ID redaction in tool details, explicit invalid-calendar-date errors, and multi-day output caps, and 30-day maintenance backfill for the 30d recall surface.

Summary by CodeRabbit

  • New Features

    • Timezone-aware daily/weekly/monthly rollups with per-conversation build state, deterministic maintenance builds, token-budget trimming, merged rollup content, and a new lcm_recent activity-report tool (with rollup lookup and fallback).
  • Performance

    • Added indexes and query optimizations to speed rollup and leaf-summary retrieval.
  • Tests

    • End-to-end tests for migrations, build idempotency, timezone edge cases, recent-tool fallbacks.
  • Chores

    • Changeset and minor release note added.

Copilot AI review requested due to automatic review settings April 27, 2026 21:24
@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 Apr 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds DB schema and views for rollups, a RollupStore and RollupBuilder to produce per-conversation daily markdown rollups with fingerprinting and token-budget trimming, engine hooks to mark/trigger rebuilds, a new lcm_recent tool with SQL fallback, tests, and a changeset.

Changes

Cohort / File(s) Summary
DB Migrations / Schema
src/db/migration.ts
Adds lcm_rollups, lcm_rollup_sources, lcm_rollup_state tables, partial and regular indexes (including summaries partial indexes for kind='leaf'), uniqueness on (conversation_id, period_kind, period_key), and read-only views daily_rollups/weekly_rollups/monthly_rollups.
Persistence Store
src/store/rollup-store.ts
New RollupStore and typed interfaces; transactional upsert/get/list/delete/markStale operations; replaceRollupSources transaction; per-conversation state getState/upsertState; getLeafSummariesForDay with deterministic ordering and correlated counts.
Rollup Builder
src/rollup-builder.ts
New RollupBuilder, helpers (getLocalDateKey, getLocalDayBounds, computeFingerprint), builds daily rollups from leaf summaries, renders timeline/key-items/statistics, enforces token budgets (iterative trimming), and persists rollup + ordered sources inside a transaction.
Engine Integration
src/engine.ts
Engine initializes/exposes RollupStore and RollupBuilder (added getters); ingestSingle flags pending_rebuild for non-heartbeat messages; maintain() reads state and invokes buildDailyRollups(conversationId, { daysBack: 7, forceCurrentDay: true }), logs results, and continues on individual errors.
Tooling / Plugin
src/tools/lcm-recent-tool.ts, src/plugin/index.ts
Adds lcm_recent tool and registers it; normalizes period inputs to timezone-aware bounds/keys; prefers aggregated rollups when available (requires complete coverage), merges/deduplicates sources and tokens; falls back to bounded leaf-summary SQL when rollups are missing/incomplete; returns markdown plus structured details.
Tests & Release Note
test/rollup-store-builder.test.ts, .changeset/lcm-daily-rollups.md
Adds Vitest integration tests covering migrations, rollup build/idempotency/fingerprints, timezone edge cases, tool fallback/invalid inputs, and a changeset for a minor release.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Plugin as Plugin/\nToolHandler
    participant Engine as LcmContext\nEngine
    participant Builder as RollupBuilder
    participant RollupStore as RollupStore
    participant DB as Database

    Client->>Plugin: call lcm_recent(period)
    Plugin->>RollupStore: listRollups(conversationId, periodKind)
    alt rollups exist & ready
        RollupStore->>DB: SELECT lcm_rollups...
        DB-->>RollupStore: rollup rows
        RollupStore-->>Plugin: rollup content + metadata
        Plugin-->>Client: return rollup-based markdown
    else missing/incomplete/stale
        Plugin->>RollupStore: getLeafSummariesForDay(dateRange)
        RollupStore->>DB: SELECT summaries(kind='leaf')...
        DB-->>RollupStore: leaf summaries
        RollupStore-->>Plugin: leaf summaries
        Plugin-->>Client: return fallback markdown
    end

    rect rgba(100,150,200,0.5)
    Note over Engine,Builder: Background maintenance flow
    Engine->>Engine: ingestSingle(message)
    Engine->>RollupStore: upsertState(conversationId, pending_rebuild=1, last_message_at)
    Engine->>Engine: maintain()
    Engine->>RollupStore: getState(conversationId)
    RollupStore-->>Engine: state
    alt pending_rebuild
        Engine->>Builder: buildDailyRollups(conversationId, daysBack=7, forceCurrentDay=true)
        Builder->>RollupStore: getLeafSummariesForDay(day)
        RollupStore->>DB: SELECT summaries...
        DB-->>RollupStore: summaries
        RollupStore-->>Builder: summary rows
        Builder->>Builder: computeFingerprint()/render/trim tokens
        Builder->>DB: BEGIN IMMEDIATE
        Builder->>RollupStore: upsertRollup(...)
        Builder->>RollupStore: replaceRollupSources(ordered list)
        Builder->>RollupStore: upsertState(pending_rebuild=0)
        Builder->>DB: COMMIT
        Builder-->>Engine: BuildResult
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.75% 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
Title check ✅ Passed The title 'feat: add daily lcm_recent rollups' directly and concisely describes the main change: adding a daily rollup feature for the lcm_recent tool.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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/lcm-daily-rollup-mvp

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

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@100yenadmin

Copy link
Copy Markdown
Member Author

Pushed hardening fixes in 0d2bedb.

Addressed the blocking review points on the daily-rollup foundation:

  • replaceRollupSources now propagates transaction failures instead of discarding them
  • lcm_rollup_state updates are atomic (INSERT ... ON CONFLICT DO UPDATE) instead of N+1 writes
  • day leaf-summary lookup now uses overlap predicates with julianday(...) to avoid mixed TEXT timestamp bugs
  • pending_rebuild is cleared only after the full sweep, so later failures still retry
  • maintenance now backfills when rollup state is missing and runs rollup rebuilding after summary-producing maintenance
  • ingest-side dirty marking is fault-isolated so rollup-state write failures do not break core ingestion
  • rollup builder uses the engine’s effective timezone
  • added a required changeset for the user-facing feature
  • tightened daily token-budget handling so key-item-heavy days shrink instead of bypassing the cap
  • removed duplicate local variable and no-op stale transition in the daily builder

Verification:

  • npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts
  • npm run build

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@100yenadmin

Copy link
Copy Markdown
Member Author

Pushed follow-up fixes in 4a33546.

Closed the remaining mergeability gaps from review:

  • corrected lcm_recent wording to describe the actual direct SQL leaf-summary fallback (not lcm_grep)
  • added strict round-trip calendar validation for date:YYYY-MM-DD parsing so impossible dates like 2026-02-31 are rejected
  • tightened parseDateKey() with the same impossible-date rejection
  • moved the existing rollup lookup inside the BEGIN IMMEDIATE transaction in buildDayRollup() to eliminate the TOCTOU window around rollup-id reuse
  • removed the duplicate full-row upsertRollup() write; the transactional build now persists the final ready row once, then replaces sources and updates state
  • expanded rollup tests to assert weekly_rollups and monthly_rollups compatibility views alongside daily_rollups
  • added regression coverage for impossible date-key rejection

Verification:

  • npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts
  • npm run build

At this point I don’t see any remaining blocking concerns for PR #13.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@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/db/migration.ts (1)

1046-1048: 🧹 Nitpick | 🔵 Trivial

Drop legacy summaries_leaf_temporal_idx; keep expression indexes only.

  • Line 1046-1048 keeps a raw-column index that doesn’t match the current julianday(coalesce(...)) temporal predicates.
  • This adds avoidable write/update index maintenance on summaries.
Proposed migration diff
-      CREATE INDEX IF NOT EXISTS summaries_leaf_temporal_idx
-      ON summaries (conversation_id, kind, latest_at, earliest_at, created_at)
-      WHERE kind = 'leaf';
#!/bin/bash
set -euo pipefail

echo "Find temporal leaf-summary predicates used by query code:"
rg -n -C2 "julianday\\(coalesce\\(earliest_at, latest_at, created_at\\)\\)|julianday\\(coalesce\\(latest_at, earliest_at, created_at\\)\\)" src --type ts

echo
echo "Find any leaf-summary query still relying on raw latest/earliest ordering/filtering:"
rg -n -C3 "kind\\s*=\\s*'leaf'|kind\\s*=\\s*\"leaf\"" src --type ts | rg -n "latest_at|earliest_at|created_at"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migration.ts` around lines 1046 - 1048, The migration currently
creates a legacy raw-column index summaries_leaf_temporal_idx on summaries
(conversation_id, kind, latest_at, earliest_at, created_at) which is redundant
and harmful because query code uses expression indexes based on
julianday(coalesce(...)); remove the CREATE INDEX for
summaries_leaf_temporal_idx and replace it with a DROP INDEX IF EXISTS
summaries_leaf_temporal_idx in the migration so only the expression indexes
remain, and verify no code relies on raw latest_at/earliest_at ordering before
applying the change (search for uses of latest_at/earliest_at/created_at in leaf
queries and the julianday(coalesce(...)) expression references to confirm
compatibility).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/db/migration.ts`:
- Around line 1046-1048: The migration currently creates a legacy raw-column
index summaries_leaf_temporal_idx on summaries (conversation_id, kind,
latest_at, earliest_at, created_at) which is redundant and harmful because query
code uses expression indexes based on julianday(coalesce(...)); remove the
CREATE INDEX for summaries_leaf_temporal_idx and replace it with a DROP INDEX IF
EXISTS summaries_leaf_temporal_idx in the migration so only the expression
indexes remain, and verify no code relies on raw latest_at/earliest_at ordering
before applying the change (search for uses of latest_at/earliest_at/created_at
in leaf queries and the julianday(coalesce(...)) expression references to
confirm compatibility).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70c6a54a-ac15-4fe2-a7ec-3ff4c8f4e41a

📥 Commits

Reviewing files that changed from the base of the PR and between 1d91bd0 and a3eb81e.

📒 Files selected for processing (1)
  • src/db/migration.ts
📜 Review details
🔇 Additional comments (2)
src/db/migration.ts (2)

989-1033: Rollup table schema is coherent and constraint-safe.

  • Good use of CHECK constraints and FK links for lcm_rollups, lcm_rollup_sources, and lcm_rollup_state.
  • UNIQUE (conversation_id, period_kind, period_key) correctly enforces one logical rollup per period key.

1067-1078: Compatibility views are clean and useful.

  • The day/week/month views provide stable query surfaces without duplicating storage logic.

devin-ai-integration[bot]

This comment was marked as resolved.

@100yenadmin

Copy link
Copy Markdown
Member Author

Current-head triage summary

Head: 60e5b539506e841d8f12eb936184f9983945f00e

Validation:

  • npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 12 tests passed.
  • npm run build — passed.
  • git diff --check clean.

Thread matrix:

  • SQL fallback wording / “time-bounded lcm_grep” claim: fixed. Description now says direct bounded SQL leaf-summary fallback.
  • Week/month overclaim in daily MVP: fixed/clarified. This branch supports daily/exact/rolling daily recap; calendar aggregate rollups are explicitly follow-up scope.
  • Invalid plain dates: fixed with strict real-calendar validation and tests.
  • Mixed timestamp comparisons: fixed with interval overlap and julianday(...) predicates.
  • Fallback query index mismatch: fixed. Added partial expression indexes aligned to the fallback overlap expressions and removed the redundant plain temporal index from the migration.
  • Naive UTC timestamp outputs: fixed. Leaf-summary rows now return canonical UTC timestamp strings for JS parsing.
  • replaceRollupSources async transaction handling: fixed. The promise is awaited so failures propagate.
  • buildDayRollup TOCTOU lookup: fixed. Existing rollup lookup occurs inside BEGIN IMMEDIATE.
  • pending_rebuild spin / premature clearing: fixed. Full scans update last_rollup_check_at and clear/retry pending state after the scan result.
  • Content-change fingerprints: fixed. Fingerprints include content and temporal fields, not only IDs/token totals.
  • Source-message counts: fixed. Counts reflect source messages, not token totals.
  • Compatibility views / SQL fallback / rollup-present paths: covered by tests.
  • Stored-rollup timezone semantics: fixed. Rollup uniqueness and reads include timezone.
  • upsertState clobber risk: fixed. Updates preserve omitted fields atomically in SQL.
  • Dead retrieval/since/before code: fixed.
  • Drilldown hint when includeSources=false: fixed. The tool now asks the caller to re-run with sources instead of referring to hidden IDs.
  • recall-tools.md discoverability: handled in docs: plan LCM temporal memory architecture #12, where shipped skill docs are availability-gated.
  • Hardcoded fallback LIMIT 20 / duplicated formatting helpers / listRollups SELECT duplication: nonblocking polish. The fallback is intentionally bounded for the MVP; feat: add sub-day lcm_recent windows #14/feat: add weekly and monthly lcm rollups #15 improve higher-recall paths with rollups.

Remaining concerns: none known for the daily MVP slice.

@100yenadmin

Copy link
Copy Markdown
Member Author

Latest review-thread triage after 8e5d421

Validation at this head:

  • npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 13 tests passed.
  • npm run build — passed.
  • git diff --check — clean.
Comment ID Status Evidence Action
3153906664 fixed buildDailyRollups() now derives candidate days with shiftDateKey(todayKey, -offset), and buildDayRollup() uses getLocalDayBoundsForDateKey(dateKey, timezone) rather than a UTC-noon surrogate. Added UTC+13 regression coverage. Resolving.
3150297222 fixed buildDailyRollups() now updates last_rollup_check_at and clears pending_rebuild after a successful full scan, including skipped/empty scans. It preserves pending when last_message_at advanced during the scan. Resolving.
3150297116 fixed/stale Tool description now says direct SQL leaf-summary fallback and explicitly scopes week/month aggregate rollups to the follow-up PR. Resolving.
3150297284 fixed Tests now cover schema/views, daily rollup use, SQL fallback, invalid dates, empty-scan state updates, content-change fingerprints, and UTC+13 daily bounds. Resolving.
3150297080, 3150297242 stale after newer commit Fallback and leaf-summary scans use julianday(...) comparisons and canonical UTC output formatting. Resolving.
3150297148, 3150297193 stale after newer commit Source replacement errors now propagate through awaited transaction flow. Resolving.
3150307392, 3153797461 valid non-blocking polish All-conversation fallback remains intentionally bounded in the MVP; larger budgeted all-conversation controls arrive in the aggregate/deepening slice. Leaving explained in summary; not a blocker.
3153544741 delegated to PR #12 Skill/reference guidance is intentionally owned by the docs/spec PR in this stack, which now gates lcm_recent availability and documents recap-vs-proof. Not a code blocker for this slice.
3151696627, 3153907149 incorrect / safe by construction pending_rebuild=0 is the intended maintenance short-circuit; ingest and timezone/maintenance paths mark the state dirty when rebuild work exists. Resolving.
3153906868 safe by construction Intl hour 24 is normalized in the shared local-time formatter path and covered by the temporal parser tests in descendant slices. Resolving.

Remaining concerns: no current-head blocker identified for the daily MVP slice.

@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

🤖 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/rollup-builder.ts`:
- Around line 179-187: The current logic clears pending_rebuild based only on
last_message_at vs scannedAt which can overwrite a newer rebuild flag set after
scannedAt; change the update to only clear pending_rebuild when you can verify
the row is the same causal version that you inspected—e.g., read a
generation/version from latestState (latestState.dirty_generation) when
computing shouldClearPending and pass that generation into the write to
this.store (either via a new compare-and-swap API like
this.store.compareAndSwapState(conversationId, expectedGeneration, update) or by
extending this.store.upsertState to accept an expectedGeneration), and set
pending_rebuild to 0 only if the stored generation equals the expected
generation; otherwise leave pending_rebuild as 1. Ensure to reference
latestState, scannedAt, result.errors, pending_rebuild, and conversationId when
implementing the conditional write.
🪄 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: 32008203-7297-4d61-85b5-d162169a4a45

📥 Commits

Reviewing files that changed from the base of the PR and between 60e5b53 and 8e5d421.

📒 Files selected for processing (2)
  • src/rollup-builder.ts
  • test/rollup-store-builder.test.ts

@100yenadmin

Copy link
Copy Markdown
Member Author

Follow-up after 44de084: direct buildDayRollup() now deletes an existing stale daily rollup when the rebuilt day has no leaf summaries. Added regression coverage. Validation: npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 14 tests passed; npm run build — passed; git diff --check — clean.

coderabbitai[bot]

This comment was marked as resolved.

@100yenadmin

Copy link
Copy Markdown
Member Author

Follow-up after fb6dfc8: daily sweep now deletes existing rollups for days whose last leaf summary disappeared, so maintenance reaches the same stale-row cleanup as direct rebuilds. Validation: npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 15 tests passed; npm run build — passed; git diff --check — clean.

coderabbitai[bot]

This comment was marked as resolved.

@100yenadmin

Copy link
Copy Markdown
Member Author

Follow-up hardening landed in 4ca89da.\n\nAddressed the active CodeRabbit blocker:\n- The daily sweep no longer overwrites last_rollup_check_at with the pre-sweep scan timestamp after a later day build writes fresher state.\n- Empty-day cleanup is now inside day-scoped error handling, so cleanup failures are recorded and final state is still persisted.\n\nValidation on PR #13:\n- npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 16 tests passed.\n- npm run build — passed.\n- git diff --check — clean.\n\nThe active blocker thread was replied to with evidence and resolved via GraphQL.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 0 new potential issues.

Open in Devin Review

@100yenadmin

Copy link
Copy Markdown
Member Author

Follow-up hardening landed in 7be18db.\n\nAddressed current-head review items:\n- Intl formatters used for temporal midnight/offset math now use hourCycle: h23, avoiding the Node/Intl hour-24 midnight rollover path.\n- Remaining current-head notes were classified as non-blocking or safe by construction with inline evidence.\n\nValidation on current #13 head:\n- npm test -- --run test/rollup-store-builder.test.ts test/lcm-tools.test.ts — 16 tests passed.\n- npm run build — passed.\n- git diff --check — clean.

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread src/rollup-builder.ts
this.config.timezone
);
if (existing) {
this.store.deleteRollup(existing.rollup_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automatic rollup deletion in daily sweep also violates AGENTS.md lossless rule

Same issue as the buildDayRollup path: in buildDailyRollups at line 138, when an empty-day is detected during the sweep, the existing rollup is deleted instead of marked stale. This is the second call site for the same AGENTS.md violation — automatic maintenance code performing destructive recovery instead of using a stale marker.

Suggested change
this.store.deleteRollup(existing.rollup_id);
this.store.markStale(existing.rollup_id);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/rollup-builder.ts
Comment on lines +16 to +20
export interface RollupBuilderConfig {
timezone: string;
dailyTargetTokens?: number;
dailyMaxTokens?: number;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: No new plugin config options added — openclaw.plugin.json update not needed

The RollupBuilderConfig type has dailyTargetTokens and dailyMaxTokens fields, but these are only used internally by RollupBuilder and are not exposed as user-facing plugin config options. The engine constructs the builder with only timezone (src/engine.ts:1507-1509), using default values for the token settings. Per the AGENTS.md Config Schema Sync rule, no openclaw.plugin.json or docs update is needed for this PR.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@100yenadmin

Copy link
Copy Markdown
Member Author

Consolidation note: this public fork PR remains review provenance for the LCM temporal-memory stack. I consolidated the docs + daily + sub-day + aggregate/debug layers onto current upstream main in Martian-Engineering#516 so upstream reviewers can inspect one clean current-main diff. This PR should be read as historical/layered context for that upstream PR rather than the current merge target.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread src/rollup-builder.ts
Comment on lines +226 to +276
const summaries = this.getLeafSummariesForDay(conversationId, start, end)
.filter((summary) => summary.kind === "leaf")
.sort(compareSummariesChronologically);

if (summaries.length === 0) {
const existing = this.store.getRollup(
conversationId,
PERIOD_KIND,
dateKey,
this.config.timezone
);
if (existing) {
this.store.deleteRollup(existing.rollup_id);
return true;
}
return false;
}

const totalSourceTokens = summaries.reduce(
(sum, summary) => sum + safeTokenCount(summary.tokenCount),
0
);
const sourceMessageCount = summaries.reduce(
(sum, summary) => sum + safeTokenCount(summary.sourceMessageCount),
0
);
const fingerprint = computeFingerprint(
summaries.map((summary) => ({
id: summary.summaryId,
tokenCount: summary.tokenCount,
content: summary.content,
updatedAt: summary.updatedAt,
createdAt: summary.createdAt,
earliestAt: summary.earliestAt,
latestAt: summary.latestAt,
sourceCount: summary.sourceMessageCount,
}))
);
const draft = buildDailyRollupContent({
dateKey,
summaries,
timezone: this.config.timezone,
maxTokens: this.dailyMaxTokens,
});
const builtAt = new Date();
const sourceSummaryIds = JSON.stringify(
summaries.map((summary) => summary.summaryId)
);
const coverageStart = minSummaryTime(summaries)?.toISOString() ?? null;
const coverageEnd = maxSummaryTime(summaries)?.toISOString() ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Summaries read outside the transaction in buildDayRollup

In src/rollup-builder.ts:226-228, the leaf summaries are fetched and processed OUTSIDE the withDatabaseTransaction block at line 277. The fingerprint, draft content, and source metadata are all computed from this pre-transaction read. If summaries change concurrently between the read and the write transaction, the rollup would be built on stale data. This is mitigated by: (1) the source_fingerprint is stored, so the next sweep detects the mismatch and triggers a rebuild; (2) the engine's maintenance code serializes per-conversation work; (3) BEGIN IMMEDIATE on the write path prevents concurrent writers. This is an eventual-consistency trade-off rather than a correctness bug, but worth noting for future reviewers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@100yenadmin

Copy link
Copy Markdown
Member Author

Closing this local/fork PR as superseded by the canonical upstream temporal-spine PR:

The consolidated architecture and sprint map is now recorded in:

  • docs/projects/lcm/spec-lcm-ultimate-architecture-plan.md

This local PR remains useful as historical source material only; future work should happen upstream.

@100yenadmin

Copy link
Copy Markdown
Member Author

Closed as part of LCM PR-stack cleanup. See the preceding comment for the canonical upstream PR or follow-up issue.

@100yenadmin

Copy link
Copy Markdown
Member Author

Fork cleanup note: daily lcm_recent rollup scope is superseded by upstream Martian-Engineering/lossless-claw PR Martian-Engineering#516. Follow-up hardening and observed-work integration live in PR Martian-Engineering#526 and descendants.

100yenadmin pushed a commit that referenced this pull request May 6, 2026
… + 8 P1 closed

Wave 8 ran 22 parallel Opus 1M-context agents at ~1k LOC each across
the full PR — verifying Wave-7 fixes don't break untouched surfaces +
catching pre-existing bugs Wave-7 missed. Surfaced 1 NEW P0 +
~10 P1s + many P2/P3.

P0 (CRITICAL — Wave-4+W6 anti-fabrication validation was no-op for months)
==========================================================================

Wave-8 Auditor #7-12 P0-1: lcm_expand_query citedIds validation has
been silently broken since Wave-4 introduced it. The `runDelegatedExpandQuery`
function calls `params.lcm.getDb()` for citation validation against the
summaries table, BUT `RunDelegatedExpandQueryParams` never declared a
`lcm` field, AND neither call site (lines 1232, 1283) passed `lcm`.
Every validation call threw `TypeError: Cannot read properties of
undefined (reading 'getDb')`. The empty `catch {}` on the validation
block silently swallowed the error → `citedIdsValidated` stayed as
the unvalidated set, `citedIdsRejectedAsFabricated` and
`citedIdsExceededValidationCap` stayed at 0 forever.

This means: an LLM that hallucinates `sum_xyz` IDs has those IDs
surfaced unchanged to the caller, with telemetry that LOOKS like
validation ran. The entire W4 + W6 anti-fabrication contract was
fictional in production.

Fix:
1. Added `lcm: LcmContextEngine` to `RunDelegatedExpandQueryParams`.
2. Both call sites now pass `lcm: input.lcm` (the tool's LcmContextEngine).
3. Replaced empty `catch {}` with logging + graceful degrade — so any
   future regression in validation is visible in logs instead of
   silently no-op.

P1 — HIGH IMPACT (8 closed)
============================

Auditor #7-12 P1-1: conversation-store.searchRegex lacked SQL LIMIT
(W8 R1 fixed summary-store but missed the parallel conversation-store).
Without LIMIT, regex grep on broad scope materialized the entire
messages table into memory before the JS-side MAX_ROW_SCAN cap.
Fix: bind SQL_SCAN_BOUND=10000 LIMIT.

Auditor #7-12 P1-4: lcm_describe budget=0 status reused
"skipped-non-summary" for "delegated-grant exhausted" — programmatic
consumers couldn't distinguish file-target skips from budget-exhausted
skips. Fix: distinct `"budget-exhausted"` status added to both
expandChildrenStatus + expandMessagesStatus union types.

Auditor #7-12 P1-5: verbatim grep FTS-fallback hard-coded
`ftsBindIndex = 0` with a brittle "always pushed first" comment. Any
future refactor moving the FTS bind would silently break the LIKE
fallback substitution. Fix: track `ftsBindIndex = binds.length` AT
THE PUSH SITE so it's correct regardless of bind order.

Auditor #13-18 E-P1: purge.ts resolved targetLeaves OUTSIDE the
BEGIN IMMEDIATE transaction — race window where a concurrent
suppression update between resolve + apply caused audit-trail loss
(stale `suppress_reason` overwrite). Fix: extracted runSoftPurgeAtomic
that opens BEGIN IMMEDIATE first, resolves INSIDE the tx, then runs
the cascade. Old runSoftPurge kept as a backward-compat shim.

Auditor #19-22 B-P1-1: PR_DESCRIPTION Scenario 1 ("yesterday") said
to use `window_kind: 'time'` — but every other doc routes "yesterday"
through `window_kind: 'period'` (the lcm_recent replacement). Fix:
corrected to period mode.

Auditor #19-22 A-P1-1: v41-live-db-harness cost comment was 100×
wrong ($0.05 vs actual $0.001 for 5K tokens at $0.18/1M). Fix:
corrected math + reasoning.

Wave-8 R1 (already shipped earlier in this session, included for
completeness):
- best-of-N 1-survivor short-circuit was returning a malformed
  SynthesizeResult missing 4 required fields (primaryPromptId,
  auditIds, totalLatencyMs, totalCostCents) — TYPE CONTRACT VIOLATION
- judge `parseJudgeOutput(_, ctx.bestOfN)` should be
  `parseJudgeOutput(_, candidateResults.length)` — judge can pick
  out-of-bounds index when survivors < requested N
- summary-store.searchRegex SQL LIMIT
- semantic-search filteredAfterJoin: false on entity-only branch
- getLeafSummaryLinksForMessageIds suppression filter

REGRESSION TESTS ADDED (4 new)
================================

Wave-7 P0-2 message-orphan: 2 tests covering shared-message
suppression invariants (only suppress when ALL referencing leaves
purged together).

Wave-8 P0 best-of-N short-circuit: 2 tests covering the
1-survivor-returns-complete-SynthesizeResult contract + the
judge-survivor-count fix.

VERIFICATION
============
- 1349/1349 tests passing (1345 + 4 new regression)
- QA runner full: 30/30 pass
- Total cost ~$0.11 per full QA run

DEFERRED (acknowledged)
========================
- Auditor #13-18 L-P1: reconcileSessionTail partial-failure semantics
  (pre-existing, not introduced by Wave-7 ingestSingle wrap).
- Auditor #13-18 J-P1: eval timeout indistinguishable from genuine
  zero-recall — needs RecallResult shape change; cycle-3.
- Auditor #7-12 P1-2: expandRecursive token-cap overshoot — minor
  contract violation, in-flight; defer.
- Auditor #7-12 P1-3: describe raw-count COUNT(*) performance — needs
  schema-level (parent_summary_id) index check, deferred.
- Auditor #7-12 P0-2 hybrid confidenceBand mislabel on rerank — W7
  added confidenceBandSource field (cosine|rerank); thresholds were
  re-evaluated as P3 calibration concern.
- Auditor #19-22 D: 7 of 11 W7+W8 fixes still lack regression tests
  (4 added in this commit; 7 deferred to a follow-up backfill PR).
- Various P2/P3 cosmetic items.

Audit cycle stats:
- Waves 1-3: 32 P1+ findings closed
- Wave 4 (full PR, 22 agents): 7 P0 + 30 P1 closed
- Wave 5: 4 P1 + 4 P2 closed (focused on W4 changes)
- Wave 6: 0 P1 + 4 P2 closed (focused on W5 changes + integration)
- Wave 7 (full PR re-audit, 22 agents): 7 P0 + 15 P1 closed
- Wave 8 (full PR re-audit, 22 agents): 1 P0 + 8 P1 closed
TOTAL: ~140 unique findings closed across 8 waves
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.

3 participants