Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c319a17
test: add failing tests for Issue #675 (regex fallback) and #676 (han…
jlin53882 Apr 20, 2026
30ffe96
fix: Issue #675 #676 - regex fallback and handleSupersede batch writes
jlin53882 Apr 20, 2026
e832aa9
fix(handleSupersede): invalidate old entry in batch mode via invalida…
jlin53882 Apr 21, 2026
5ce0551
test: rewrite supersede test with real SmartExtractor via jiti
jlin53882 Apr 21, 2026
388b47b
test: rewrite regex-fallback test with real MemoryStore via jiti
jlin53882 Apr 21, 2026
da3d12b
fix(handleSupersede): add error handling to invalidateEntries loop
jlin53882 Apr 21, 2026
6e4f48d
test: register new test files in CI manifest
jlin53882 May 4, 2026
b8b2885
fix: restore invalidateEntries fix and fix scope-filter/lock-stale tests
jlin53882 Apr 21, 2026
64b007c
fix: Must Fix #1 #2 - api.logger undefined + Issue #675 index.ts bulk…
jlin53882 Apr 22, 2026
dae57b1
test(RF-1): add invalidation error handler regression test
jlin53882 Apr 22, 2026
9e017aa
fix: align CI manifest baseline + register RF-1 regression test
jlin53882 Apr 22, 2026
085f9c9
fix: decouple mdMirror from bulkStore try-catch to prevent store.stor…
Apr 28, 2026
b7ecbde
fix(handleSupersede): backfill superseded_by backlink in batch mode
Apr 29, 2026
234fa43
fix(handleSupersede): add rollback on partial invalidation update fai…
Apr 29, 2026
fa8ecdd
fix(RF-1): correct test expectations to match actual rollback behaviour
Apr 29, 2026
ae81382
fix(rollback): correct rollback to target succeeded entries instead o…
May 4, 2026
33f14ef
refactor: add InvalidateEntry interface, eliminate all as any casts i…
May 4, 2026
fa86d10
fix: add try-catch around embed/categorize in regex fallback (F3)
jlin53882 May 4, 2026
9c9be07
fix(smart-extractor): F3 rollback now deletes bulkStore new entries
jlin53882 May 5, 2026
4730ce1
fix(F2): rollback deletes ALL newEntryIds, not just succeeded
jlin53882 May 5, 2026
8d97de6
fix(TC-6): correct assertion for MR2 dedup behavior
jlin53882 May 5, 2026
024770e
Merge remote-tracking branch 'upstream/master' into fix/issue-675-676…
jlin53882 May 5, 2026
a4bfe33
fix(ci): add missing issue606 entry to EXPECTED_BASELINE
jlin53882 May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 57 additions & 16 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3199,8 +3199,13 @@ const memoryLanceDBProPlugin = {
`memory-lancedb-pro: regex fallback found ${toCapture.length} capturable text(s) for agent ${agentId}`,
);

// Store each capturable piece (limit to 2 per conversation)
let stored = 0;
// FIX #675: Collect entries and use bulkStore() once (1 lock instead of N).
// Limit to 2 capturable pieces per conversation.
const capturedEntries: Array<{
text: string; vector: number[]; importance: number;
category: string; scope: string; metadata: string;
}> = [];

for (const text of toCapture.slice(0, 2)) {
if (isUserMdExclusiveMemory({ text }, config.workspaceBoundary)) {
api.logger.info(
Expand All @@ -3209,8 +3214,17 @@ const memoryLanceDBProPlugin = {
continue;
}

const category = detectCategory(text);
const vector = await embedder.embedPassage(text);
let vector: number[];
let category: string;
try {
category = detectCategory(text);
vector = await embedder.embedPassage(text);
} catch (err) {
api.logger.warn(
`memory-lancedb-pro: regex fallback embed/categorize failed for agent ${agentId}, skipping text: ${String(err)}`,
);
continue;
}

// Check for duplicates using raw vector similarity (bypasses importance/recency weighting)
// Fail-open by design: dedup should not block auto-capture writes.
Expand All @@ -3229,7 +3243,7 @@ const memoryLanceDBProPlugin = {
continue;
}

await store.store({
capturedEntries.push({
text,
vector,
importance: 0.7,
Expand Down Expand Up @@ -3261,21 +3275,48 @@ const memoryLanceDBProPlugin = {
),
),
});
stored++;
}

// Dual-write to Markdown mirror if enabled
if (mdMirror) {
await mdMirror(
{ text, category, scope: defaultScope, timestamp: Date.now() },
{ source: "auto-capture", agentId },
// FIX #675: bulkStore once (1 lock for N entries) instead of N store.store() calls (N locks).
// FIX #Bug-1 (post-Codex-review): mdMirror errors are handled separately and do NOT
// trigger the store.store() fallback (which would create duplicate rows).
if (capturedEntries.length > 0) {
try {
await store.bulkStore(capturedEntries);
api.logger.info(
`memory-lancedb-pro: auto-captured ${capturedEntries.length} memories for agent ${agentId} in scope ${defaultScope} (bulkStore)`,
);
} catch (err) {
api.logger.warn(
`memory-lancedb-pro: bulkStore failed for ${capturedEntries.length} entries, falling back to individual store: ${String(err)}`,
);
// Fallback: store individually (less efficient but preserves the data)
for (const entry of capturedEntries) {
await store.store(entry);
}
api.logger.info(
`memory-lancedb-pro: auto-captured ${capturedEntries.length} memories for agent ${agentId} (individual fallback)`,
);
}
}

if (stored > 0) {
api.logger.info(
`memory-lancedb-pro: auto-captured ${stored} memories for agent ${agentId} in scope ${defaultScope}`,
);
// FIX #Bug-1: mdMirror is called AFTER bulkStore succeeds, with its own
// error handling. If mdMirror fails, bulkStore is ALREADY committed —
// we log the error and continue. We do NOT retry via store.store()
// (which would create duplicate rows in LanceDB).
if (mdMirror) {
for (const entry of capturedEntries) {
try {
await mdMirror(
{ text: entry.text, category: entry.category, scope: entry.scope, timestamp: Date.now() },
{ source: "auto-capture", agentId },
);
} catch (mdErr) {
api.logger.warn(
`memory-lancedb-pro: mdMirror failed for entry "${entry.text.slice(0, 40)}…", bulkStore already committed: ${String(mdErr)}`,
);
}
}
}
}
} catch (err) {
api.logger.warn(`memory-lancedb-pro: capture failed: ${String(err)}`);
Expand Down
49 changes: 49 additions & 0 deletions pr_body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## F3 Fix: Rollback Now Deletes bulkStore New Entries (commit 9c9be07)

### Problem
When bulkStore writes new entries (active), then some invalidate updates fail,
rollback only restored old entries' metadata. **New entries from bulkStore
remained active** — both old (restored) and new (committed) existed
simultaneously, breaking isLatest semantics.

### Solution
Rollback now has two phases:
1. **Phase 1 (Delete)**: Delete the new entries that bulkStore wrote
(identified by newEntryId stored on each InvalidateEntry during 2nd pass)
2. **Phase 2 (Restore)**: Restore old entries' metadata from _origMetadata

If either phase fails → ROLLBACK FAILED logged with breakdown of which
operations failed (N deletes + M restores).

### Code Changes
- `src/smart-extractor.ts` InvalidateEntry interface: added newEntryId field
- Second pass: store bulkResults[newEntryIndex].id as inv.newEntryId
- Rollback block: two-phase delete-then-restore with Promise.allSettled
- `test/invalidate-error-regression.test.mjs`: TC-5 enhanced to verify
Phase 1 delete is called with bulkStore-created entry IDs

### Verification
```
node --test test/invalidate-error-regression.test.mjs
# pass 5, fail 0 (all 5 TC cases pass)
```

---

## Previously Addressed in This PR

| Flag | Status |
|-------|--------|
| F1 | Fixed in commit fa86d10 |
| F2 | No regex fallback path used in this PR |
| F3 | Fixed in commit 9c9be07 |
| F4 | N/A (test infrastructure issue) |
| F5 | Fixed in commit fa86d10 |
| F6 | N/A |
| MR1-MR4 | Fixed/regressed in prior commits |

## Remaining Issues

| Issue | Status | Note |
|-------|--------|------|
| EF1 (smart-extractor-branches.mjs) | **Pre-existing** | Regex fallback fails due to unavailable embedding service in test environment — unrelated to this PR |
8 changes: 8 additions & 0 deletions scripts/ci-test-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export const CI_TEST_MANIFEST = [
// Issue #492 agentId validation tests
{ group: "core-regression", runner: "node", file: "test/agentid-validation.test.mjs", args: ["--test"] },
{ group: "core-regression", runner: "node", file: "test/command-reflection-guard.test.mjs", args: ["--test"] },
// Issue #676: handleSupersede batch mode invalidation fix
{ group: "core-regression", runner: "node", file: "test/supersede-existing-found-bulk.test.mjs", args: ["--test"] },
// Issue #675: regex fallback bulkStore fix
{ group: "core-regression", runner: "node", file: "test/regex-fallback-bulk-store.test.mjs", args: ["--test"] },
// Issue #670/#675: lock stale threshold regression
{ group: "core-regression", runner: "node", file: "test/lock-stale-threshold.test.mjs", args: ["--test"] },
// Issue #676: handleSupersede invalidation error handler regression (RF-1)
{ group: "core-regression", runner: "node", file: "test/invalidate-error-regression.test.mjs", args: ["--test"] },
];

export function getEntriesForGroup(group) {
Expand Down
10 changes: 10 additions & 0 deletions scripts/verify-ci-test-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,21 @@ const EXPECTED_BASELINE = [
{ group: "storage-and-schema", runner: "node", file: "test/smart-extractor-bulk-store-edge-cases.test.mjs", args: ["--test"] },
// Issue #680 regression tests
{ group: "core-regression", runner: "node", file: "test/memory-reflection-issue680-tdd.test.mjs", args: ["--test"] },
// Issue #606 SDK migration Bug 2 regression tests
{ group: "core-regression", runner: "node", file: "test/issue606_sdk-migration.test.mjs" },
// Issue #736 recall governance - isRecallUsed() unit tests
{ group: "core-regression", runner: "node", file: "test/is-recall-used.test.mjs", args: ["--test"] },
// Issue #492 agentId validation tests
{ group: "core-regression", runner: "node", file: "test/agentid-validation.test.mjs", args: ["--test"] },
{ group: "core-regression", runner: "node", file: "test/command-reflection-guard.test.mjs", args: ["--test"] },
// Issue #676: handleSupersede batch mode invalidation fix
{ group: "core-regression", runner: "node", file: "test/supersede-existing-found-bulk.test.mjs", args: ["--test"] },
// Issue #675: regex fallback bulkStore fix
{ group: "core-regression", runner: "node", file: "test/regex-fallback-bulk-store.test.mjs", args: ["--test"] },
// Issue #670/#675: lock stale threshold regression
{ group: "core-regression", runner: "node", file: "test/lock-stale-threshold.test.mjs", args: ["--test"] },
// Issue #676: handleSupersede invalidation error handler regression (RF-1)
{ group: "core-regression", runner: "node", file: "test/invalidate-error-regression.test.mjs", args: ["--test"] },
];

function fail(message) {
Expand Down
Loading
Loading