記憶智能
會彼此取代的事實、會變成規則的錯誤、可成批取回的記憶:三項升級疊加在現役記憶引擎之上,無須重寫 schema。
比喻:醫師的病歷
Section titled “比喻:醫師的病歷”優秀的醫師不會把病歷當成一疊扁平的筆記,而是當成三個彼此相連的習慣來經營:
- 事實有時間軸。「病患服用 10mg 該藥物」這件事為真,直到劑量被調整。當記下新劑量時,舊的那一行不會被抹去,而是被標註「有效至 3 月 3 日」,由新的一行接手。詢問「去年冬天劑量是多少?」病歷就能從歷史中正確的那一刻給出答案。
- **錯誤會變成準則。**當某種藥物交互作用第三次被漏掉之後,診所不會只修正那一個案例,而是寫下一條常設規則:「永遠檢查交互作用 X。」下一位醫師讀的是這條規則,無需再翻那三份事故報告。
- **取回是成批進行的。**回顧病例時,醫師會用編號精準調出需要的那幾頁,不必把整本病歷一張一張重看一遍。
DuDuClaw 的記憶智能(v1.19.0)賦予 Agent 同樣的三個習慣。它以非侵入式方式建構在既有的 SqliteMemoryEngine 之上(不重寫 schema,MemoryEntry 維持不變)。
| 功能 | 作用 | 所在位置 | |
|---|---|---|---|
| F1 | Temporal Memory | 事實獲得有效期區間 + 知識圖譜三元組;新事實取代舊事實並串成鏈 | engine.rs — store_temporal、get_history、get_at |
| F2 | Reflexion Loop | 將近期未解決的錯誤注入 prompt(F2a);將同類別 ≥3 筆錯誤整併為一條 semantic 規則(F2b) | channel_reply.rs、reflexion.rs、MistakeNotebook |
| F3 | Batch Fetch | 單次呼叫以 ID 取回至多 100 筆記憶,部分命中時回傳 missing_ids |
engine.rs — get_by_ids;MCP memory_fetch_batch |
三者皆建構於現役引擎:遷移是一個冪等的 ALTER 迴圈,而非重建。
F1:Temporal Memory
Section titled “F1:Temporal Memory”新欄位(冪等遷移)
Section titled “新欄位(冪等遷移)”遷移迴圈新增九個可為 NULL/具常數預設值的欄位,使 ALTER TABLE ... ADD COLUMN 在既有資料列上合法,並另建兩個索引:
| 欄位 | 意義 |
|---|---|
valid_from |
事實開始為真的時間(NULL ⇒ 回退到 timestamp) |
valid_until |
事實不再為真的時間(NULL ⇒ 仍然有效) |
superseded_by |
取代本列的那一列的 id |
supersedes |
本列所取代的那一列的 id |
subject / predicate / object |
知識圖譜三元組 |
confidence |
0.0–1.0,預設為 1.0 |
metadata |
JSON 區塊,預設為 {} |
-- F1 Temporal Memory columns (v1.19.0) — all nullable / constant-defaultALTER TABLE memories ADD COLUMN valid_from TEXT;ALTER TABLE memories ADD COLUMN valid_until TEXT;ALTER TABLE memories ADD COLUMN superseded_by TEXT;ALTER TABLE memories ADD COLUMN supersedes TEXT;ALTER TABLE memories ADD COLUMN subject TEXT;ALTER TABLE memories ADD COLUMN predicate TEXT;ALTER TABLE memories ADD COLUMN object TEXT;ALTER TABLE memories ADD COLUMN confidence REAL NOT NULL DEFAULT 1.0;ALTER TABLE memories ADD COLUMN metadata TEXT NOT NULL DEFAULT '{}';
-- Triple index only covers currently-valid rows (cheap conflict lookup)CREATE INDEX IF NOT EXISTS idx_memories_triple ON memories(agent_id, subject, predicate) WHERE valid_until IS NULL;CREATE INDEX IF NOT EXISTS idx_memories_valid ON memories(agent_id, valid_until);迴圈會吞掉 duplicate column name 錯誤,因此在已升級的資料庫上重跑屬於 no-op。
自動衝突解決
Section titled “自動衝突解決”當 store_temporal(entry, TemporalMeta) 同時帶有 subject 與 predicate 時,引擎會把 (agent_id, subject, predicate) 視為一個事實識別。任何具有相同三元組、目前仍有效的資料列,會在插入新列之前被關閉;目前事實的可信度較高時例外,這時寫入會被拒絕(見下文):
store_temporal(agent="dudu", subject="user", predicate="deploy_target", object="Cloudflare Workers") | v查詢 (dudu, user, deploy_target) 目前仍有效的資料列 | 找到? ──否──> 直接 INSERT 新列(valid_until = NULL) | 是 | vUPDATE 舊列: valid_until = now superseded_by = <新 id> | vINSERT 新列: supersedes = <舊 id> valid_until = NULL (目前有效)兩列現在被串接成一條取代鏈(supersession chain):
[ deploy_target = Vercel ] [ deploy_target = Cloudflare Workers ] valid_from : Jan 1 valid_from : Mar 3 valid_until : Mar 3 ───────► valid_until : NULL (目前) superseded_by ──────────┘ supersedes ─────────┘若沒有完整三元組,store_temporal 僅單純記錄一筆帶時間戳的事實;不發生取代。
預設過濾為「目前有效」
Section titled “預設過濾為「目前有效」”search() / search_layer() 會在每個查詢加上 AND (m.valid_until IS NULL OR m.valid_until > now),因此一般取回只會回傳當下為真的事實。過期事實仍留在資料庫供查閱歷史,但絕不會洩漏進 prompt。
兩個讀取 API 揭露這條鏈,也都以 MCP 形式提供 memory_get_history / memory_get_at(scope memory:read):
| API / MCP 工具 | 回傳 |
|---|---|
get_history(agent, subject, predicate) — memory_get_history { subject, predicate } |
完整取代鏈,由舊到新,含每筆的 ingested_at、invalidated_by_event/invalidated_at 與 reaffirmed_by |
get_at(agent, subject, predicate, at) — memory_get_at { subject, predicate, at } |
某時間點仍有效的單一事實(valid_from <= at AND (valid_until IS NULL OR valid_until > at)) |
Bi-temporal + build-time provenance(D1)
Section titled “Bi-temporal + build-time provenance(D1)”時序儲存追蹤兩條時間軸:valid_from/valid_until(world-time,事實為真的時段)與 ingested_at(transaction-time,系統得知該事實的時間)。取代由 world-time 的 valid_from 決定(非攝入順序),因此就算攝入次序被打亂(先得知離婚、後得知更早的結婚),任一時間點仍能解析出正確的事實:valid_from 早於現行事實的一筆,會以有界的歷史區段插入,不擾動現行事實。再次觀察到完全相同的事實(相同 subject/predicate/object +內容)會**再確認(reaffirm)**它:把新的 source_event 附加到 reaffirmed_by(上限 20)並累加 access_count,而不另開新列。當一筆事實被關閉時,關閉用的 source_event 與時間會蓋印到被取代的那一列(invalidated_by_event/invalidated_at)。
來源回溯(memory_invalidate_by_origin)
Section titled “來源回溯(memory_invalidate_by_origin)”invalidate_by_origin(agent, origin, since)(MCP memory_invalidate_by_origin,scope admin)是投毒來源的補救閥門:它會讓某個精確 origin 的所有現行有效事實過期(只過期、不刪除;比對採等值,絕不用子字串),並可選擇只限於 since 之後(含)得知的事實。凡 derived_from 引用到被清除 id 的事實,其 origin_trust 會被壓到 ≤ 0.1(源自投毒輸入的衍生物不能繼續被信任)。search() 會立即停止回傳這些被清除的事實,而 get_history() 仍保留完整的鏈,並標記 invalidated_by_event = "origin_purge"。
v1.67.1 起,被視為 AI 員工的呼叫者只能讓 channel、mcp_external、tool_echo 這三個低於 agent 衍生上限的類別過期。其他來源會被拒絕並記入稽核 memory_invalidate_refused。這項判定採 fail-closed:凡是使用 gateway 共用內部金鑰的呼叫者,不論行程裡有沒有員工身分,一律視為 AI 員工;屬於某位員工或臨時員工(eph- 開頭的 id)的金鑰也一樣。只有對應不到任何員工的 admin 金鑰不受限制。之前,被操縱的 AI 員工一次呼叫就能讓它命名空間裡所有操作者等級的事實過期(v1.68.0 起是該員工自己的命名空間,見下方記憶命名空間)。
記憶來源與依來源忘記
Section titled “記憶來源與依來源忘記”每一筆寫進 memories 或 key_facts 的記憶,都會在同一個 SQLite 交易裡多寫一列 memory_origins,記下它的來源。來源由 gateway 建立,不由模型填寫:一則通道訊息(對話 id 加訊息序號)、一次排程或派工執行、員工的一回合 MCP 呼叫、外部 MCP 用戶端的一次呼叫、匯入檔案的一筆紀錄、一天的使用足跡,或系統本身。匯入的檔案以解析後的路徑識別,每筆紀錄以內容雜湊識別,所以同一個檔案換順序再匯入,仍是同一個來源;放在另一個路徑的副本則是另一個來源。
由其他記憶衍生出來的記憶(反思規則、整併後的事實、取代舊值的新敘述)寫入時,會把所有父記憶的來源一併複製過來,每筆最多 512 個,所以「這個來源產生的所有東西」只要一次索引查詢。衍生寫入若指名的父記憶不存在,這次寫入會被拒絕;以前只是降低這筆寫入的信任度。
duduclaw memory forget-source(只能在操作者的終端機執行)用這份紀錄運作:
plan是預演。算出由該來源直接或間接產生的記憶、關鍵事實與封存副本,把計畫連同到期時間記下來,並送出一筆核准請求。- 管理員在儀表板核准這份計畫。
apply --confirm在memory.db的一個交易內刪除這些列,並寫下封鎖紀錄(forgotten_sources、forgotten_memories)。每一條寫入路徑都在自己的交易內檢查封鎖紀錄,資料庫觸發器會擋掉檢查漏掉的寫入,所以同一個來源不會再被學進去。被擋下的寫入會記成稽核事件memory_write_fenced(只含雜湊,每位員工每個 UTC 日最多 50 筆,超過只計數)。- 接著做
memory.db以外的步驟,每一步都有紀錄、可重複執行:刪除仍列著該來源的自動建檔 wiki 頁、撤回審核卡、對員工隱藏被忘記的訊息(原文仍留在sessions.db)、清除該對話的壓縮摘要。失敗的步驟用resume重跑,gateway 開機時與之後每 10 分鐘也會自動重試。
一筆記憶若同時由被忘的來源與其他來源支撐,會整筆刪除,因為它的內容可能包含被忘來源說過的話。只是被該來源再次提到(佐證)的記憶會保留,信心值也不會回退。取代鏈上經過被刪記憶的連結會被切斷;它先前取代掉的舊版本維持失效,不會恢復成目前值。
員工在回合或執行中用 wiki_write 自己寫的頁面,現在會帶一個 host_sources frontmatter 欄位,記下這次寫入的來源(最近 20 筆)。這個欄位歸主機所有,員工自己寫的值會被丟掉。忘記某個來源不會刪這類頁面,因為員工可能寫了不是來自該來源的內容;計畫只會列出記錄的來源命中的頁面,請你自己檢視。
要留意的限制:這個指令不會刪除對話原文、已送出的訊息、備份、其他員工收到的內容,也碰不到工具呼叫紀錄、錯誤筆記、任務看板、工作狀態與 Agent Mail 裡的內容。在這個功能之前寫入的記憶沒有來源紀錄,計畫會顯示數量但不會刪。用忘記之前的備份還原,被忘的記憶會回來、封鎖紀錄也一起消失;不支援降回沒有這個功能的版本。逐步操作見 忘記對話、排程執行或匯入檔案,核准與守門行為見安全防禦。
在 config.toml 設 [memory] forget_source = false 可停止建立新計畫與套用;apply 前的儀表板核准沒有開關。這個功能尚未在真實的 gateway 與通道上做過活體驗證。
寫入端投毒防護(D2)
Section titled “寫入端投毒防護(D2)”D1 讓你能撤銷一個投毒來源;D2 則在源頭就攔下大部分毒物(PoisonedRAG,arXiv:2402.07867)。自動蒸餾的寫入路徑在兩端設防:
- 寫入端掃描+爆量偵測。一筆蒸餾出的事實在儲存前,其內容與
(subject, predicate, object)會先過共用的 prompt-injection 規則引擎:命中即丟棄該事實(fail-closed,絕不寫入),並記錄一筆prompt_injection安全稽核事件。另外有一個 per-(agent, origin, subject)的滑動窗計數器(knowledge_guard,與 dispatch 斷路器同樣採持久化 + advisory-lock 模式),當單一來源在窗內對同一 subject 寫入>= max_per_subject筆事實時,會把整批隔離(即「One Shot Dominance」/k-doc 模式)。被隔離的事實以quarantined = 1儲存,屬惰性:它們永不取代乾淨事實,並在人工裁決前被排除於所有取回讀取路徑(FTS、graph、vector、list_recent、summarize)之外。 - **處理流程。**一次隔離會發出一筆
ApprovalBroker請求(action_kind = "knowledge_quarantine",期限 24 小時)並送出knowledge.quarantined事件。核准 → 每筆事實依一般時序規則與下方的取代可信度檢查釋放:成為目前事實(或重複確認、或存成歷史);被可信度更高的目前事實擋下的,會轉成有自己審核項目的暫存說法,不直接套用(v1.67.1;之前核准只是清掉旗標)。寫入時就會被檢查擋下的爆量事實,直接進暫存說法,不進爆量批次。轉換時文字超過 600 字的資料列不建立審核項目,只記入稽核memory_supersession_refused(not_held_reason: "too_long")。部分失敗後再次核准同一個爆量項目,會補建還缺的衝突審核項目(判斷是否已存在時只看衝突項目)。拒絕 → 事實過期(invalidated_by_event = "quarantine_reject"),其origin_trust壓到 ≤ 0.1;TTL 逾時視同拒絕(fail-closed)。
排序端信任。origin_trust 現在會參與取回排序(權重 w_trust,預設 0.10):每個候選的分數會乘上 (1 − w_trust) + w_trust · origin_trust,讓未經驗證的頻道蒸餾事實(trust 0.3)無法勝過策展過的事實(trust 1.0)。在 HippoRAG-lite graph 中,一個三元組的邊會依其 origin_trust 加權,縮小低信任事實的 Personalized-PageRank 質量,直接抑制「單一投毒三元組被 PPR 放大兩跳」這條路徑。舊資料列(trust 1.0)的排序與 D2 之前逐位元組相同。
取代時的可信度檢查(v1.67.1)
Section titled “取代時的可信度檢查(v1.67.1)”v1.67.1 之前,同一個 (agent, subject, predicate) 的新事實一定會取代目前的事實,不管來源。從聊天對話萃取的事實(可信度 0.3)會蓋掉同一 subject 與 predicate 的現有事實,不論它的可信度,例如 AI 員工自己推得的事實或沒有記錄來源的舊資料(0.6),鍵值相同時也包括匯入的事實(0.7)。操作者核准過的事實(1.0)從 v1.67.1 起才有,由下方的審核核准寫入,並受這項檢查保護。現在 store_temporal 在取代之前會先比較可信度(duduclaw-memory/src/supersession_guard.rs):
- 寫入的可信度是有效的
origin_trust(套用來源類別上限與derived_from限制之後的值)。目前事實的可信度是它儲存的origin_trust,再以它的來源類別上限封頂;來源綁定之前寫入的資料列視為unattributed(0.6)。多方佐證只提高confidence,不提高可信度,所以不列入比較。 - 只要這個三元組目前有效、未被隔離的資料列中,有任何一列的可信度嚴格高於這次寫入,就什麼都不寫。可信度相同或更高時照舊取代,舊資料列留在歷史鏈裡。
- 重複確認同一個 object、沒有完整三元組的寫入、以及時間較早的歷史片段(
valid_from較舊)不經過這項檢查。 - 檢查比對的是完全相同的
subject與predicate字串。同一件事若用拼法不同的 subject 或 predicate 存下,會被當成另一個三元組,兩者照舊並存。
來源類別上限(origin.rs):user_direct(含舊別名 user)1.0、operator 1.0、import 0.7、agent_derived 0.6、user_profile 0.6、unattributed 0.6、tool_echo 0.5、channel 0.3、mcp_external 0.3。user_profile 在 v1.67.1 成為獨立類別,之前它是 user_direct 的別名,上限 1.0。user_profile_record MCP 工具與「說話者描述自己」的使用者輪廓萃取現在都寫入 user_profile(萃取之前寫的是 channel,0.3)。因此 AI 員工的紀錄與使用者後來的說法可以互相更正、不需審核,但兩者都無法取代操作者核准過的值。
各寫入路徑遇到拒絕時的處理:
| 路徑 | 被拒絕時 |
|---|---|
對話事實萃取(wiki_ingest,來源 channel) |
暫存待審(見下) |
使用者輪廓特徵萃取(profile_distill,來源 user_profile) |
暫存待審(見下) |
user_profile_record MCP 工具 |
回傳錯誤(“a more trusted value already exists for this field, so it was not changed”),不寫入 |
duduclaw migrate from |
該項目回報為略過 |
| 足跡萃取、reflexion 規則整併、夜間引擎的 schema 與整併 | 略過該項目並記錄 log |
其他呼叫 store_temporal 的地方 |
收到寫出兩邊可信度的錯誤 |
暫存待審的說法。 在兩條萃取路徑中,被拒絕的說法會以惰性資料列存下(quarantined = 1,三元組只放在 metadata.held_claim,檢索看不到),並在儀表板「收件匣」建立一筆 knowledge_quarantine 審核項目。項目只依存下來的暫存列產生,列出核准後會寫入的所有東西:完整的新說法與新值,旁邊並排目前內容與目前的值;如果是某個人的使用者輪廓,會寫出是誰的輪廓(使用者 id)。目前內容超過 600 字會截斷,並註明已截斷。新說法超過 600 字時不送審,只寫入稽核(not_held_reason: "too_long")。相同的說法(subject、predicate、object 都一樣)已在等待審核時,不會重複暫存。每位 AI 員工每個 UTC 日最多新增 20 筆。某個 UTC 日第一次超過上限時,動態牆會出現一則事件(knowledge_review_cap_reached);超過上限的拒絕都只寫入稽核紀錄(memory_supersession_refused,review_cap_hit: true,not_held_reason: "daily_cap")。
- 核准:以操作者權限(來源
operator)寫入的,正是建立審核項目時依據的那筆暫存說法,取代目前事實。審核項目帶有這筆說法的摘要值(內容、subject、predicate、object)。若說法本身或受保護的事實在建立項目後改變,就不寫入任何內容,結果訊息會說明情況已改變,暫存列隨之關閉;同一說法日後再出現時會重新送審。 - 系統先套用變更,成功後才記錄決定;同一個項目的核准與拒絕會依序逐一處理。套用失敗時,項目維持待審,儀表板顯示伺服器的錯誤,可以再試一次。變更已套用但決定沒能記錄時(項目剛好在中途逾期,或儲存發生錯誤),儀表板會收到說明已套用了什麼的錯誤,並寫入稽核紀錄
knowledge_review_decision_unrecorded。 - 拒絕:捨棄這則說法。
- 超過 24 小時期限的項目無法核准,逾期視同拒絕。
- 這類項目只能在儀表板決定,需要 manager 或 admin 角色(
approvals.decide)。聊天通道只會收到不含按鈕、也不含說法內容的通知,而且不會送到說法來源的那個對話;按舊按鈕或用文字回覆決定都會被拒絕,並提示到儀表板處理。Telegram Mini App 的詳細頁對這類項目也只顯示同一則通知。v1.67.1 之前,在通道按下知識審核的按鈕只會把審核標成已決定,不會真的釋放任何內容。 - 每天一次的清理(與記憶衰減一起執行)會把審核項目已不在等待狀態、且建立超過一小時的隔離資料列(暫存說法與爆量批次都算)當成拒絕關閉。
- 資料主體的匯出與刪除(
gdpr.rs)也會比對暫存說法的 subject 與 object;被刪除的暫存說法無法再被核准。duduclaw gdpr erase <contact> --confirm另外會撤回涵蓋被刪資料列的待審項目、把審核紀錄裡所有這類項目(不論狀態)的文字換掉,並刪除對應的knowledge.quarantined事件。沒有帶資料列 id 的事件(例如注入掃描丟棄的紀錄)無法比對,會留到事件保存期限 7 天後清除。前一步失敗時後面的步驟照樣執行;失敗的步驟會列出來,指令以非零結束,並提示重新執行同一個指令即可完成,重複執行是安全的。重新執行時即使已找不到記憶資料列,仍會用完全相同的 subject 從審核項目與事件中移除這個人的文字。 - 排程與系統提示(虛擬使用者
system)不再寫入使用者輪廓。user_profile_record會拒絕虛擬使用者(system、anonymous、unknown),並對 predicate 與值都做提示注入掃描,達到封鎖等級就拒絕。
config.toml [memory] supersession_trust_guard(預設 true)設成 false 會關閉這項檢查。讀取這個設定的是 gateway 經 memory_factory::build_memory_engine 建立的引擎,以及 duduclaw mcp-server 的記憶引擎。直接建立的引擎(例如儀表板的記憶 RPC 與 duduclaw migrate from)不看這個設定,檢查一律開啟。尚未在真實聊天通道上驗證。
記憶命名空間(v1.68.0)
Section titled “記憶命名空間(v1.68.0)”MCP 記憶工具(memory_store、memory_search、memory_read、memory_fetch_batch、memory_alias_add / memory_alias_list、memory_get_history、memory_get_at、memory_invalidate_by_origin、user_profile_record、user_profile_get、user_code_profile)依呼叫者決定命名空間(crates/duduclaw-cli/src/mcp_namespace.rs 的 resolve_for_caller):
| 呼叫者 | 讀寫的命名空間(讀取時另加 shared/public) |
|---|---|
| gateway 內部金鑰,且員工身分驗證通過 | 該員工自己的 id,例如 agnes |
| gateway 內部金鑰,身分未驗證 | internal/gateway-internal(舊的共用池) |
個別員工的 MCP 金鑰,client id 是現有員工(有 agents/<id>/agent.toml) |
該員工的 id |
| 其他內部金鑰 | internal/<client_id> |
| 外部金鑰 | external/<client_id>(不變) |
「驗證通過」指 DUDUCLAW_AGENT_ID 由 DUDUCLAW_AGENT_TOKEN 證明(以 ~/.duduclaw/identity.key 對 id 做的 HMAC)。identity.key 存在時,gateway 會把這組值寫進每位員工的 .mcp.json。權杖缺少、錯誤或無法驗證時,呼叫者留在舊的共用池,無法證明的 id 碰不到任何員工的記憶。HTTP 傳輸(duduclaw http-server)一律不從自己的行程環境取身分:經 HTTP 的內部金鑰留在共用池,個別員工金鑰仍對應到該員工。
員工自己的 id 就是 gateway 原本萃取、核准審核、注入提示(重點事實、輪廓區塊)所用的命名空間。升級後,員工的 memory_search 看得到 gateway 萃取的內容,透過工具存的就是 gateway 注入的,可信度檢查在同一處仲裁兩者,員工之間也不再讀得到彼此透過工具存的記憶。
行為變更。 v1.68.0 以前寫入的資料留在 internal/gateway-internal,不會自動搬移;身分驗證通過的員工透過工具也看不到它們。操作者用一個隱藏指令搬移,這個指令在 AI 員工的工作階段中(設有 DUDUCLAW_AGENT_ID 或 DUDUCLAW_AGENT_TOKEN)會拒絕執行:
duduclaw memory migrate-namespace list # 筆數,以及每筆是誰寫的duduclaw memory migrate-namespace export --out pool.json # 全部欄位,檔案權限 0600,不覆寫既有檔duduclaw memory migrate-namespace assign --to agnes --attributed # 預演(預設)duduclaw memory migrate-namespace assign --to agnes --attributed --confirm # 實際執行duduclaw memory migrate-namespace archive --confirm # 剩下的設為失效並加標籤 namespace-archivedlist依tool_calls.jsonl(含tool_calls.jsonl.old)判斷每筆資料的作者:記憶工具結果裡的記憶 id、memory_store輸入內容完全相同,或者(較弱的線索)前後十分鐘內只有一位員工有稽核紀錄。user_profile_record不寫入tool_calls.jsonl,這類資料只能依時間推定。assign --to <員工>必須指定--all(連同共用池的實體別名一起搬)、--ids <id,…>、--attributed(依 id 或內容歸屬的資料;加--include-inferred連時間推定的也搬)三者之一。沒有--confirm只印出計畫;--dry-run一律只印計畫。- 搬移的資料保留原 id。在目標命名空間裡比照新寫入判斷:目標已有相同的目前事實,就成為指向該事實的歷史;比目標目前事實舊的成為歷史片段;被目標中可信度較高的目前事實擋下的,
--refused hold(預設)會轉成待審並在儀表板收件匣建立審核項目,--refused skip則留在共用池。隔離中的資料留在共用池;看起來像提示注入的資料也留在共用池,除非加上--include-flagged。 archive把共用池裡仍有效的資料設為失效並加上namespace-archived標籤,保留供查閱。- 實際執行的
assign與archive會寫稽核事件memory_namespace_migrated。
單元與整合測試在同一個 memory.db 上驅動真實的 MCP 處理函式與 gateway 注入路徑。
自動建檔的知識庫頁面(WP5c)
Section titled “自動建檔的知識庫頁面(WP5c)”對話蒸餾現在有第二個 sink:長效參考文件(章程/SOP/規格/政策)會落成該 AI 員工 auto/ 命名空間下的一頁 wiki,不再是一堆記憶列。信任模型刻意共用而非另立一套:頁面 frontmatter 的 trust 是 0.300,與 origin.rs 給 channel 類別的上限同值,source_type 為 raw_dialogue(排序係數 0.6)。呼叫端無法把任一項調高;升級為策展信任是人工動作,在策展台執行。
記憶端每頁只留一列指標:subject = wiki:auto/<doc_type>/<slug>、predicate = documented_in、origin channel、trust 0.3。文件全文只存在一處,而接替、回溯與檢索在記憶端仍然照常運作。移除一頁時以精確 subject(expire_by_subject)讓該指標失效,絕不走 invalidate_by_origin(那會把所有從對話學到的記憶一併帶走)。詳見 17 — Wiki 知識層。
圖檢索演進(D3)
Section titled “圖檢索演進(D3)”HippoRAG-lite graph 獲得四項各自獨立的改良(對齊 HippoRAG 2 + LightRAG)。每一項都是 fail-safe:在沒有 alias、圖很小、且 embedding seeding 關閉時,排序與先前的 per-query 建圖逐位元組相同。
- 持久化增量圖快取。一旦某 Agent 累積大量事實,每次查詢都重建 Personalized-PageRank 圖就很浪費。圖現在會 per agent 快取(
RwLock)並跨查詢重用;一個 per-agent 的世代計數器(generation counter)會被每一筆更動三元組的寫入(store_temporal/supersession、隔離釋放/拒絕、origin purge、decision 逾期、decay 歸檔、GDPR 抹除、Agent 重新指派)遞增,藉此讓過時的快取失效,使查詢永遠看到現行事實。快取只在超過GRAPH_CACHE_MIN_TRIPLES(500)時啟用;低於此值時 per-query 建圖較便宜,故保留。 - **實體別名合併。**一張
entity_alias(agent_id, canonical, alias)表會在建圖與 seeding 之前,把各種表面形式收斂到同一個節點,讓「老闆/李老闆/zhixu」不再是三座孤島。兩側都會正規化(trim + 轉小寫),別名鏈在儲存時攤平。透過memory_alias_add/memory_alias_listMCP 工具管理(write/read scope)。沒有任何 alias 時,圖逐位元組相同。 - **述詞邊標籤。**每條 SPO 邊現在會附帶其 predicate 作為標籤(PPR 運算從不讀取它,故排序不變)。
engine.export_graph(agent, limit)API 回傳一份可序列化的{ nodes, edges }快照(含仍待裁決的隔離事實,並加註旗標),供 D6 知識圖譜策展 UI 使用。 - Embedding seeding(可選)。當
graph_embed_seed開啟且掛上 embedder 時,PPR 的 seed 會變成「whole-word FTS 實體命中」與「query embedding 最鄰近實體向量」(同模型 cosine,top-k)兩者的聯集。實體向量會惰性快取於entity_embedding,embedding 失敗則回退到 FTS seeding。預設關閉(沒有 embedder 時為 no-op),呼應 HippoRAG 2 對「弱 embedder 會損失 recall」的提醒。
F2:Reflexion Loop
Section titled “F2:Reflexion Loop”F2 把既有的 MistakeNotebook 橋接進回答路徑,它不是一個新的儲存。觸發訊號是既有的 ErrorCategory(Significant/Critical,由 MetaCognition 自我調適),而非進化引擎的 Gate/Measure 驗證器(後者評的是候選 playbook 條目)。
F2a — 將過去的錯誤注入 prompt
Section titled “F2a — 將過去的錯誤注入 prompt”在 Agent 回答頻道訊息之前,會把它近期未解決的錯誤,以 ## Past Mistakes to Avoid 標題浮現到 prompt 中:
頻道訊息抵達 | v擷取以空白分隔的關鍵字(≥3 字元,至多 12 個) | 有關鍵字? ──否──> query_by_agent(agent, 3) ← CJK 近期回退 | (CJK 無空白 token) 是 | vquery_by_topic(keywords, agent, 3) ← 主題範疇的回想 | 為空? ──是──> query_by_agent(agent, 3) ← 近期回退 | v附加到 prompt: ## Past Mistakes to Avoid - <錯誤 1 的 prompt 區塊> - <錯誤 2 的 prompt 區塊>這把 MistakeNotebook 橋接到跨任務學習,讓 Agent 在相似主題上停止重蹈覆轍,不只侷限在進化引擎內。
F2b — 將同類別 ≥3 筆錯誤整併成一條規則
Section titled “F2b — 將同類別 ≥3 筆錯誤整併成一條規則”當同一個 MistakeCategory 累積 >= DEFAULT_CONSOLIDATE_THRESHOLD(= 3)筆未解決項目時,reflexion::maybe_consolidate 會把它們合成為單一條 semantic 記憶規則,然後將來源標記為已解決:
Agent 的未解決錯誤,依 MistakeCategory 分組 | vcount_unresolved_by_category(agent, Capability) = 3 | < 3? ──是──> 不動作 | >= 3 | vquery_unresolved_by_category(...) → MistakeEntry[] | vsynthesize_rule(category, mistakes) ← 確定性,不呼叫 LLM "Recurring capability issues consolidated from 3 past mistakes. Apply extra care: ..." | v存為「一條」semantic 記憶 (source_event = "reflexion_consolidation") | vmark_resolved(source ids) ← 那三筆原始錯誤現在已解決合成是抽離且確定性的(沒有 LLM 往返)。三件散落的事故收斂成一條 Agent 日後會讀到的常設規則。
之前: 之後: ☒ 錯誤 A (capability) ✓ A 已解決 ─┐ ☒ 錯誤 B (capability) ───► ✓ B 已解決 ─┼─► 1 條 semantic 規則 ☒ 錯誤 C (capability) ✓ C 已解決 ─┘ "Apply extra care: ..."F3:Batch Fetch(memory_fetch_batch)
Section titled “F3:Batch Fetch(memory_fetch_batch)”重建上下文往往意味著要以 id 取回許多特定項目。一次一個 MCP 呼叫去做既慢又囉嗦。get_by_ids(引擎)與 memory_fetch_batch MCP 工具可在單次呼叫取回至多 100 筆:
memory_fetch_batch { "ids": ["m_1", "m_2", "m_404", ...] } (上限 100) | vget_by_ids(namespace, ids) SELECT ... FROM memories WHERE agent_id = ? AND id IN (?,?,?...) | (強制 namespace/所有權檢查——屬於其他 | namespace 的項目與不存在無法區分) v切分請求的 id: 命中 → memories[] 缺失 → missing_ids[] ← 不是錯誤 | v{ "memories": [...], "missing_ids": ["m_404"], "total_found": N, "total_missing": M }關鍵性質:
- 硬上限 100:
ids超過 100 會被拒絕,防止失控查詢。 - 部分命中不是錯誤(命中的項目連同
missing_ids清單一起回傳)。 - 不洩漏存在性:屬於其他 namespace 的項目與不存在的 id,兩者都落入
missing_ids。呼叫者無法探測其他 Agent 擁有什麼。
沒有什麼需要開啟。記憶智能搭載在既有的記憶引擎上:
- F1 在呼叫者把
subject+predicate傳給store_temporal的當下生效;單純儲存維持不變。 - F2a 只要 channel-reply 路徑上存在
ctx.mistake_notebook就會觸發。 - F2b 使用
DEFAULT_CONSOLIDATE_THRESHOLD = 3。 - F3 以
memory_fetch_batchMCP 工具揭露,與其他每個記憶工具一樣受 scope 管控。
遷移在引擎初始化時自動執行:既有資料庫由冪等的 ALTER 迴圈就地升級。
寫入端投毒防護(D2)
Section titled “寫入端投毒防護(D2)”寫入端的爆量偵測器預設開啟,可在 config.toml 調整。區段缺失或格式錯誤時會回退到下列預設值(fail-safe,偵測器維持開啟):
[knowledge_guard]enabled = true # 同來源爆量偵測器的總開關。預設 truewindow_secs = 3600 # 滑動窗長度(秒)。預設 3600(1 小時)max_per_subject = 5 # 一個來源在窗內對同一 subject 可寫入的事實上限,超過即隔離。預設 5取代時的可信度檢查預設開啟:
[memory]supersession_trust_guard = true # false = 可信度較低的寫入又能取代可信度較高的目前事實(v1.67.1 之前的行為)寫入路徑上的注入掃描是無條件執行的(無設定項)。排序信任權重 w_trust(預設 0.10)位於 RetrievalWeights(per-engine,非 config 鍵);當 w_trust = 0.0 時,排序與 D2 之前逐位元組相同。
為什麼這很重要
Section titled “為什麼這很重要”事實不再悄悄過期
Section titled “事實不再悄悄過期”在 F1 之前,一筆記憶會永遠寫著「部署目標是 Vercel」,即使使用者早已轉到 Cloudflare。現在舊事實被關閉、新事實接手,一般搜尋只會回傳當下為真的內容,而歷史仍可透過 get_history / get_at 查詢。
錯誤複利成能力
Section titled “錯誤複利成能力”F2 在預測引擎的錯誤訊號與 Agent 未來行為之間閉環。錯誤不只是被記錄,它會在相似主題上被浮現(F2a),一旦重複發生,便固化為一條常設的 semantic 規則(F2b)。Agent 在模型不變的情況下變得更好。
取回不付往返稅
Section titled “取回不付往返稅”F3 把 N 次囉嗦的 MCP 呼叫變成一次,附帶乾淨的部分命中契約,且不會跨 namespace 洩漏。上下文重建變得便宜。
設計上即非侵入
Section titled “設計上即非侵入”這一切都無須重寫 schema 或新建 MemoryEntry。九個可為 NULL 的欄位、兩個索引、一個冪等遷移,以及一本早已存在的 notebook。整個功能疊加在現役引擎之上。
一疊扁平的筆記什麼都不忘,也什麼都不學。一份好病歷兩者兼具:它替事實打上時間戳,讓舊的能優雅退場;它把重複的錯誤化為常設準則;它讓你一次就調出需要的那幾頁。記憶智能賦予每個 DuDuClaw Agent 這份病歷,建構於它早已擁有的記憶引擎之上。