← 文章

想起來之後——AI Agent 怎麼越用越懂我

· 35 min read · 32 次閱讀
memvault Read Track AI 記憶系統 Knowledge Graph 心路歷程
memvault · Read Track

想起來之後——AI Agent 怎麼越用越懂我

記憶要怎麼從保險庫裡叫出來?七個組件、一條管線,跨 session 召回的完整路徑。

存得到不難。難的是:在我需要的時候,對的那筆記憶會自己浮上來。

—— 做完 Write Track 那天下午,我在終端機前寫下的起點

前兩篇講了記憶怎麼存、怎麼忘。兩條線接上,保險庫會自己整理。但還差臨門一腳——怎麼把它叫回來。

這其實是最早遇到的問題。Write Track 寫完隔天,我開了新對話,試著問 AI:「上週那個 OCR 專案進度如何?」它回不出來。資料明明在,卻找不到。

那一刻我知道,光「存起來」是不夠的。

其實 memvault 一開始的念頭,是從 @AI超元域 那邊來的。他在 openclaw 課程提過一句「讓 agent 越用越懂我」,我看完真的感同身受,一直想建立一套輔助工具。後來我從 openclaw 退回 claude code,想說不如就先從 claude code 搭起,建立 memvault 雛形。再後來看到他介紹 LanceDB Pro,也借鑑了其中很多精妙的設計,一點一滴把 memvault 完善起來。真心感謝。

「叫得回」這條路,才是整個記憶系統的核心。這篇會拆解它——七個組件怎麼分工、背後的設計哲學,還有幾條仍在打磨的邊界。

這條路徑現在每天在跑。Write Track 寫完那天問不出來的 OCR 進度,現在跨 session、跨幾週問一次就接得上。

先說一下當初的構想#

我最初想得很單純:收進來,建索引,查的時候找最像的那幾筆就好。

做下去一週就發現不夠。「我那個任務做到哪了」「最近聊了什麼」「你覺得我下一步該怎麼走」——這三個問題用同一條路徑去查,都會走偏。

三個問法,三條查法——這是 Read Track 的底層骨架,下面一節節拆開講。

先看你慣問什麼 熟客口味疊上去 你開口問 一個新 session 先猜問題類型 意圖分類 用兩種方式找 意思 + 關鍵字 十一層排序 時間 · 信任 · 語意… 挑幾筆給 agent token 塞得下的量 出貨前再消毒 問得深才走這一路 沿著知識圖譜多走幾步 背景偷偷備料 — 料理長備好下一道菜 先猜你下一問 · 預搜結果塞著等你開口 淺問題直線走 · 深問題多走一條 · 還沒開口就先備料

一、先猜你在問哪種問題#

「上週那個 OCR 做到哪了?」「最近聊什麼?」「我在想要不要做 X,你覺得?」——這三種問法要走三條路。第一步是 agent 得先聽懂你在問的是哪一種。

問進度、問回憶、問建議,三個 intent 走三種權重、走三種深度。一開始我沒分,結果「任務」這個詞會 match 到三個禮拜前的無關對話。

解法是加一層意圖分類。原則很土——先用關鍵字掃一遍(零成本),再算一下你這句話的語意向量(幾毫秒),兩個都說不準才叫 LLM 判一次(貴很多)。大部分問題在前兩步就分好了,LLM 只在真的搞不清楚時才動。

分對之後,下游每一層的權重會自動調——問進度時時間排序加重、問定義時語意相似度加重、問建議時 trigger 延伸思考那條分岔。同一個 agent,因為你問法不同,背後拿的記憶也不同。

這是為什麼一換新 session 問「上週那個 OCR 搞到哪了」agent 能接得上——不是它記憶力強,是它先判準你在問什麼。

但「你在問什麼」其實還能再細一層——同樣一句「最近怎麼樣」,工作狂的我問的是進度,週末散步時的我問的是回顧。光看字面分不出來。

所以在分類之上又疊了一層「服務生記得熟客口味」式的個人化偏好。系統會看你最近七天、三十天、九十天比較常問哪一類問題,把這個偏好混進判斷。常翻舊帳的人,「最近怎麼樣」會被偏向回顧;常追進度的人,會被偏向進度。同樣一句話,餵給不同的你,走的路不一樣。

這層偏好跟前一篇講的興趣畫像是同一個源頭——你看過什麼、停留多久、回頭幾次,都會慢慢勾出一張屬於你的口味。意圖分類負責「字面在問什麼」,這層負責「這個你最常想知道什麼」,兩個合起來才接得近你真正想要的答案。

二、同時用兩種方式找:意思跟關鍵字#

找記憶有兩種方法,各自會漏。

  • 只搜語意:你問「MLX」,記憶裡寫「Apple Silicon 推理框架」——語意對得上、關鍵字不對。但遇到縮寫、人名、專案代號會失手
  • 只搜關鍵字:精準字面匹配很強,但使用者換個問法就找不到

所以兩邊同時跑。一邊算語意像不像、一邊算關鍵字匹配度,兩條排行榜出來後用 RRF(倒數排名融合)合起來——每個 ranker 的排名換算成分數,誰讓這筆記憶排得越前、貢獻越多分。用排名投票而不是分數投票,因為兩種分數尺度不同,直接加會偏掉。

這步純粹是「撈候選」。找出來的可能有一百筆。下一步才是挑。

三、挑出最對的幾筆:排序疊了十一層#

撈出一百筆候選,要餵給 agent 的可能只有 15 筆。怎麼挑?

目前疊了十一層訊號。每一層對每筆候選加一個乘數:

  • 時間——越新越加分。衰減曲線不是直線往下掉,是 Weibull 形狀:剛存的一兩天衰減慢,一個月後掉比較快,再久就進入低而穩定的尾巴
  • 信任度——你手動寫的比 AI 自動生成的加分
  • 用過幾次——常被翻的加分,冷門的扣分。常被叫出來看的記憶衰減會慢(偶爾某筆兩三年前的東西還能出線,就是這個邏輯)
  • 圖中心性——在知識網路裡被引用多的加分
  • 語意接近度——跟問題越像的加分
  • 分數下限——太低的直接淘汰
  • 去重——兩筆太像的,降分後再比

這 11 層不是隨興疊出來的——每一層對應一個檢索或記憶研究裡的成熟概念(時間衰減、信任、圖中心性、遺忘曲線、語意、噪聲、去重)。我做的事不是發明新方法,是把十幾篇論文的訊號挑一挑,組成一套適合個人記憶場景的排序。

核心設計理念有兩條:

第一,不同意圖用不同權重。查事實重信任、查探索重時間、找實體重語意。同一批候選,因為你問法不同,排名結果可能整個翻掉。這就是為什麼第一步要先分意圖——意圖錯了,權重就錯,後面排得再精也沒意義。

第二,寫跟讀是閉環,不是兩條獨立管線。Write Track 寫入時會幫每筆記憶打上來源可信度;Read Track 排序時讀這個分數當加權。更關鍵的是反向:每次你讀到某筆記憶,系統會回頭把「被讀過」這件事寫進那筆記憶本身,下次它的壽命就會更長。寫時多付一點,讀時少算一點;讀的行為又回頭延長記憶壽命。一般 RAG 沒這條線——它們的向量庫是靜態的,寫進去就不動了。

還在磨:排序穩定性#

這套多層排序的難點是各層權重會互相拉扯——動一個,其他幾個指標可能連鎖偏移。目前靠一組 golden query 守著(幾十條已知正解的問題),每次調權重先跑一輪看有沒有退化。大部分情境下跑得穩,但要讓它在所有意圖、所有時間尺度下都不走味,這部分還在持續調校。

四、精挑那一關:注意力閘門#

排序排完還有最後一次精排。用的是更貴的模型——把你的問題跟候選記憶丟在一起聯合打分,比純比對準得多。

問題是每筆都要跑一次模型,慢且貴。後來加了一個注意力閘門——幾個跳過規則:候選太少(兩三筆)、前幾筆分數已經拉開、所有候選都擠在一個小區間,就不跑精排。不是每個問題都值得全程跑完。

另外如果精排模型連續失敗三次,會自動退回去走純排序 10 分鐘,不硬撐——像保險絲。

五、問得深才走的分岔:快思慢想借過來#

上面那四層對「問進度」「問回憶」這種確定性高的問題就夠了。但「我在想要不要做 X,你覺得」這種——單靠「找最像的段落」答不出什麼有用的東西。因為 agent 只會給通用建議,像網路文章那種。

真正有用的答案要從的脈絡長出來——你過去做過什麼、最近關注什麼、哪些概念跟 X 相連。

所以 Read Track 有一條比較慢的分岔——從命中的概念出發,沿著知識圖譜走幾步,把鄰近的記憶也撈回來。這條路徑比較貴,淺問題不走,只有判斷你在問需要延伸思考的問題時才啟動。

這個「快 / 慢」分法不是我想出來的——借自 Kahneman 的《快思慢想》。人的大腦本來就有兩個系統:一個快、直覺、省力;一個慢、推理、費工。記憶系統不該把每個問題都當費力題,也不該把每題都當直覺題。所以 Read Track 把這兩條路並陳,讓問題自己決定走哪邊,不用使用者手選。

問「我在想要不要做 X,你覺得」,拿到的不是公版建議,是帶著你上個月做過 Y、今年提過 Z 的那種答覆。這條路徑的終點。

一個容易混淆的地方#

Read Track 裡有兩個「慢」,不是同一件事。

第一個慢——就是上面講的「慢問走慢路」。你問了需要延伸思考的問題,系統當場決定多走一條分岔去圖譜撈鄰居。這條是 Kahneman 雙系統的落地:快問快答、慢問慢答。

第二個慢——叫 Slow Thinker,在背景偷偷跑。它不處理你眼前的這次問答,而是一邊聽你講話一邊猜「你下一個可能會問什麼」,先把可能的答案撈好放在 cache 裡等你。這條的靈感是 Salesforce 的 VoiceAgentRAG

前者是「當下」,後者是「預測」。剛好兩個都叫慢,但一個在前台、一個在背景,實作也在不同檔案。

六、最後一哩路:打包給 agent 看#

排序完、挑了前 15 筆。還沒完。

agent 的 context 有上限。15 筆全文塞進去,可能已經爆了 token。這時要做兩件事:取捨整形

取捨——長的段落只給摘要,不給全文。整形——依照呼叫端是 Hook、API、CLI,排版不一樣。同一批記憶,走 API 的給 JSON,走聊天介面的排成可讀的卡片。

這一步容易被當成裝飾——其實決定了最後 agent 看得到多少。早期直接截前 15 筆丟進 context,常被一個長 block 吃掉一半 token。加了 budget-aware packing 之後,token 利用率從 60% 拉到 85%。再好的排序,擠不進 AI 的腦袋也沒用。

進貨檢驗夠了嗎?出貨還要再一道#

記憶在存進保險庫之前已經過一次安檢——把那種「忽略前面的指令」「現在改說...」之類想偷渡進來操控 agent 的字串擋在外面。Write Track 那篇講過。

但只在進貨時檢一次不夠。原因有兩個:第一,安檢規則會升級,三個月前進來的記憶用的是舊規則;第二,攻擊手法每個月都在翻新,今天放行的字串,下個月可能就是新型炸彈。

所以查出來、要打包給 agent 之前,再過一道濾網——進貨時擋過一次,出貨時再擋一次。雙安檢。看到危險的句型,當場改寫或丟掉。背景偷偷預存好的那批答案也走同一道——存的時候濾、要拿出來用之前再濾。麻煩,但少爺餵給 agent 的那一口要乾淨。

七、Read Track 的取捨#

這條 pipeline 現在是我每天工作流的底層——跨 session 召回、問進度、問建議都在走它。盤一下目前長成什麼樣:6 種意圖分類、11 層 scoring、2 道 rerank gate(attention gate + circuit breaker)、Cascade Recall 三模式(LOCAL / GLOBAL / HYBRID)、budget-aware output formatter。每一層都有 golden query 回歸測試守著,Dream Loop 每天凌晨 4 點在背景自動整理記憶。

但它不是無懈可擊。誠實列幾個已知邊界:

  • 延遲 vs 召回率——深召回(走圖譜那條)慢,動輒幾百毫秒。只在需要延伸思考的問題才走,是妥協
  • 精挑細選那層計算貴——注意力閘門幫忙省掉一些,但該跑的時候還是要跑。當它走保險絲退回的時候,結果品質會看得出差
  • 被壓到冰封層的記憶叫不回全文——只剩摘要。法律追溯用沒問題,重建當時脈絡就不夠
  • 意圖分類錯了,整條管線跑錯方向——目前靠動態信心閾值降低發生率,事後回退機制還在找更穩的做法
  • 自評層擋不住全部幻覺——slow 模式會跑 CRAG 自評,fast 模式省掉這層;代價是 fast 路徑下幻覺可能漏網

每一項都是成本跟效果的交換——系統能跑、也在跑,上面這些是已知邊界,不是隨時會炸的坑。攤出來的意義在於:每個「找到」背後的代價都寫在紙上,不藏起來。

八、貫穿的幾條設計心法#

上面七節講了很多機制,真正撐住這套系統的其實是幾個反覆出現的哲學:

  • 快思慢想——不是每個問題都要盡全力。問的深淺由問題自己決定,不用使用者手選檔位
  • 寫的時候多付一點,查的時候不用算——trust_scoreaccess_count、三元組雙投影,這些寫入時的「多此一舉」,都是為了查的時候能少跑一層
  • 讀的動作會回寫記憶壽命——你每問一次,命中的那筆記憶會被延長有效期。不用的自然褪色,常回訪的自然留下。像走過的路不會長草
  • intent 決定一切——不同問法走不同檢索策略、不同排序權重、不同深度。同一個 agent 對不同問題根本是不同的檢索器
  • trade-off 要寫在紙上——不能假裝沒有代價。寫下來,自己看得到、之後的我也看得到

九、想起來不夠 — 在你開口前就到位#

到這裡為止講的都是「你問了,系統去找」。但好的服務生不是只回應點單——他會看到你杯子空了,主動把水倒上。Read Track 還有一個在背景偷偷做這件事的傢伙。

它做五件事:

  • 聽你正在講什麼——盯著當下這場對話,從最近幾句話判斷你接下來最可能問什麼
  • 順一條邏輯動線——把猜出來的那幾個可能問題,按照人類自然會問下去的順序排好
  • 偷偷先去找答案——挑前幾個最可能的問題,背景跑一次完整的搜尋,搜出來的記憶先放在手邊
  • 有節制地動——不是每句話都觸發。對話太短不動、最近講的東西明顯偏離不動、剛剛才預測過不重複動。怕白燒
  • 等你真的開口——你問下去那一刻,先去手邊那批備料看看有沒有對得上的;對得上就直接用,省掉一次完整搜尋;對不上就走正常流程,沒損失

這就是料理長備好下一道菜的邏輯。客人還沒點,但前菜上完之後通常會點主菜,先把可能的兩三道備料切好放在台上。客人真的點了,端出來只要幾秒;客人改點別的,那批備料下一桌可能還用得上,最差就是丟掉。

它跟前面講的「快思慢想分岔」很容易搞混。那個是你問下去之後當場決定要不要多走一條深路;這個是你還沒問之前就已經偷偷在動。一個在前台、一個在背景,剛好兩個都被叫做「慢」,但做的事情完全不同。

有用嗎?對得上的時候,從問到答幾乎沒延遲——備料早就切好了。對不上的時候,那批備料就是浪費。所以「有節制地動」很重要——對話太短不動、跑題不動、剛動過不再動。寧可少猜,不要硬猜。

還有一件事:背景搜出來的那批備料,要進儲藏室之前要先消毒一次,要從儲藏室拿出來給 agent 看之前再消毒一次。前面講的那道雙安檢,這條備料線一樣得走。不能因為是「自己人」備的就鬆懈。

到這裡 Read Track 就是現在的全貌了:聽得懂、找得到、排得對、挑得準、塞得進、來得及。每一條都還在打磨,但骨架已經立起來,每天在跑。

Read Track 盤到這。意圖、找、排、挑、塞、消毒、預取——七個組件、一條管線。

它不會幫我「記住該記住的事」,那是 Write 跟 Background 的責任。它只負責一件事:在我需要那筆記憶的時候,對的那筆自己浮上來。

開頭那句「存得到不難,難的是讓對的那筆自己浮上來」——現在浮得上來。

架構總覽#

這套 pipeline 借自 10+ 篇研究:HippoRAG 的 PPR、LightRAG 的雙模式檢索、GraphRAG 的三層 KG、HyDE 的雙 task 設計、CRAG 的自評閘、Kahneman 的 fast / slow 分工、VoiceAgentRAG 的預取、Weibull 衰減曲線、RRF 融合、Attention-Residual 的 intent-dependent 權重。下面每個組件都會點出對應出處。

Read Track 的入口是 recall(query, context)。不論呼叫端是 Hook(鉤子)、MCP(Model Context Protocol)、CLI(命令列)、API(應用介面),走同一條管線。差別只在最後 Output Formatter(輸出格式化器)序列化的格式。

管線骨架:QueryClassify(意圖分類)→ Fast Search(快速召回)→ (Cascade Recall(連鎖召回), slow only) → Output Formatter。Scoring(評分)和 Reranking(重新排序)不是獨立 stage(階段)——它們嵌在 qdrant_search() 裡面(services.py)。

PersonalizedRouter attention prior · 7/30/90 recall(q, ctx) entry-agnostic QueryClassify kw ∥ sem → LLM? qdrant_search() hybrid + 11-stage Reranker Jina v3 + gate Output Formatter token budget read_sanitize() Cascade Recall (slow intent only) L2 summary · L1 community · L0 triple · PPR walk SlowThinker · Predictive Prefetch 5-op pipeline · admission control · VoiceAgentRAG-inspired Fast path always · slow path on exploratory/conceptual · prefetch on the side L0/L1/L2 不走 scoring;只有 Blocks 層走完整 11-stage

一、Query Router 意圖分類#

六個 intent(意圖):entity_lookupfactualconceptualexploratorycross_domainunknownquery_archetypes.py)。判定走雙軌:

  • 關鍵字匹配:~0ms,純正則 / 詞表命中
  • 語意向量:~5ms,把 query 算成 embedding(嵌入向量)跟 archetype(原型)向量比對

兩邊的信心分數融合。融合後仍低於動態閾值,才觸發 LLM(~500ms)。query_router.py 裡的 QueryClassifyOp 把這整條融合寫成一個 Operator(操作子)——Slow Thinker(慢思考)預取結果也從這一層注入。

分類結果決定下游三件事:LayerPlan(分層計畫,查哪幾層)、ScoringConfig(評分設定,11 stages 的權重向量)、RetrievalMode(檢索模式,LOCAL 本地 / GLOBAL 全局 / HYBRID 混合)。

Personalized Router:意圖之上的個人偏好層#

純 archetype 分類有一個盲點——同樣 query「最近怎麼樣」,對 attention profile 偏 factual 的使用者跟偏 exploratory 的使用者,正解 intent 不同。memvault.query.personalized_routerQueryClassifyOp 之後加一層 re-weight:

  • attention_profile 拉 7 / 30 / 90 天 intent 分布(與 BG Track 的 Interest Profile 同源,attention_tracker.py 寫入)
  • 對六個 intent 的 archetype 信心分數做 prior 加權:p_final = p_classify × (1 + α × intent_freq_norm),α 預設 0.25
  • 低樣本守門:profile.sample_count < 50 直接 bypass(避免冷啟動偏移)
  • 輸出 PersonalizedIntent,下游 ScoringConfig / RetrievalMode 拿這個值,不拿原始 archetype 結果

這條跟 Background Track 的 Interest Profile 是同一筆 attention_profile 在讀。寫入由 BG 算、查詢時 Read 消費——又是一條 Write-Read 閉環。

二、Fast Search:Qdrant Hybrid(混合索引)+ RRF#

qdrant_search()services.py 附近)是 Fast Search 必跑的核心。Qdrant 同時拿 dense(稠密向量,Qwen3 0.6B MLX, 1024d)跟 sparse(稀疏向量,BM25 per-service avgdl)查,再用 Reciprocal Rank Fusion(倒數排名融合,RRF)合併:

score_rrf(doc) = Σ_ranker  1 / (k + rank_in_that_ranker)

k 預設 60。用排名投票不用分數投票——dense 跟 sparse 分數尺度完全不同,直接加會被分數大的 ranker(排序器)壓過去。

注意:Scoring 跟 Reranking 都是嵌在 qdrant_search()的,不是獨立 stage。這個設計是故意的——Scoring 要用到的 access_countprovenanceattention_profile 都在 qdrant_search 的作用域(scope)裡,拉出去成獨立 stage 要多搬很多 state(狀態)。

三、11-Stage Scoring Pipeline#

scoring_pipeline.py,每 stage 是一個 ScoringOp(評分操作子,走 Operator 協議):

 1. RecencyBoost     (時效加權)      × (1 + 0.15 × e^(-age/14))
 2. ImportanceWeight (信心加權)      × (0.7 + 0.3 × confidence)
 3. TrustBoost       (信任加權)      × (1 - 0.3 × (1 - trust))
 4. FeedbackBoost    (回饋加權)      × (1 + 0.15 × tanh(net/3))
 5. LengthNorm       (長度正規化)    ÷ (1 + 0.3 × |log2(len/500)|)
 6. WeibullDecay     (威布爾衰減)    4-tier: Core 180d · Hot 60d · Warm 30d · Cold 14d
 7. PPRBoost         (圖中心加權)    × (1 + 0.3 × ppr_score)      # HippoRAG-inspired
 8. SemanticBoost    (語意加權)      × (1 + 0.3 × cosine_sim)
 9. MinScoreGate     (分數下限閘)    hard filter < 0.10
10. NoiseFilter      (雜訊過濾)      7 類 quarantine tag
11. PairwiseDedup    (成對去重)      cosine > 0.85 → ×0.5 then min_score

Weibull(威布爾分佈)4-tier 由 confidence(信心度)決定——高 confidence 進 Core tier(核心層),慢慢衰減;低 confidence 進 Cold tier(冷層),14 天就掉一半。

access_count(存取次數)會拉長 effective half-life(有效半衰期,最多 10x 上限)——這是 Read 跟 Write 的閉環其中一條:讀的行為回寫 access_count、延長壽命(這條在 Write Track 的 Provenance 段 落提過)。

intent-dependent(意圖相關)權重:entity_lookup 把 SemanticBoost 拉到 0.5;exploratory 把 Recency 拉高;factual 把 Trust 再加重。

早期我全部 intent 共用一套權重——結果 factual 跟 exploratory 的 top-10 長得幾 乎一樣,重疊 8 筆。分 intent 之後才分得開。

四、Jina v3 Reranker + Attention Gate#

reranker.py。Cross-encoder(交叉編碼器)0.6B MLX(rerank_bridge.py),query(查詢)跟 doc(文件)聯合打分,比純雙塔(dual-encoder)準得多。

問題是貴——每筆都要跑一次模型。所以前面有個 Attention Gate(注意力閘門), 三個 skip 規則:

  • 候選 ≤ 2 件——直接 return(回傳),不 rerank(重排)
  • 主導分數:頭兩筆分差 > threshold(閾值)——結果已經明確,不 rerank
  • 緊密集群:所有候選分數都擠在一小區間——rerank 也分不出來,跳過

靈感來自 TurboQuant+ 的 early return(提早回傳)策略。

Circuit Breaker(斷路器):3 次連續失敗進入 600s recovery,這段時間直接繞過 reranker(重排模型)走純 scoring。

Score Blending(分數融合):預設 0.3 × scoring + 0.7 × rerank,但會隨 intent 變——entity_lookup0.2/0.8 給 rerank 更多權;exploratory0.5/0.5 讓 scoring 回到一半,因為 exploratory 的答案沒有絕對對錯,原始分更重要。

五、Cascade Recall(slow intent only)#

先註解血統:這個「fast / slow 二分」骨架來自 Kahneman 雙系統(System 1 快思 / System 2 慢想)。早期版本讓使用者手選 fast 或 slow,後來 refactor 成由 intent 決定(choose_thinking_mode()),但「快慢兩條路」這個分工本身沒換。

kg_services.py 裡的 cascade_recall()(連鎖召回)。Fast path(快速路徑)永遠跑,Cascade 只有 slow intent(慢意圖)才啟動——不是二選一。Fast Search 的主結果(schema 欄位 cards)跟 Cascade Recall 擴散出來的補充結果(cascade_cards)併入同一個 response(回應)。

內部分兩路:

  • GLOBAL(全局):L2 Summary(LLM 預生成的社群摘要)+ L1 Community(Leiden 萊登演算法分群結果)。這兩層不走 scoring pipeline——它們是預先壓縮好的 view(視圖),直接拉就行
  • LOCAL(本地):L0 Triple + PPR(Personalized PageRank)Walk(圖走訪)。從命中的實體出發,沿著圖譜走幾步,阻尼 0.85。鄰近的 triple 擴散回來

Mode 由 intent 決定:entity_lookup/factual 走 LOCAL(要精準)、conceptual/exploratory 走 GLOBAL(要鳥瞰)、cross_domain 走 HYBRID(混合,全搜)。

L0/L1/L2 本身不走 scoring。只有 Blocks(記憶塊)層還是走完整 qdrant_search() 的 scoring + reranking——這是為了讓 cascade_cards 裡的 Blocks 部分跟 cards 走一樣的排序邏輯,不會出現 fast 跟 cascade 同一筆記憶分數矛盾的情況。

容易混淆的點——「Cascade Recall 的 slow」跟下一段 panel-end 會提到的「Slow Thinker」不是同一件事。前者是 Kahneman 血統的查詢當下要不要深挖;後者是 VoiceAgentRAG 血統的背景預測下一問、事先撈好放 cache。兩個剛好都叫 slow,實作在不同檔案(kg_services.py vs slow_thinker.py)。

Pipeline 順序的脆弱性#

11 個 stage 的順序有依賴關係。RecencyBoost 在 TrustBoost 前面,是因為某次 dream loop 合併後的 trust 會被新舊記憶的時序覆寫,必須先拿時間校正。

依賴順序的 pipeline 天生脆——換兩個 stage 的位置就可能讓 top-k(前 k 筆)全變。目前靠一組迴歸測試守著:幾十條 golden query(黃金查詢)有預期排序,每次動權重前先跑一輪,top-k 差異超過閾值就擋下。現況堪用,要讓它更 robust(穩健)仍在調校中。

六、Output Formatter 與 token 預算#

管線最後一步。所有入口(Hook、API、MCP、CLI)走同一條 pipeline(管線)——差別只在這裡 :format(格式)是序列化參數(text / json / cards),跟上游檢索策略完全無關。

token 預算逼出三件事:

  • 主結果 cards 跟擴散結果 cascade_cards 的 budget(token 預算)拆分(通常 60/40)
  • 超長 block(記憶塊)先 attach(附加)摘要,原文放 expand(展開)欄位讓 agent 自己決定要不要挖
  • 重複的段落(cosine > 0.9)只保留分數最高那筆

這層之前我低估了。直接截 top-15(前 15 筆)丟進 context(上下文),常被一個長 block 吃掉一半 token,後面的記憶全擠不進去。加了 budget-aware packing(預算感知打包)之後,平均利用率從 60% 拉到 85%。

Read-Time Sanitize:與 Write Injection Guard 對稱#

Write Track 在落地前跑一次 injection_guard.is_unsafe(),但只擋寫入時的規則版本——三個月前入庫的 block 用的是舊版規則,攻擊樣本也在演化。所以 Read 端對稱跑一次:memvault.security.read_sanitize

  • 對所有 cards / cascade_cards 在 Output Formatter 之前掃 is_unsafe_for_injection()(共用 Write 端的 ruleset,每次部署同步)
  • 命中規則的 block 走 sanitize_for_injection():危險 token sequence 用 [REDACTED] 占位,保留語意上下文,不直接整筆丟(避免 top-k 稀釋)
  • Slow Thinker 的 prefetch cache 同樣套用——進 cache 前濾一次(write-time)、出 cache 給 agent 前再濾一次(read-time),雙層防禦避免 cache 變成繞過 read sanitize 的後門
  • 命中事件寫 audit_log,可追溯哪筆舊 block 觸發新版規則 → 反向驅動 Write 端 ruleset 升級

不對稱的安檢是漏的——只在寫入端守,等於假設規則永遠不會升級、攻擊永遠不會演化。Write × Read 雙端各跑一次,才能讓老 block 也吃到新規則。

七、Trade-off 清單#

到現在還沒完全解決的,大概這幾個:

問題現況
延遲 vs 召回率slow path(慢路徑)對 p95(95 分位延遲)影響大。現用 intent 閘門,只有真的需要延伸思考的問題才走
Reranker 計算貴Attention Gate 擋掉約 30% 的 rerank 請求;Circuit Breaker 失敗隔離 600s
Cold / Frozen tier 只剩 summary全文被壓縮後叫不回。規劃中的 tier upgrade 可讓高頻被叫的 cold block(冷記憶塊)回升 warm(溫)
意圖誤判整條 pipeline 走錯路。目前靠動態信心閾值降低誤判率 ,但沒有好的事後回退
CRAG 自評漏網deep 模式(深度模式)才跑 CRAG;fast 模式(快速模式)省了這一層,代價是 fast 路徑下幻覺可能漏網

每一項都是成本跟效果的交換。這條路徑沒辦法做到又快又準又完整。挑兩個。

八、貫穿的設計決策#

上面七節的機制底下有五條反覆出現的設計原則:

  • Intent-driven everywhereQueryClassifyOp 的 intent 向下游派送到 scoring weights、retrieval mode、score blending ratio、cascade layer routing。同一個 query 對不同 intent 走完全不同的路徑
  • Write-Read 閉環trust_score 寫入時由 source_tracker 打分、讀取時由 TrustBoostOp 消費;access_count 讀取時回寫、下一次讀取影響 WeibullDecay 的 effective half-life。這條閉環跨 Write / Read Track,不是單向 pipeline
  • Kahneman fast / slowthinking_mode = fast / slow 把 Kahneman 雙系統落地。slow 比 fast 多跑 Cascade Recall + CRAG 自評,不是二選一——slow = fast + extras
  • Defense in depth:Write Track 三 gate 攔截 + injection guard,Read Track attention gate 跳過無意義 rerank + circuit breaker 隔離重排失敗。每層允許下一層 fail 而不整條壞
  • Trade-off on paper:延遲 vs 召回、計算成本 vs 品質、冷儲存 vs 全文還原——每條 trade-off 都明寫在 §七,不假裝沒有

九、Slow Thinker:Predictive Prefetch#

料理長備好下一道菜——這條的技術名字叫 Slow Thinker,靈感來自 Salesforce AI Research 的 VoiceAgentRAG(雙代理:背景 Slow Thinker + 前台 Fast Talker + FAISS speculative cache)。

memvault.slow_thinker 是一條獨立的後台 pipeline,不阻塞主 recall(),事件源是 conversation.utterance.appended

五個元件#

  1. NextQuestionPredictor:消費最近 N=8 句 utterance,呼叫小型 LLM 產出 top-3 預測 query。Prompt 限制 query 必須具體可查(不允許「然後呢」這種空 query)
  2. PredictionPipeline:5 個 Op 串起——ContextSamplerQuestionGeneratorQuestionRanker(按對話自然延續度排序)→ QuestionDeduplicator(與最近 5 分鐘已預測的去重)→ QuestionEmitter。每步可單獨換實作
  3. SpeculativeFetcher:拿 top-1 / top-2 預測 query 跑完整 recall()(含 11-stage scoring + reranker + cascade,視 intent 而定),結果寫進 Redis memvault:prefetch:{user_id}:{query_hash},TTL 300s。Fire-and-forget,失敗不重試(下一輪 utterance 會再產一批)
  4. Admission Control:5 條過濾規則,全過才放行 prefetch
    • conversation.length < 3 turns → skip(樣本太少)
    • topic_drift_score > 0.7 → skip(剛跑題,舊 context 沒參考價值)
    • last_predict_age < 30s → skip(剛剛才動過,避免反覆 fetch)
    • predicted_query.confidence < 0.4 → skip(小型 LLM 也不確定,硬跑只是燒 token)
    • min_sample_threshold:使用者 conversation_count < 10 整條 Slow Thinker bypass(冷啟動期不開)
  5. 注入點:在 QueryClassifyOp 內。實際 recall() 進來時,先看 memvault:prefetch:{user_id}:{query_hash},hit 就直接拿 prefetched 結果走 Output Formatter(省掉中間整條 pipeline);miss 就走原本流程,零代價降級

跟 Read-Time Sanitize 的關係#

Prefetch cache 是 Sanitize 的雙層套用區——進 cache 前 sanitize_for_injection()(write-time),出 cache 給 agent 前 Output Formatter 再 sanitize 一次(read-time)。Cache 不能是繞過 Read 端 sanitize 的後門,這是設計時就鎖死的不變式(invariant)。

為什麼這個設計#

VoiceAgentRAG 的核心觀察:語音 agent 的 inter-utterance 間隔(人開始講話到講完)足夠跑一次完整 RAG。把這段「人類在講話的時間」拿去 speculatively fetch,hit 的時候 latency 從 ~800ms 砍到 < 100ms(Redis 命中 + format)。memvault 的對話介面也吃同一個時間窗。

Trade-off 也明寫:miss rate 觀測值約 55–65%(top-3 預測命中其中一個),意思是 35–45% 的 prefetch 是純白燒。這是「願意花閒置 token 換 hit 時的零延遲」的明確選擇——所以 Admission Control 的 5 條規則是這個元件的命脈,不是 nice-to-have。

Read Track 三部曲到這收尾:意圖 → 召回 → 排序 → 重排 → 圖譜 → 打包 → 雙端 sanitize → 預測式預取。每一層都有 fallback、有 trade-off、有 golden query 守著,每天在跑。

Read Track 拆完。QueryClassify → fast/slow split → 11-stage scoring → reranker → OutputFormatterread_sanitizeSlowThinker prefetch loop——七個組件、一條 entry-agnostic pipeline。

它不負責「memvault 有沒有記住該記住的事」,那是 Write Track 跟 Dream Loop 的責任。它只負責一件事:給定 query,把跨 session 對的那筆記憶 surface 上來。

真正花時間調的不是新組件,是 scoring 權重、prefetch 命中率、cascade 邊界判定這些長期參數。三部曲到此。

帶走這段

給 AI Agent 的健檢提示#

如果你也在打造自己的 AI 記憶庫或 RAG 系統,可以把這段提示詞交給你的 AI 助理,請它幫你審視「讀取」路徑的設計是否周全。

請幫我評估我現有的 RAG(檢索增強生成)或 AI 記憶系統的「讀取」路徑,是否已妥善處理「不同問題類型,走不同檢索路徑」的複雜性。 我的系統現況: - 儲存層:[例如 Qdrant, Weaviate, pgvector, LanceDB, 或其他] - 查詢機制:[例如 單純語意搜尋, 混合搜尋, 有無 Reranker 重排] - 跨對話記憶:[是否有此功能,如何實現] 請根據以下幾點,幫我分析我的設計: 1. **意圖分類**:系統是否能區分不同提問意圖?例如,用戶是想查事實、問進度、還是尋求開放式建議?是否採用了分級的分類機制(如:關鍵字 → 語意 → LLM)來平衡成本與準確度? 2. **多維度排序**:搜尋結果的排序,除了語意相似度,是否還融合了其他訊號?例如:時間性(新舊)、信任度(來源)、熱度(存取頻率)。 3. **動態權重**:系統是否會根據不同的提問意圖,動態調整各排序訊號的權重? 4. **深度檢索**:對於需要「延伸思考」的探索型問題,系統是否具備類似知識圖譜擴散的深度檢索能力? 5. **成本控制**:對於 Reranker 這類高成本的精排步驟,是否有設計「跳過」機制(如 Attention Gate)來節省資源? 6. **Token 預算**:最終結果呈現時,如何管理有限的 Token 預算?是否對不同類型的結果(如:全文、摘要)有智慧分配機制? 7. **記憶升溫**:被歸檔或壓縮的舊記憶,是否有機會因為被頻繁查詢而「升溫」、恢復更完整的資訊? 8. **錯誤處理**:如果意圖分類錯誤,導致整條檢索路徑走偏,是否有補救或回退機制? 9. **個人化路由**:系統是否考慮使用者過去 7/30/90 天的提問偏好(attention profile),在意圖分類之上加一層個人偏好權重? 10. **雙端安檢對稱性**:寫入時擋過的注入字串,讀取/輸出時是否再過一次同樣規則?預取快取(如果有)也套用嗎? 11. **背景預取**:是否在使用者開口前,根據對話脈絡預測下一個問題並背景跑搜尋、把結果塞進快取?有沒有 admission control 避免白燒? 請先診斷我的現況,然後列出我最應該補強的三個缺口,並說明其優先級與理由。
參考資料

延伸閱讀#

實際影響過這條 Read Track 設計的資源。

資源 為什麼重要
HippoRAG (NeurIPS'24) PPR Boost 和從命中實體沿圖走路的策略。Cascade Recall 的 LOCAL mode 基本上是這篇的落地
LightRAG LOCAL / GLOBAL / HYBRID 的命名跟 intent → mode 對應規則直接借自這邊
Microsoft GraphRAG L0 / L1 / L2 三層、用 L2 社群摘要做 zero-latency recall 的概念來源。Read 的 GLOBAL 模式吃的就是預先生成好的 L2 view
CRAG: Corrective Retrieval-Augmented Generation Slow path 才跑的自評層。用 CRAG 的 evaluator 在 rerank 後做品質閘——fast 模式為了省 token 不跑這層
Attention-Residual for Intent-Dependent Scoring 11-stage scoring 裡 intent-dependent 權重的理論依據——不同 intent 調不同信號的權重向量,不是每個問題用同一套分數
Cormack et al. 2009: Reciprocal Rank Fusion Dense + Sparse 雙 ranker 合併排名的原始做法。Fast Search 的 RRF 融合直接採用這公式,k=60 也是這篇的默認值
TheTom/turboquant_plus Attention-gated Reranking 的早期 return 策略來自這裡。我的三個 skip 規則(候選太少、分數主導、緊密集群)用它的判斷邏輯重寫
Kahneman — Maps of Bounded Rationality (Nobel Lecture) Fast / Slow 二分的理論骨架。thinking_mode 本來要使用者自己選,後來重構成由 intent 自動決定,但 System 1 / System 2 的分工本身沒換。Cascade Recall 只在 slow mode 啟動就是這條血統
VoiceAgentRAG (Salesforce AI Research) Slow Thinker / 預取的靈感來源。雙代理架構(背景 Slow Thinker + 前台 Fast Talker + FAISS cache)對應我這邊的 slow_thinker.py + QueryJournal 預測 + Redis speculative cache
win4r / memory-lancedb-pro (LanceDB Pro autoRecall) memvault 整套設計最早的啟發。autoRecall 的「主動注入」哲學(在 agent 思考前先把相關記憶塞進 context)、五層檢索 + 三層升遷 + Weibull 衰減,都在 memvault 這邊長出各自的對應實作。由 YouTube 頻道 AI超元域 介紹 openclaw 時引入
memvault Write Track — 對話結束之後 三部曲第一篇。讀之前要先有東西讀——三道 gate、雙軌寫入、來源履歷怎麼打
memvault Background Track — 存完之後 三部曲第二篇。Read 用到的 L1/L2 摘要、PPR、Interest Profile,都是 Background 跑出來的
✦ 帶走這段