AI Workflow · part 12
[Dev Workflow] 搜尋找得到筆記、卻連不起來:幫 AI 記憶加一張知識圖譜
❯ cat --toc
TL;DR
我的 AI 長期記憶,是 ~600 篇純 markdown 檔,分成三層。檔案本身是唯一的真相源;一個本地搜尋引擎(qmd)讓它們找得到;一張知識圖譜(musubi)疊在上面,用『共享概念』把筆記連起來,讓我問得出搜尋答不了的問題——『這篇還連到什麼?』。撐住這套設計的一條規則:檔案是正本,搜尋和圖都是疊在上面、可以隨時重建的快取,箭頭只往上。這篇先把這個設計、每一層為什麼存在講清楚,最後才另外講我建這張圖時走錯的路(它一個下午變垃圾兩次才能用)。

白話版:把學到的東西寫成純文字檔,再教 AI 看出它們怎麼連
我把學到的東西寫成一個一個純文字檔(markdown),一個檔講一件事——踩過的坑、修好的 bug、哪台機器該怎麼設定。這樣的檔累積了大概六百個。
只是把檔堆在那,AI 用不到。你得先給它一個搜尋框,它才找得到。但搜尋框有個先天的毛病:它只找『用同樣字詞』的檔。同一件事,我一篇寫『模型一直佔著記憶體不放』、另一篇寫『那個服務一開就崩』,其實是同一個原因——記憶體被開得太滿了。可是搜其中一篇,另一篇永遠不會浮出來,只因為兩篇用的字不一樣。
知識圖譜要補的就是這個。你可以把它想成一張自動畫出來的地圖:哪幾個檔在講相關的事,就用線連起來,連的是『概念』不是『用詞』。這篇文章就是這套三層記憶的設計:每一層是什麼、為什麼要有它、換來什麼好處。至於比較亂的部分——我怎麼先把它建錯幾次的——收在最後、標得清清楚楚,不讓它擋住主線。
前言
想像一間工作坊。你先要架子,東西才不會落地——那是儲存。接著你要能一伸手就找到某一支工具——那是搜尋。但真正讓一間工作坊變成『你的』工作坊的,是另一件事:你知道三號架上那個治具,跟九號抽屜那支鑽頭,是同一件工作會一起用到的——就算架上沒有任何標籤這樣寫。那不是儲存,也不是搜尋,是一張關係的地圖,你花好幾年在腦子裡慢慢建起來的。
這個系列的 Part 6,我把工具(搜尋引擎加圖)當成一個『幫你省 token』的方法介紹給你。這篇再往下拆一層:這套記憶到底是怎麼設計的、每一塊為什麼存在。我先把整體架構講清楚,最後才另外講我走到這裡踩過的坑。
這套系統長什麼樣:三層,只有一層是真相源
先給你一張全景。我的長期記憶,底層是一個裝滿 markdown 檔的資料夾。疊在它上面的,是兩個讓這些檔『用得動』的東西——一個搜尋 index、一張圖——但這兩個都不是記憶本身,檔案才是。上面那兩層都是衍生出來的,我隨時可以砍掉重建。

- 檔案——一個檔講一件事,大概幾百個。這是唯一的真相源。
- qmd——一個在本地跑、蓋在這些檔上的搜尋引擎:keyword(比對關鍵字,大約 30ms)加 vector(比對語意、不只看字面,大約 2 秒)。
- musubi——疊在上面的一張圖,用『共享概念』把筆記連起來,讓你問得出『這跟什麼相關』,而不只是『什麼字對得上』。
接下來整篇,就是在講上面這兩層各自為什麼非有不可。它們誰都沒有取代下面那層;每一層都是在回答下一層答不了的問題。
一個資料夾為什麼不算記憶——搜尋補上的那塊
agent 一有記憶,就會撞上一個問題:它會一直重新發現自己早就知道的事。換一個 session、context 乾乾淨淨,它又把三個禮拜前自己寫下的那個 fix,原封不動再推導一遍。理由很單純:一個資料夾的 markdown 不是檢索,它就只是一個資料夾。
這就是 qmd 做的事。它讓這一堆檔『找得到』——keyword 找精確的字,vector 在你講不出確切字眼時用語意去找。等我把它接成一個 first-class tool(agent 一眼就看得到、會主動去用的工具),agent 開工前就會自己先查記憶。一天跑上幾十次很平常,我根本不會特別注意。這堆檔,從一個資料夾變成了記憶。
但搜尋也有極限,而這正是下一層存在的理由。
光有搜尋為什麼不夠——圖補上的那塊
先把兩件事分清楚。搜尋幫你做的是『找到某一篇你想找的筆記』;它答不出的是另一種問題——『我正在看的這篇,還跟哪些筆記有關?』。「找得到」跟「連得起來」是兩回事,而搜尋只做得到前者。
只要有兩篇筆記明明有關、卻沒有共同用詞,你馬上就會卡在這裡。我有一篇筆記寫『Ollama 會把模型常駐在記憶體裡』,另一篇寫『vLLM 一啟動就崩』。這兩篇其實在講同一個問題——同一個 128GB 的記憶體池被開得太滿——但它們幾乎沒有半個共同的用詞。你搜其中一篇,另一篇永遠不會冒出來。這個關聯是真的,只是它活在概念這一層,而搜尋的 index 根本沒把它記下來。
圖就記得下來。做法其實很土:把每篇筆記當成一個點(node),兩篇只要講到同一個概念,就在它們之間連一條線(edge);整張圖算一次,之後就能『順著線一路走過去』(traverse)。這樣一來,『這篇的鄰居是誰?』變成一個真的問得出口的 query;『哪些筆記是孤立的、哪些正在過時?』也是——這些全是搜尋框根本答不了的問題。
有一個設計決定,讓上面這一切加起來是安全的:我沒把任何東西搬進 Obsidian、或任何一個 app。markdown 檔留在原地當唯一的真相源;圖是疊在上面、永遠不去改動那些檔的一層。檔案優先、圖只是一個隨時可以重建的衍生物——就是這一刀切得乾淨,後面每一步才都保持可逆。
這樣分三層,換來什麼
三層疊起來,有四個我在任何記憶系統裡都會想留下的性質:
- 層是疊加的,不是彼此取代。 圖沒有取代搜尋,搜尋也沒有取代檔案。每一層疊在下一層上面,回答它答不了的問題:檔案負責『存』、搜尋負責『找』、圖負責『連』。
- 只有檔案是正本。 搜尋 index 跟圖都只是快取。箭頭只往一個方向——快取從檔案重建,檔案不會從快取長回來——所以我隨時可以把任一個快取砍掉重建,不會掉任何東西。
- 不被綁死。 它就是資料夾裡的純 markdown。換機器、換工具,只要幾行指令就能從檔案把 index 跟圖重建回來。
- 每一層都得先證明自己非有不可。 我不是一開始就規劃三層。我先有一個資料夾,撞牆了才加搜尋;又撞牆了才加圖。一層如果沒在回答下面那層答不了的問題,它就不該存在。
架構到這裡就講完了。只想直接套用的,文末那份五行 checklist 就是全部;想看我怎麼踩坑的,再往下讀。
我實際上怎麼走到這裡——那些走錯的路
底下是我走到這套設計之前試過的失敗版本,以及每次失敗教會我的事。這些都不是「用這套系統」的必要知識——只想要設計的,直接跳到收穫。
這張圖一個下午變成垃圾兩次,才終於能用
你以為難的地方是『把圖建出來』。其實真正難的是能不能『快點把它建壞』——你得先多踩幾次雷,才知道一條有用的 edge 該長什麼樣。我一個下午做了三版,而每一版的差別其實只有一件事:我到底『憑什麼說這兩篇筆記該連一條線』。
v1 我用 embedding 相似度來連 edge——embedding 就是把一段文字壓成一串數字,兩串數字越接近,兩篇就越像。結果是一坨 hairball(所有點全糊在一起、看不出結構)。我那些 .py、.swift、.tsx 檔把整張圖洗版了——兩個 Python 檔當純文字看天生就像,但這不代表它們在知識上相關。__init__.py 這種檔直接變成一個 hub。我得把相似度 threshold 一路拉到 0.90,才勉強看得到一點結構——光這一點就在說:embedding 距離,是一個很爛的 edge 指標。602 個 node、142K 條 edge、零訊號。
v2 我只留 markdown、edge 改用共享概念來連:乾淨多了,但這下換 102 篇筆記(35%)變成孤島,因為我的概念字典太窄,接不住它們在講什麼。
v3 我把概念字典擴成三倍、再加上從檔案路徑推出來的概念,並且讓 embedding 只在概念完全接不到時當 fallback:這才總算做對——零孤島、大約 7000 條 edge、五個真的社群。而且這五個社群告訴我一件我的資料夾從來沒告訴我的事:我的 collection 分類,跟知識實際的結構對不上。一批我自己歸在一起的交易筆記,散在三個不同的社群裡。還有一個概念 api,在當時那 291 篇裡就佔了 172 篇——它泛到變成一個 god concept(幾乎每篇都有,多到根本分不出誰跟誰),把所有東西連到所有東西,跟 NLP 裡的 stopword(像『的、是、在』這種到處都有、可以直接略過的字)一模一樣。你得先把它丟掉,剩下的連結才有意義。

修好這張圖的,不是一個更聰明的演算法,是一個更好的定義:什麼才算一條 edge。
稽核說我 82% 搜尋都在失敗,其實在說謊
一開始,我打開自己記憶系統的儀表板,看到一個數字:82% 的查詢『低分』。直覺——至少我的直覺——馬上跳到一個結論:知識庫破了一堆洞,我該去把缺的筆記補上。
錯了,而且錯的方式值得記下來。拿一句長長的自然語言去跑 keyword 計分,本來就『必然』撈不太到東西:你問的是一整句話,它比對的只是 token。同一批被判低分的 query,丟去 vector search 分數就打到 0.7 上下。真的一篇一篇去查,只有 2 篇是真的缺。那 82% 不是知識缺口,是我量到『自己在用哪一種檢索方法』的假象。(修法現在是一條硬規則:keyword 空手、或 query 超過大約四個詞,就先自動用 vector 重試。)看到一個檢索指標之前,先確認它量的是方法本身、還是資料本身。
我做了一個更好的工具,結果自己都不用它
這是最讓我火大的一段。圖做完之後,碰到『這跟什麼相關』這種問題,它明顯比純搜尋好用。然後我發現,我在 Claude Code 裡還是一直伸手去拿那個舊的搜尋框,新做的這個幾乎碰都沒碰。
原因跟品質一點關係都沒有。qmd 對 agent 來說是一個 first-class tool——它列在 tool schema(agent 每次開場就會讀的那份『我有哪些工具可用』清單)裡,看得到。musubi 呢,『只是一個 CLI』。agent 會伸手去拿的,是它 schema 裡列出來的那些工具,不是我的 shell history——介面沒告訴它有這條命令,它就當這條命令不存在。工具更有用,卻更不可見;而每一次,agent 伸手拿的都是『看得見』的那個。修法大概 150 行:把 musubi 掛進去,包成一個 MCP server(把外部工具掛進 agent 工具清單的標準介面),這樣 agent 就直接看到它是真的工具。通則是:替一個 agent 做東西,最後一步不是把它做好,是把它做到『看得見』。
圖長成一坨 hairball,我畫的那張索引反而把答案洗版了
每天寫筆記寫了幾個月之後,這張圖涵蓋了我所有 collection,規模衝到大約 1272 篇、二十萬條以上的 edge、平均 degree 好幾百。又是一坨 hairball——但這次是反過來的:連結太多,不是太少。
那個具體的失敗,講出來幾乎有點好笑。我寫過一份 orientation 檔——一份手動維護的索引,用來幫 agent 在知識庫裡認路。這份檔變成了整張圖最大的 hub,degree 破 1100,因為它引用了所有東西。於是我搜某一篇特定筆記時,回傳結果開始把『那份索引』排在筆記本身前面:graph boost 會幫命中筆記旁邊的鄰居加分,結果它硬是把這份 meta-doc(描述知識庫本身的檔,不是知識本身)抬了上去,壓在它本來該指向的那個答案上面。修法就是把排序規則收緊:直接命中的排在圖鄰居前面、meta-doc 沒被指名就降權、鄰居的 boost 設上限、鏡像重複的合併成單一 canonical(1272 篇 raw 併成 993 篇)。這次踩的坑如果你讀過 Part 11 會很眼熟——系統對自己的描述,慢慢跟它實際在做的事對不上——只是這次發生在更底層。一張蓋在知識上面的圖,跟知識本身一樣需要治理。
收穫
最花時間的地方
不是把圖建出來,是把它建錯兩次。整個第一個下午,說穿了只換來一個發現:embedding 相似度,對一個程式碼跟散文混在一起的 corpus 來說,是一個很爛的 edge 指標——因為程式碼不管在講什麼,當純文字看都自我相似。真正有效的訊號,是概念的共現,再把太常見的概念降權。那個東西其實就是 IDF(越稀有的概念,鑑別力越強),只是我用硬撞的方式撞出來的。
可以搬走的診斷
當一個指標看起來像知識缺口(82% 低分),先確認它是不是方法本身造成的假象,再決定要不要動手。把你的 god concept 當 stopword 處理——任何一個出現在過半筆記裡的概念,是連結的噪音,不是訊號。記得包成 MCP server:agent 在 schema 裡看不到的工具,再好也不會被用。盯著你的 meta-doc:一份『蓋在圖上面的索引』,會悄悄長成圖裡最吵的 hub,把真正的答案蓋掉。
通則
這些層是疊加的,不是彼此取代。檔案是真相源;搜尋和圖是疊在上面、可以隨時重建的快取,箭頭只往上。而且每一層,都是被下面那層撞牆逼出來的:先要能存、再要能找、最後要能連。還有第三道牆在等著:就算檢索做到再好,它撈回來的還是『原始材料』,你每一次都得重新推它到底在講什麼——這件事本身就有成本。不過那是下一篇的事了。
如果你要給 AI agent 一個 markdown 記憶,這是我試出來的順序
- 檔案優先。 一個檔講一件事、純文字。這是唯一『是真相源』的那一層,其他全部從它重建。
- 加搜尋,而且知道它的天花板在哪。 keyword 找精確的字、vector 找意思。query 是一整句時,自動 fallback 到 vector——而且不要去信一個用 keyword 算出來的『覆蓋率』數字。
- 需要 traverse、不只是 lookup 的時候,才加圖。 edge 用共享概念、不要用 embedding 相似度,然後把那些出現在所有東西裡的概念 stopword 掉。
- 讓它看得見。 如果你的使用者是一個 agent,工具沒變成 MCP tool 就還沒算做完。
- 治理那張圖。 直接命中排在鄰居前面、你自己畫的那些地圖降權、鏡像去重——不然你自己的索引會把答案淹掉。
在自己的筆記上試試
負責知識圖譜的就是 musubi——開源、MIT、在本地跑,建圖不需要任何 AI 服務(全是 deterministic 的概念比對,不用 LLM)。裝起來、指到一個 markdown 資料夾就行:
# 裝一次,之後就能對自己的筆記跑
uv tool install git+https://github.com/coolthor/musubi
musubi init # 互動式——可以先玩內建 demo,或直接指到你自己的筆記
musubi build --source ~/your-notes
musubi neighbors "docker" # 換成你筆記裡的某個關鍵字或標題
它還有 musubi benchmark,省多少 token 你可以在自己的 corpus 上量,不用信我的數字;以及 musubi mcp——一個 stdio server,註冊進 Claude Code 之後,agent 就會把 musubi 當 first-class tool(這正好補上前面 discoverability 那個洞)。搜尋那一半是 qmd。repo 在這:github.com/coolthor/musubi(覺得有用就給顆星)。
下一篇:為什麼『搜尋得到你的筆記』還是不夠——以及我疊在這兩者之上的那一個蒸餾層。
常見問題
- 知識圖譜用白話講是什麼?
- 把每篇筆記當成一個點,兩篇只要講到同一個概念就牽一條線,連的是『概念』不是『用詞』。連完之後你會得到一張地圖:哪些筆記在講相關的事、哪些是孤立的、哪些太熱門。這讓你問得出搜尋框答不了的問題——這篇筆記,還連到哪些我早忘掉的東西?
- AI 記憶只靠 keyword 搜尋,為什麼不夠?
- 因為 keyword 只找『用同樣字詞』的筆記。同一件事,一篇寫『模型佔著記憶體不放』、另一篇寫『服務一開就崩』,搜其中一篇,另一篇永遠不會浮出來——明明在講同一回事,只因為用詞不一樣就湊不到。這是『連得起來』的問題,不是『找得到』的問題,搜尋解不了,要靠圖。
- 要做這件事,是不是得把筆記全搬進 Obsidian?
- 不用,我到現在還慶幸沒搬。markdown 檔留在原地當唯一的真相源,圖是疊在上面、永遠不去改那些檔的一層。檔案優先、圖只是一個隨時可以重建的衍生物——就是這一刀切得乾淨,後面每一步才都保持可逆。
- 把檔案、搜尋、圖分成三層,而不是做成一個系統,到底有什麼好處?
- 每一層回答下一層答不了的問題:檔案負責存、搜尋負責找、圖負責連。分開的好處是檔案永遠是唯一的真相源,搜尋 index 跟圖都只是可重建的快取——你可以把任一個丟掉再重建,而且沒有任何東西被綁死在某個工具或某台機器上。
接著讀
- 2026-07-15[Dev Workflow] 搜尋找得到,卻要每個 session 重推一遍:幫 AI 記憶加一層蒸餾層
我拿一批 markdown 檔當 AI 的長期記憶,在檢索(搜尋加知識圖譜)之上再疊一層蒸餾層。檢索撈回的是原始材料,每個 session、每個 agent 都得重讀重推一遍;蒸餾層把定案結論冶煉一次、存成一檔一個 claim,之後直接取用。這篇講清楚這層是什麼、為什麼它的目的是『少解釋一次』而不是『逼大家講一樣』、以及我怎麼學會真的去信它。
- 2026-07-17[Dev Workflow] 幫 AI agent 的 skill 減肥:193 個塞爆 2% 預算,砍到剩 7 個
AI Workflow 系列第 15 篇。某天早上我讀 codex 紀錄,滑到一行:skill 描述被剪短,才塞得進 2% 的 context 預算。我共用的 root 累到 193 個 skill,全載進去撐爆上限,每條描述都被截斷——我天天在用的,被剪掉是為了替我從不碰的一百多個騰位子。這篇講怎麼用 per-agent allowlist(不是刪)砍到 7 個、薄殼配深引擎壓低每個 skill 的載入成本,再用一個閘門加稽核加可逆退場區守住,不讓它肥回去。
- 2026-07-16[Dev Workflow] 一群 AI 交接工作卻不用重講:原來是同一套系統的兩條軸
這是 AI Workflow 系列裡『兩軸合流』這一段的收尾。回頭看整段路,前面六篇其實在蓋同一套系統的兩條軸:持久知識(我知道什麼,長效、要用再查)和即時狀態(我現在做到哪,易逝、隨時寫)。這篇講清楚兩條軸為什麼互不代替、怎麼匯流到『多 AI 交接』、又受同一套哲學管——以及那條讓一群不完全信任的模型還能安全協作的中心律:沒有收據就不信。
- 2026-04-13Claude Code Token 燒太快?8 招讓你的 Session 撐 10 倍久
剛開始用 Claude Code,context window 一直滿。這篇解釋 token 花去哪、哪些浪費了、怎麼讓 Claude 更精準地找資料而不是暴力讀檔。
不想錯過新文章?
訂閱我確保不漏接!
隨時一鍵退訂。