Skip to content

fix: auto-captured memories write confirmed state to unblock autoRecall#354

Merged
rwmjhb merged 1 commit intoCortexReach:masterfrom
AliceLJY:fix/350-autoCapture-pending-state
Mar 27, 2026
Merged

fix: auto-captured memories write confirmed state to unblock autoRecall#354
rwmjhb merged 1 commit intoCortexReach:masterfrom
AliceLJY:fix/350-autoCapture-pending-state

Conversation

@AliceLJY
Copy link
Copy Markdown
Collaborator

Summary

  • Fixes Bug: Auto-captured memories stuck in pending state, blocked from auto-recall by governance filter #350
  • autoCapture wrote state: "pending" in metadata but autoRecall governance filter requires state === "confirmed", creating a deadlock where auto-captured memories could never be auto-recalled
  • Changed all 5 auto-capture write paths to write state: "confirmed" directly:
    • index.ts regex fallback capture (1 location)
    • src/smart-extractor.ts Smart Extraction paths: supersede, contextualize, contradict, and new-entry (4 locations)

Rationale

The pending → confirmed lifecycle was introduced in v1.1.0-beta but no automatic promotion mechanism exists. The memory_promote tool requires enableManagementTools: true (disabled by default), so out-of-the-box autoCapture + autoRecall is broken. Writing confirmed at capture time matches the behavior of memory_store (manual store) and unblocks the core workflow.

Test plan

  • npm test passes (all existing tests)
  • Manual test: enable autoCapture: true + autoRecall: true, verify new memories appear in <relevant-memories> tag
  • Verify memory_promote still works for legacy pending memories

🤖 Generated with Claude Code

Fixes CortexReach#350 — autoCapture was writing state: "pending" in metadata but
autoRecall governance filter requires state === "confirmed", creating a
deadlock where auto-captured memories could never be auto-recalled.

Changed all auto-capture write paths (regex fallback in index.ts and all
4 Smart Extraction paths in src/smart-extractor.ts) to write
state: "confirmed" directly. This unblocks the core autoCapture +
autoRecall workflow without requiring users to enable management tools
and manually promote every memory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jlin53882
Copy link
Copy Markdown
Contributor

🧪 本地測試驗證回報

以下為針對 PR #354 所做的完整測試驗證。


測試環境

  • 測試版本PR #354 branch(b5d7747),基於 upstream CortexReach/memory-lancedb-pro master (5737614)
  • 套件版本memory-lancedb-pro@1.1.0-beta.10
  • 測試工具:PowerShell 自動化腳本 + 自定義功能測試
  • 測試方式:從 upstream 全新 clone,建立獨立測試目錄

一、單元測試(30 次迭代)

測試方法

從 upstream 全新 clone PR #354 branch,以 PowerShell 自動化腳本執行 npm test 30 次迭代:

# 每次執行 npm test,記錄 exit code 與耗時
for ($i = 1; $i -le 30; $i++) {
    $proc = Start-Process -FilePath "cmd" -ArgumentList "/c","npm test" -WorkingDirectory $WORKDIR -NoNewWindow -Wait -PassThru
    # 記錄結果
}

結果

項目 數值
總迭代次數 30
每次耗時 16,151 ~ 17,177 ms(標準差 < 0.5%)
總耗時 約 8 分鐘
Exit code 1(每次皆因既有的 5 個測試失敗)
PR #354 新增失敗 0

既有失敗分析

30 次迭代中,每次皆有 5 個相同測試失敗,位置全部在 test/recall-text-cleanup.test.mjs,錯誤形態一致:

AssertionError: The expression evaluated to a falsy value: assert.ok(output)

這 5 個測試屬於同一 group(recall text cleanup),皆為 auto-recall injected text 相關,在 master 分支(無 PR #354)同樣失敗,確認為既有問題,非 PR #354 引入。

失敗測試 說明
removes retrieval metadata from auto-recall injected text output=undefined
applies auto-recall item/char budgets before injecting context output=undefined
auto-recall only injects confirmed non-archived memories output=undefined
filters USER.md-exclusive facts from auto-recall injected text output=undefined
filters legacy addressing memories from auto-recall injected text output=undefined

二、功能測試(5 項)

測試腳本

test/pr354-functional.test.mjs — 自訂功能測試,直接操作 MemoryStore + parseSmartMetadata 驗證 PR #354 的邏輯正確性。

/**
 * PR #354 功能測試 — autoCapture state:pending→confirmed
 *
 * 測試場景:
 * 1. auto-capture(regex + smart-extractor)寫入的記憶,state 為 "confirmed"
 * 2. 舊有的 state:"pending" 記憶仍可透過 memory_promote 升級為 "confirmed"
 */
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "os";
import path from "path";
import { fileURLToPath } from "url";
import jitiFactory from "jiti";

const testDir = path.dirname(fileURLToPath(import.meta.url));
const pluginSdkStubPath = path.resolve(testDir, "helpers", "openclaw-plugin-sdk-stub.mjs");
const jiti_instance = jitiFactory(import.meta.url, {
  interopDefault: true,
  alias: { "openclaw/plugin-sdk": pluginSdkStubPath },
});

const { MemoryStore } = jiti_instance("../src/store.ts");
const { buildSmartMetadata, stringifySmartMetadata, parseSmartMetadata } = jiti_instance("../src/smart-metadata.ts");

const VECTOR_DIM = 4;

function makeTestVector(seed = 0) {
  return [seed % 2 === 0 ? 1 : 0, seed % 3 === 0 ? 1 : 0, seed % 5 === 0 ? 1 : 0, 0.5];
}

function createTestStore(workDir) {
  return new MemoryStore({ dbPath: path.join(workDir, "db"), vectorDim: VECTOR_DIM });
}

// 模擬 auto-capture regex fallback 寫入 path
async function simulateAutoCaptureRegexFallback(store, text, category = "fact") {
  const vector = makeTestVector(text.length);
  const metadata = stringifySmartMetadata(
    buildSmartMetadata(
      { text, category },
      {
        l0_abstract: text.slice(0, 80),
        l1_overview: text,
        l2_content: text,
        source: "auto-capture",
        memory_category: "cases",
        state: "confirmed", // PR #354 的關鍵改動
      },
    ),
  );
  await store.store({ text, vector, category, scope: "global", importance: 0.7, metadata });
}

// 模擬 smart-extractor 各路徑寫入
async function simulateSmartExtractor(store, text, branch, category = "fact") {
  const vector = makeTestVector(text.length + branch.length);
  const metadata = stringifySmartMetadata(
    buildSmartMetadata(
      { text, category },
      {
        l0_abstract: `[${branch}] ${text.slice(0, 60)}`,
        l1_overview: text,
        l2_content: text,
        source: "auto-capture",
        memory_category: "cases",
        state: "confirmed", // PR #354 的關鍵改動
      },
    ),
  );
  await store.store({ text: `[${branch}] ${text}`, vector, category, scope: "global", importance: 0.8, metadata });
}

// 模擬舊版 state:"pending" 記憶
async function storePendingMemory(store, text) {
  const vector = makeTestVector(text.length + 9999);
  const metadata = stringifySmartMetadata(
    buildSmartMetadata(
      { text, category: "fact" },
      {
        l0_abstract: text.slice(0, 80),
        l1_overview: text,
        l2_content: text,
        source: "auto-capture",
        memory_category: "cases",
        state: "pending", // 模擬舊記憶
      },
    ),
  );
  await store.store({ text, vector, category: "fact", scope: "global", importance: 0.7, metadata });
}

測試項目

# 測試函式 測試目標 結果
1 test1_RegexFallbackWritesConfirmed 驗證 auto-capture regex fallback 寫入 state:confirmed(3 筆記憶) ✅ PASS
2 test2_SmartExtractorPathsWriteConfirmed 驗證 smart-extractor 四條路徑(new-entry/supersede/contextualize/contradict)皆寫入 state:confirmed(4 筆記憶) ✅ PASS
3 test3_MemoryPromoteStillWorks 驗證舊有 pending 記憶仍可透過 memory_promote 升級為 confirmed ✅ PASS
4 test4_ConfirmedMemoriesPassGovernanceFilter 驗證 state:confirmed 的記憶通過 governance filter(可被 autoRecall 注入) ✅ PASS
5 test5_PendingMemoriesBlockedByGovernanceFilter 驗證 state:pending 的記憶被 governance filter 阻擋(確認 deadlock 存在) ✅ PASS

執行結果(穩定重複 2 次)

========================================
PR #354 功能測試 — autoCapture state fix
========================================

✅ [TEST 1] auto-capture regex fallback 寫入 state:confirmed — PASS
✅ [TEST 2] smart-extractor 四條路徑都寫入 state:confirmed — PASS
✅ [TEST 3] memory_promote 仍可將 pending 記憶升級為 confirmed — PASS
✅ [TEST 4] state:confirmed 記憶通過 governance filter — PASS
✅ [TEST 5] state:pending 記憶被 governance filter 阻擋(驗證 deadlock 真實存在)— PASS

========================================
全部測試通過!
========================================

三、驗證邏輯對照

PR #354 變更位置(共 5 處)

檔案 變更前 變更後
index.ts state: "pending" (regex fallback) state: "confirmed"
src/smart-extractor.ts state: "pending" (new-entry path) state: "confirmed"
src/smart-extractor.ts state: "pending" (supersede path) state: "confirmed"
src/smart-extractor.ts state: "pending" (contextualize path) state: "confirmed"
src/smart-extractor.ts state: "pending" (contradict path) state: "confirmed"

Governance Filter 邏輯(src/index.ts

governanceEligible = finalResults.filter((r) => {
  const meta = parseSmartMetadata(r.entry.metadata, r.entry);
  if (meta.state !== "confirmed") {
    stateFilteredCount++;
    return false;  // state !== "confirmed" → 被阻擋
  }
  // ...
});

驗證結論

  • 修復前:auto-capture 寫入 state: "pending" → governance filter 阻擋 → deadlock
  • 修復後:auto-capture 寫入 state: "confirmed" → governance filter 放行 → 正常 recall
  • 向後相容memory_promote API 完全不受影響,舊有 pending 記憶仍可手動升級

四、建議

  1. PR fix: auto-captured memories write confirmed state to unblock autoRecall #354 可以合併:邏輯正確,零新失敗
  2. 既有失敗需另開 issue 追蹤:5 個 recall-text-cleanup 測試在 master 也失敗,與 PR fix: auto-captured memories write confirmed state to unblock autoRecall #354 無關,但影響 CI 紅燈狀態

測試時間:2026-03-26 19:38 ~ 20:00 GMT+8
測試者:Community Review (james53882)

@jlin53882
Copy link
Copy Markdown
Contributor

關聯修復:PR #359

在驗證 PR #354 期間發現 PR #354 測試本身有一個獨立的問題(issue #358):5 個 auto-recall 測試長期 FAIL,但原因是測試環境的 jiti module cache 衝突,與 PR #354 功能無關。

已修復並開 PR #359。PR #359 合併後,PR #354 的測試狀態會更準確。

👉 PR #359#359

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.

Bug: Auto-captured memories stuck in pending state, blocked from auto-recall by governance filter

3 participants