AI 工程

CogniKernel 拆解:把 Claude Code 的記憶做成「分類問題」,跨 session 跨 Codex 共用一份 SQLite

昨天你跟 Claude Code 討論了半小時,最後拍板:「rate limit 用 Redis,不能用 in-memory,因為要跨 pod 共享計數。」今天早上開新 session,同一個 repo,你叫它加一個 endpoint——它熟練地寫了一個 in-memory token bucket。

你有 CLAUDE.md。但 CLAUDE.md 是手寫的,而昨天你忙著吵架,沒空更新它。

CogniKernel 想補的就是這個洞:掛在 Claude Code 的 hook 上,session 結束時把「決策、硬性限制、被否決的方向」抽出來存進本機 SQLite,下次 session 開頭自動注回去。作者是 Kanishk Singh(GitHub 帳號 KanishkNoir),2026-07-23 上 PyPI,版號 0.1.0,Apache-2.0,repo 建立於 2026-05-23。

先把期待值調準:GitHub 星數目前還是個位數、PyPI 上標的是 Development Status 4 – Beta、benchmark 文件裡作者自己寫「directional, not publication-grade」。這不是你今天該推給全公司的基礎設施。但它的幾個設計選擇——特別是「抽記憶不呼叫 LLM」——非常值得任何在做 agent memory 的人抄走。

一、它跟 CLAUDE.md 到底差在哪

CLAUDE.md 的問題不是「不好用」,是三個結構性缺陷:

1. 要人寫。 你得記得寫、記得改。決策發生在對話裡,寫入發生在你的意志力裡,兩者常常對不上。

2. 沒有取代語意。 CLAUDE.md 裡如果同時躺著「用 Postgres」和三週後補的「改用 SQLite 做本機測試」,agent 看到的是兩條並存的指令,不知道哪條比較新。矛盾會累積。

3. 沒有墓園。 你試過但失敗的路——「asyncio.subprocess 跑 ffmpeg 在 Windows 會卡死」——通常不會被寫進 CLAUDE.md,於是 agent 每個月都重新發明一次那個 bug。

CogniKernel 用四種 typed memory 直接對應這三件事:DECISION(決策)、CONSTRAINT_HARD / CONSTRAINT_SOFT(依語氣強度分的規則)、APPROACH_ABANDONED_DO_NOT_RETRY(墓園)。每筆決策有 decision key,新的同 key 事實會取代舊的(latest-wins),矛盾不會疊加。

二、15 分鐘裝起來

需求:Python ≥ 3.11。

pipx install "cognikernel[embedding]"
# 或 uv tool install "cognikernel[embedding]"
# 純 lexical 版:pip install cognikernel

cd /path/to/your/repo
cognikernel init .
cognikernel install-heads    # 一次性下載模型權重,README 標約 270 MB,sha256 驗證
cognikernel doctor .

install-heads 不是必要的——沒有權重時系統會退回關鍵字比對的 fallback。但兩顆 encoder 才是它跟「grep CLAUDE.md」的差距所在,建議裝。(README 表格寫兩顆模型「130 MB each」,PyPI 描述寫「130 MB 合計」,install-heads 又標 ~270 MB;三處數字彼此不一致,我沒實跑無法確認,你就當作要留 ~300 MB 磁碟。)

動手前先看一眼 init 會改哪些檔案。src/cognikernel/integration/cli.py 讀到的,它會寫:

  • .claude/settings.json——九個 hook entry(下一節細講)
  • .mcp.json——把 python -m cognikernel mcp-serve 註冊成 stdio MCP server
  • .codex/config.toml——Codex 那邊的同一個 MCP server,帶 COGNIKERNEL_PROJECT_PATH 環境變數
  • AGENTS.md——叫 Codex 開場先跑 codex-sync,並把 get_session_state 當成專案決策的唯一真相來源
  • .cognikernel/config.toml——專案級設定

這是五個檔案的侵入性改動,其中三個很可能已經在你的版控裡。先 git status 確認乾淨再跑,跑完再 diff 一次。記憶資料庫本身是本機狀態(config.pycognikernel_dir 預設 ~/.cognikernel),我的建議是把 .cognikernel/ 加進 .gitignore、但保留 config.toml——repo 沒有明文規範這件事,這是我的判斷。

三、運作原理:一條寫入路徑、一條讀取路徑

CogniKernel 記憶迴圈:四個 hook 掛載點、寫入路徑與讀取路徑

寫入:把 session 當成分類問題,不是生成問題

這是整個專案最關鍵的一個賭注。市面上大多數 agent memory 工具的做法是:把 transcript 丟給一顆 LLM,叫它「總結重點」。這代表每個 session 結束都要付一次 API 錢、等一次延遲,而且結果不可重現。

CogniKernel 的說法是「classification, not generation」。Stop hook 觸發後,pipeline 走 sanitize → 分類 → 打分 → 產生 decision key:

  • salience_v2:SetFit 分類器、bge-small backbone、ONNX 格式、CPU 上跑毫秒級。把每個句子分成 DECISION / CONSTRAINT_HARD / CONSTRAINT_SOFT / APPROACH_ABANDONED_DO_NOT_RETRY / noise。
  • supersession_xenc:cross-encoder,判斷新事實是「取代」還是「重述」既有事實。這是純字面比對做不到的——「改用 SQLite」和「Postgres 那條先不做了」字面上零重疊,語意上是同一件事。

沒有 LLM in the loop 換來三件事:零 API 成本、可離線、可重放(cognikernel rebuild --from-raw 能從原始事件重建整個記憶狀態)。

儲存:event-sourced SQLite

WAL 模式 + FTS5 全文檢索,schema 目前 v18。它是 append-only 的事件流,不是可變狀態表——「golden record」是讀取時才 latest-wins 收斂出來的。這代表你隨時可以回溯「這條限制是什麼時候、哪個 session 進來的」。

搭配的可靠性設計包括:每個編號 migration 的 body 跟版號寫在同一個 transaction 裡(原子性)、worker job 冪等重放(不會重複計數)。

讀取:BM25 為主、dense 為輔

檢索是 FTS5 BM25 ∪ 選配的 dense embedding,用 Reciprocal Rank Fusion 融合。詞彙檢索當主力這件事在 agent memory 裡算是反潮流的選擇,但對「這個 repo 的專有名詞」這種查詢其實很合理——你的 service 名稱不會有好的 embedding。

有兩個細節值得抄:

  • prohibition_search:「不要做 X」這類規則會被丟進一個 type-specific 的檢索池單獨排名,避免在通用排序裡被大量 DECISION 擠掉。禁令一旦被擠掉就等於不存在,這個問題必須專門處理。
  • AST skeleton + PageRank:用 tree-sitter 建符號圖,用 PageRank 排 import graph 的鄰居,餵給 MCP 的 find_related

注入:注進去的 block 長什麼樣

src/cognikernel/injection/template.py 讀到,SessionStart 注進去的是這樣一塊 markdown:

## Session context [auto-generated — do not edit]
project: {name} · session {N} of {total} · state v{version}

### Hard constraints — never violate
- {description} — {rationale}

### Active thread
Working on: … / Current state: … / Next: …

### Do not retry — confirmed failures
- {approach} -> {reason}

### Key decisions
1. {description} — {rationale} (session {id})

### Component state
- {path} · {STATUS} — {intent}

空的 section 直接省略。注意一個很聰明的細節:hard constraints 和 graveyard 這兩段是按 content_hash 排序的,不是按時間。 理由是 prompt cache——如果順序每次都變,prefix cache 每次都會 miss。這招跟 CogniKernel 本身無關,任何人自己組 system context block 都該照做:section 內排序一定要 deterministic。

四個 hook 掛載點(實際是九個 entry)

README 的表格列四個面:SessionStart(注入)、UserPromptSubmit(CK-1 召回)、PreToolUse(禁令即時提醒)、Stop(抽取)。但 cli.py 實際寫進 .claude/settings.json 的有九筆,多出來的是 PostToolUse 掛在 Write / Edit / Read / Grep 四個 matcher,以及 SubagentStop。Stop hook 的 timeout 設 300 秒。

這是一筆你要算清楚的常駐成本:agent 每讀一次檔、每 grep 一次,都會多跑一個 Python hook process。hooks.py 裡有 _HOOK_TIMEOUT_S = 3.0 的預算上限,而且用的是 daemon thread(逾時直接丟掉、不 join),所以最壞情況是「慢 3 秒」而不是「卡死」。但在一個 agent 動輒讀四五十個檔的 session 裡,這個常數會累積。

關於 PreToolUse 的行為,這裡有個我沒能完全確認的地方要標出來:hooks.py 的實作看起來是一律 allow,只在命中禁令時附上 advisory 的 additionalContext;但 README 的表格把 PreToolUse 的 authority 標成 hard/JIT。兩處說法不一致,我沒實跑,保守假設是「提醒而非硬擋」。

所有 hook 都是 fail-open:例外被吞掉、記成 WARNING log,session 照跑。程式碼裡的契約是「hook 絕不能擋住 Claude」,同時「silence never reads as success」——出事了 log 裡一定 grep 得到 traceback。

四、三個你裝完一定要改的設定

init 只寫四行進 .cognikernel/config.toml

hook_policy = "strict"
extractor = "v2-broad"
cross_encoder_supersession = true
query_time_injection = true

config.py 裡的程式預設值跟這四行不一樣(分別是 advisory / legacy / false / false)。也就是說:沒被 init 寫進來的鍵,吃的是保守的程式預設。 這裡至少有三個地方值得你手動補。

1. embedding_enabled 預設是 false

pip install "cognikernel[embedding]" 裝了 fastembed,不代表 dense retrieval 開了。它不在 init 寫的那四行裡,而 config.py 的預設是關的。想要真正的 RRF 混合檢索,自己加:

embedding_enabled = true

2. token_budget = 3500 是每個 session 的固定開銷。

配上 section 級預算:hard_constraints = 150decisions = 150graveyard = 120active_thread = 80components = 80hot_files = 50summary = 40skeleton = 800。(頂層還有一個 skeleton_budget = 600 跟 section 的 800 對不上,實際以哪個為準要看程式流,我沒實跑不敢斷言。)

小專案把 token_budget 調到 2000 以下、大專案往上加,都是合理的第一次調整。決定原則很簡單:注入的 block 只要沒讓 agent 少讀檔,它就是純虧損。

3. CK-1 召回太吵時的旋鈕。

UserPromptSubmit 的召回是雙證據 gating,相關參數:query_injection_threshold = 0.75query_injection_max_tokens = 200ck1_max_events = 2ck1_min_term_overlap = 3ck1_dual_anchor_terms = 2ck1_bm25_rank_max = 5ck1_dense_rank_max = 5

覺得它每句話都在插嘴?把 threshold 拉到 0.85、ck1_max_events 降到 1。覺得它該提醒的時候反而安靜?把 ck1_min_term_overlap 降到 2。

PreToolUse 那邊也有對應的一組:pretool_pool_size = 12pretool_max_surface = 1pretool_min_term_overlap = 3pretool_bm25_rank_max = 1——預設一次最多只浮出一條禁令,很克制。

另一個實用但容易漏掉的鍵是 project_identity:如果同一個 repo 有多個 checkout(WSL 的 /mnt/c/repo 和 Windows 的 C:\repo、或多個 git worktree),用它把這些路徑釘到同一份記憶。

五、怎麼講話,它才記得住

因為抽取端是分類器不是 LLM,你的措辭直接決定它記不記得住。分類器認的是句型和語氣強度,不是你的言外之意。

模板格式是 - {description} — {rationale},也就是說理由是有欄位的。講決策時把理由一起講,抽出來的記憶才完整。

會被記住的句型:

決定:rate limit 用 Redis,因為要跨 pod 共享計數。
硬性限制:這個 repo 一律不引入 pydantic v1。
這條路不要再試:用 asyncio.subprocess 跑 ffmpeg 在 Windows 會卡死。

大概率被判成 noise 的句型:

嗯我覺得 Redis 可能比較好吧?
之前好像有試過那個

具體招式:在 session 結束前,花 30 秒把當天結論用「決定 / 一律 / 不要再試 + 理由」的句型重講一次。 這比事後補 CLAUDE.md 便宜太多,而且 Stop hook 會直接吃下去。

六、數據:少讀檔是真的,但別只看這一欄

四個測試專案的讀檔次數對比,以及 benchmark 自陳的限制

docs/benchmark.md 做的是三組對照:CogniKernel 完整版、手寫 CONTEXT.md 或原生 auto-memory、完全無記憶。四個專案,每個 3–5 個 session。

讀檔次數(越少越好):

專案 CogniKernel 手寫筆記 無記憶
Taskflow(3 sessions,小型可重讀基準) 3 29 14
Relay(5,決策演化 / 18 條易混事實) 23 63 0†
Toolbelt(5,跨套件 API 契約) 16 47 53
Conductor(5,12 條可靠性不變式) 40 89 83

† Relay 的無記憶組正確率是 0%,讀檔為零是崩潰不是效率。

正確率(產出程式碼有忠實實作被要求的決策的比例):

專案 CogniKernel 手寫筆記 無記憶
Relay 92 70 0
Toolbelt 97(無提示 90) 95(無提示 ~10) 98
Taskflow 96 97 95
Conductor 98 100 89

看清楚:在小專案和實作導向的專案上,CogniKernel 沒有贏。 Taskflow 和 Conductor 的正確率被手寫筆記小輸,token 也只是「大致打平、偶爾更貴」。它的優勢集中在 Relay(決策演化,18 條易混事實、3 條 supersession 鏈)和 Toolbelt(跨套件 API 契約、22 個檔的 breaking change)這種「狀態大、會變、活得久」的場景。

token 的部分作者處理得很誠實,值得原樣轉述:

  • Relay / Toolbelt 大約少 30–40% 原始 token
  • Taskflow / Conductor「大致打平或偶爾更貴」
  • 加權計價後優勢會縮水:因為約 95% 的 token 是 0.1× 計價的 cache-read(作者用的權重是 cache-read 0.1×、input 1×、cache-write 1.25×、output 5×)。原始 token 省 35%,實際帳單省的遠低於此。

benchmark 自己列的限制也不含糊:單一評測者、每個對照格約 2 次重複、每專案只有 3–5 個 session(長時間衰減根本沒測到)、全部是 Python 後端、單一模型家族。而且是混世代數據——正確率是 2026 年 7 月用當前 stack 重測的,讀檔數和原始 token 數沿用較早一輪、不同的 agent 模型。評分過的 transcript 和專案 fixture 都沒公開,所以不是 turn-key 可重現。

最該注意的一行藏在 caveat 裡:「corruption is followed」——記憶被污染時,agent 大約 83% 的時候會照著錯的記憶做。 這不是 CogniKernel 獨有的問題,是所有自動記憶系統的共同風險,但它意味著你需要一套清理流程。

七、記憶髒了怎麼救

這幾個 CLI 是之後你真正會天天用的:

cognikernel show . --json                   # 現在到底記了什麼
cognikernel doctor . --strict               # 子系統降級就 non-zero exit,可掛進 CI
cognikernel failures . --limit 10           # 看失敗的 job
cognikernel failures . --replay JOB_ID      # 重放單一 job
cognikernel rebuild . --from-raw --dry-run  # 從原始事件重建,先空跑
cognikernel reset . --yes                   # 全清重來

我的建議是把「每週跑一次 show --json、掃一遍 hard constraints 和 graveyard」變成習慣。這兩段是唯一會被當成強制指令的內容,錯一條的代價遠大於其他段落。

八、Codex 那半邊怎麼接

跨平台是這個專案的賣點之一,但兩邊能力不對等,講清楚比較實在。

儲存層是平台中立的:一個邏輯專案一份 SQLite,路徑解析是 alias-aware 的,C:\repo/mnt/c/repo 會落到同一份。

Codex 端是拉取式,不是 hook 式:

cognikernel codex-sync . --scan-days 30

它掃 ~/.codex/sessionscodex_home 可設定、codex_scan_window_days 預設 30),把 delta 走同一條抽取 pipeline。反向的自動交接是有的——Claude 的 SessionStart 會先把待處理的 Codex rollout 排乾,再組記憶 block。

但 action-point 的兩個介面(UserPromptSubmit 的 CK-1 召回、PreToolUse 的禁令提醒)是 Claude Code 專屬,Codex 那邊降級成「共享 block + 手動呼叫 MCP recall」。所以實務上:在 Claude Code 裡做決策、在 Codex 裡執行,記憶流向是順的;反過來就要記得手動 codex-sync

MCP 工具四個:recall(定向查記憶)、find_related(語意 + import graph 鄰接)、skeleton(AST 符號圖)、get_session_state(當前記憶狀態)。

九、什麼時候別用

  • 短命專案、一次性 script:benchmark 上 Taskflow 這類專案基本打平,你付的是安裝成本和 hook 常駐成本。
  • 決策已經穩定、CLAUDE.md 寫得好的專案:注入 3500 token 換不到少讀檔,就是純虧。
  • 要團隊共享記憶:目前是本機 SQLite,沒有同步機制。你的記憶不會變成隊友的記憶。
  • 非 Python / 非後端專案tree-sitter-language-pack 支援多語言,但 benchmark 全是 Python 後端,其他語言等於沒有證據。
  • .claude/settings.json 有嚴格控管的團隊:九個 hook entry 是不小的表面積,其中 PostToolUse 掛在 ReadGrep 上,等於每次檔案操作都多一個 process。
  • 任何 production-critical 的流程:0.1.0、beta、單一維護者、個位數星數。當實驗跑,別當基礎設施。

十、就算你不裝,這四招可以直接抄

1. 記憶抽取當分類問題做。 如果你在自己刻 agent memory,先問一句:這一步真的需要生成嗎?分類器有成本、延遲、可重現性三重優勢,而且錯了容易 debug。這是 CogniKernel 最值得學的一件事。

2. section 內部用內容雜湊排序。 任何你組進 system prompt 的動態 block,排序一定要 deterministic,否則 prompt cache 每次 miss。這是零成本的改動。

3. 禁令要有自己的檢索池。 「不要做 X」在通用相關度排序裡永遠會輸給「要做 Y」,因為正向敘述更多、更長、詞更豐富。給它一個獨立的池子。

4. 三段式 CLAUDE.md。 就算完全不裝 CogniKernel,把 CLAUDE.md 手動拆成這三段,效果就有一半:

## Hard constraints — never violate
- 一律不引入 pydantic v1 — 相依樹跟 fastapi 0.11x 衝突

## Key decisions
1. rate limit 用 Redis — 需跨 pod 共享計數(2026-07-15)

## Do not retry — confirmed failures
- asyncio.subprocess 跑 ffmpeg -> Windows 上會卡死在 pipe

這三個標題就是 CogniKernel 注進去的東西。差別只在於它幫你自動寫、自動去重、自動處理取代。而在你確認自動化真的可靠之前,手動版本反而是更安全的起點。

來源

本文所有設定值、CLI 參數與 benchmark 數字皆讀自上述 repo 的 main 分支。我沒有實際在專案上跑過完整的多 session 流程,所有數據性結論都來自作者的 benchmark 文件,並已標註其自陳限制。

整理:DataAgent · Coding Agent 實戰教學

發表迴響

%d 位部落客按了讚: