# AI Agent Memory Stack Everyone Must Use in 2026 (Builder's Guide)
**作者**: Avid
**日期**: 2026-04-20T19:23:58.000Z
**来源**: [https://x.com/Av1dlive/status/2048800691943309698](https://x.com/Av1dlive/status/2048800691943309698)
---

## here is the truth nobody tells ai builders.
you do not need to build your own model
## all you need to build is
memory layer that lives underneath all of them.
> TLDR; if you don't wanna read it, give this link to your agent and ask it questions: ➡️ github.com/codejunkie99/brain
## The thesis: memory should be portable
A store that's the same in Claude Code or Cursor. An index that survives a session reset, a model deprecation, a tool switch. A protocol that lets every harness talk to the same brain.
Harrison Chase nailed this in three lines on April 21:
> **Harrison Chase@hwchase17**: [原文链接](https://x.com/hwchase17/status/2046308913939919232)
>
> memory will be the great lock in
> and everyone knows it, so are rushing to get there first
> memory should be open!
>
> 
Taranjeet from Mem0 sharpened it on April 24:
> **Taranjeet@taranjeetio**: [原文链接](https://x.com/taranjeetio/status/2047788130623971410)
>
> Chamath is right: being locked into one foundation model is quickly becoming a bottleneck for enterprises.
> But there’s another lock-in forming one layer deeper: memory lock-in.
> It’s not just models that need to be swappable. The context that makes agents useful needs to be
I agree with both. I just pushed it one step further.
Memory shouldn't just be open. It shouldn't just be portable. It should be typed, indexed, audited, synced, and safe. The agent deserves more than a markdown blob and a vector store you can't inspect.
I spent a a little over month and half building exactly that:
- A git-backed event log. One commit per note.
- A SQLite FTS5 derived index with BM25 ranking and prefix matching.
- An MCP stdio server that exposes five tools to any compatible agent.
- A CLI and a full-screen TUI for the human in the loop.
This article is how I built it and why every piece exists.
If you just want to install it: github.com/codejunkie99/brain. Works with Claude Code, Cursor, Codex, OpenClaw, Hermes or any tool that speaks MCP or shell.
If you want to understand what you're installing, keep reading. ⬇️
## The Shape of the Project
Seven Rust crates. Five adapter directories. ~9,000 lines of code. 143 regression tests. One binary on your path.
I didn't design this from scratch. I converged on it after trying layouts that didn't work.
The key insight, the thing Harrison's post crystallized for me, is the separation:
- Git is the source of truth.
- SQLite is a disposable index rebuilt from git on demand.
- Orchestration sits above both and handles the two-phase write.
- Adapters are thin glue so each harness calls into the same shared brain.
Which means:
- Swap the harness tomorrow → lose nothing.
- Swap the model tomorrow → lose nothing.
- Lose the index entirely → rebuild from git in seconds.
The only thing that accumulates value is the git log. And that's a folder of plain JSON files.
## Why git is the right store
Every alternative had a fatal flaw:
- SQLite-only. Fast, but now you're building your own replication, conflict resolution, and audit trail. You've rebuilt half of git, badly.
- Markdown files. Good for humans, bad for agents. Parsing is slow. Updates are racy. No natural unit of write.
- DuckDB or PostgreSQL. Overkill for single-user memory. Doesn't help you sync between laptops.
- Git. One commit per event, one file per event. Free audit log. Free sync via push/pull. Free merge semantics. Free diff visualization. Free backup via any git host.
The unit of write is a commit, which is atomic. Every event has a permanent OID that never changes.
Here's what an append looks like in brain-store/src/repo.rs:
The retry loop with exponential backoff and the idempotency-key pre-check make this durable under concurrent writers. A naive "just commit" version dies the first time two agents write at the same millisecond.
## Why SQLite FTS5 for retrieval
Git is the truth, but git is slow to search. git log -S "authlib" works, but it takes seconds on a 10,000-event repo because it re-diffs every commit.
So I added a derived index. The brain-index crate keeps a SQLite FTS5 index with three weighted columns:
- Title: 10x weight
- Body: baseline
- Tags: 5x weight
BM25 ranks the result. On write: an INSERT OR REPLACE. On search: top-5 hits in under a millisecond at 100,000 events.
The two tables that matter (in brain-index/src/schema.rs):
Critical detail: the index is derived. If it gets corrupted, the schema bumps, or you delete it on purpose, brain doctor --deep rebuilds it from git in one pass.
Git history is the only truth that matters. The index is a cache.
I tried embeddings first. Slower. More complex. Required a model dependency. For a single-user system with under 100,000 events, BM25 on FTS5 produced indistinguishable retrieval quality.
The simple weighted formula won.
## The 10 event kinds
Memory isn't one thing. A preference isn't a lesson. A claim isn't a fact. They need different retrieval rules, different visibility semantics, different update policies.
Each variant has its own payload struct:
- Claims carry schema_type + key + content + supersedes for chain-aware retrieval.
- Prefs have category + key + value.
- Redacts carry a reason and trigger projection rollback.
The typing is the point. current_pref("auth", "provider") returns the current value as a SQL lookup against a materialized projection — not a string match on a markdown blob.
Typed memory is queryable memory.
Every event also carries metadata: event_id (UUIDv7, sortable by time), actor.kind + actor.id + actor.harness, chain_id, time_observed, time_recorded, layer, authority, signature_state, classification, schema_version, idempotency_key.
The actor.harness field is what makes cross-tool attribution work. A note Claude Code wrote is indistinguishable from a Cursor note at the content level. The harness field tells you which tool produced it.
## Redact rolls back, doesn't erase
When you redact the current Pref tip, the naive thing is to delete the projection row. That leaves current_pref empty — meaning the user lost ALL prior values too.
The correct behavior is rollback. Redact Pref B → projection restores Pref A, the predecessor. Like git revert for memory.
In brain-index/src/ingest.rs:
The ORDER BY time_recorded DESC, event_id DESC clause is there because same-millisecond inserts were picking predecessors non-deterministically. UUIDv7's random tail breaks ties.
## The secret prefilter
Every serialized event gets scanned through a RegexSet before it touches git.
- 18 patterns
- NFKC Unicode normalization to defeat fullwidth lookalikes
- Zero-width stripping to defeat smuggled ZWSP characters
In brain-store/src/secrets.rs:
The scan runs in three passes:
1. Scan raw input.
2. Strip zero-width characters and rescan — blocks s\u{200B}k-ant-... smuggling.
3. NFKC-normalize (folding fullwidth ASCII and mathematical alphanumerics to plain ASCII), strip zero-width again, rescan.
The pem-private-key pattern is a backstop.
- A GCP service-account blob pasted into an Observe.content field gets JSON-escaped: "type":"service_account" becomes \"type\":\"service_account\", breaking the structured gcp-service-account pattern.
- But -----BEGIN PRIVATE KEY----- has no quotes, survives JSON escaping unchanged, and the backstop catches it.
I learned this on codex review pass 11.
The commit subject gets scrubbed too. For Observe, Lesson, and Redact (the three free-form variants), the subject uses the event_id instead of echoing user text.
Otherwise a secret that snuck past the prefilter would get promoted to git log --oneline.
## Forgery defenses I didn't know I needed
Commit-level tampering is harder to defend against than I thought. Three cheap defenses, all in one function called event_from_commit:
- Trailer-block parsing. The event_id: trailer is only parsed from the last paragraph of the commit message, after the final blank line. A user note ending with \n\nevent_id: <forged> cannot smuggle a trailer.
- Blob-trailer cross-check. The blob's internal event_id field must match the trailer's event_id and the filename. Prevents "trailer points at the wrong blob" forgery.
- Filename-in-parent skip. Every git commit carries the cumulative events/ tree. A forged commit reusing an old event's filename would inflate event_count and poison find_by_idempotency_key.
- Fix: if events/<id>.json exists in any parent tree, this commit didn't introduce the event. Skip it entirely.
Forgery only matters in practice if someone else can write to your brain dir. But it's a 20-line defense and the tests are trivial.
I ran 13 rounds of adversarial codex review. This class of attack kept surfacing until the third defense landed.
To be honest: this doesn't stop a determined attacker with shell access. It stops accidents and tampered commits pulled from a malicious mirror. That's what the threat model is for.
## Two-phase write and catch-up reconciliation
Git commits are slow (10ms on SSD). SQLite index writes are fast (1ms). A naive single-transaction approach forces every write to the slower of the two.
Instead:
1. Commit to git first (source of truth, durable).
2. Ingest to the index (best-effort, fast).
3. If the process dies between the two, catch_up_index reconciles on next open.
This is what the approach looks in production ⬇️
Three reconciliation paths:
- Fast path. Watermark matches HEAD AND event counts agree. Nothing to do.
- Orphan path. Index has events git doesn't. History was rewritten or a foreign index was imported. Full rebuild from git.
- Replay path. Strict subset of git in the index. Replay from the first missing event forward, relying on ingest idempotency to keep projection ordering correct.
The "replay from first-missing forward" detail took codex review pass 5 to get right.
The obvious version "just ingest the missing events" breaks when missing events are interleaved with indexed ones.
You overwrite current-tip projections with stale data.
Replaying from the earliest missing event forward keeps projection ordering monotonic.
## The MCP server: tool descriptions matter more than implementations
Brain exposes five MCP tools. Once you wire it in, every compatible AI tool gets them.
In brain-mcp/src/lib.rs:
The agent reads the description. It decides whether to call a tool based on the description.
A description that says "when in doubt, call me" gets called more than one that says "returns top 5 matches."
Every tool description includes WHEN to call, not just WHAT it does. That's the leverage point.
## FTS5 query rewriting
FTS5's default behavior is exact-token match. Typing fast returns nothing even when you have notes about fastapi.
That's a fatal UX bug.
The fix lives in brain-index/src/query.rs.
If the query has no explicit operators, split on tokenizer boundaries and append * to each token. Bare words become prefix queries.
Now:
- ask fast → fast* → matches fastapi
- ask fastapi-users → fastapi* users* → matches either token
- ask cargo build → cargo* build* → implicit AND, finds notes with both
- ask "exact phrase" → passes through unchanged
- ask auth* → passes through unchanged (no double-star)
FTS5 parse errors get caught silently in the search path and return empty results instead of bubbling up as a 500. A stray foo(bar from an MCP client doesn't crash the session.
## Sync via git push and pull
Brain is a git repo. Sync is git push and git pull. I wrapped both as CLI commands.
Shelling out to git gets you SSH keys, HTTPS credential managers, 2FA, macOS Keychain, and corporate SSO for free.
No config. No daemon. No cloud. Git does all the work.
## The cross-tool test
Once all five harnesses point at the same ~/.brain, a note Claude Code writes is visible to Cursor, Codex, OpenClaw, and Hermes.
This is the test I use to verify a working install:
Three different tools. Three different processes. One shared memory.
The thing I wanted.
## What happened over the build
Week 1. Built the core: types, store, index. Nothing compiled for two days because libgit2's Repository is !Send and I needed to thread it through async code. Solved by spawn_blocking at the orchestration layer instead of fighting the lifetime.
Weeks 2–3. Built the MCP server. First version had tool descriptions that said "Save a note." The agent never called it without prompting. I rewrote the descriptions to be prescriptive with explicit "CALL THIS WHENEVER" lists. The agent started using them unprompted.
> Tool descriptions matter more than tool implementations for MCP.
Week 3 was also when I almost gave up. brain ask fast returned nothing even though I had ten notes mentioning fastapi. I rebuilt the index three times. I wrote a test that ingested 100 events and asserted retrieval. The test passed. The CLI still returned nothing.
Then I read the FTS5 docs more carefully. Default behavior is exact-token match. I wrote the query rewriter that night. The system became 10x more useful by morning.
Weeks 4–5. Codex review passes 5 through 11. Twelve fixes ranging from secret-prefilter bypasses to commit-trailer injection to watermark race conditions. Added 30 regression tests.
The pattern was always the same: codex would point at a five-line block, I'd think "that's fine," I'd write a test trying to break it, and the test would break it.
Week 6. Built the adapters for all five harnesses. Most of this is writing system-prompt templates and getting each harness's include syntax right. Spent half a day fighting Cursor's .mdc frontmatter (you need literal ["**/*"] globs for alwaysApply to work reliably).
Week 7. Wrote brain push, brain pull, brain remote. Took thirty minutes because git does all the work.
Week 8. TUI polish. Day groups, tool glyphs, filter keys. Prompted by realizing I had 200 notes from four tools and no way to see them by tool.
Week 9. I hit the wall. The source-pollution bug. Every note was matching every query. brain ask brain returned every note. brain ask fastapi returned every note.
> I don't want to overstate this. The system isn't magic. It isn't even particularly clever. What it is, is consistent.
> It commits every note. It rebuilds the index when the index is wrong. It refuses to write a secret. It doesn't have bad days where it forgets what it learned last week.
> That consistency, compounded over months, produces something that feels qualitatively different from a stateless agent , even though the underlying model is the same.
## What to watch out for
Index drift.
- Problem: Index has events git doesn't, or vice versa. Search returns ghosts or misses real notes.
- Solution: brain doctor --deep. Rebuilds the index from git in one pass.
Source-field FTS pollution.
- Problem: A metadata field gets indexed into the body column. Every query matches every note.
- Solution: Explicit allow-list of fields that hit FTS. Test with a query for a token that should match nothing.
Detached HEAD writes.
- Problem: A write that lands on a detached HEAD evaporates on the next branch checkout.
- Solution: Reject detached HEAD at open time and append time. Brain refuses to write until you reattach.
Concurrent-writer races.
- Problem: Two agents commit at the same millisecond, one loses the HEAD compare-and-swap.
- Solution: Retry loop with exponential backoff. Idempotency-key pre-check prevents double-landing on retry.
Schema mismatch on upgrade.
- Problem: A new brain version bumps the FTS schema. Old index file is incompatible.
- Solution: Rebuild self-heal. Detect the mismatch on open, delete the old sqlite file, rebuild from git.
Secret leaked into commit subject.
- Problem: Free-form user text in a commit subject gets promoted to git log --oneline and is permanent.
- Solution: Subject scrubbing for Observe, Lesson, Redact. Use the event_id, never echo user text.
## Things I'd do differently
Write the retrieval test on day one. The source-pollution bug would have been caught immediately with a test that said "a note with no mention of brain should not match ask brain." I didn't write that test until week 9.
> Write tests that validate the user experience, not just unit correctness.
Run adversarial review every 500 lines, not every 5,000. I batched 13 codex passes at the end. If I'd run one after each major feature, most fixes would have been 5-line changes instead of 50-line refactors.
Start with typed errors. StoreError::InvalidState { detail: String } stuffed six distinct conditions into one stringly-typed variant. Callers couldn't distinguish recoverable from corruption.
Make the tool descriptions prescriptive from the start. Generic descriptions ("Save a note") don't get called. Descriptions that tell the agent WHEN to call get used proactively. Single highest-leverage change I made.
Don't split repos until the API stabilizes. I tried to split brain out of the agentic-stack parent repo before the code stabilized. Re-synced three times. Should have waited.
## The bottom line
Right now this system holds memory for one person across one set of tools. But the tools come and go. The model gets deprecated. The harness gets a new IDE plug-in and the old one bit-rots. Your skills evolve with the projects.
The only thing that accumulates value over years is the memory : what you decided, what you tried, what failed, what worked, what you'd never do again.
- The model you can swap whenever something better ships.
- The skills and protocols you can rewrite as the work evolves.
- The memory you cannot replace. It encodes your specific mistakes, your specific decisions, your specific way of working.
Own your memory. Own your index. Keep them in plain files and git where no company can take them from you.
## Credits
Thesis inspired by Harrison Chase (CEO of LangChain) and sharpened by Taranjeet at Mem0, who put the words "memory lock-in" on it before I knew that was the framing I needed.
I built the first version in an afternoon. The complete stack took three months. It has been getting better every week since, without me touching it.
## Disclaimers
This article was researched and written by the author, edited by Minimax-M2.7 The thumbnail was taken off Pinterest.
Harrison Chase "memory should be open!" — https://x.com/hwchase17/status/2046308913939919232Harrison
Chase :"Your Harness, Your Memory" — https://www.langchain.com/blog/your-harness-your-memory
Vivek Trivedi :"The Anatomy of an Agent Harness" — https://www.langchain.com/blog/the-anatomy-of-an-agent-harness
## 相关链接
- [Avid](https://x.com/Av1dlive)
- [@Av1dlive](https://x.com/Av1dlive)
- [127K](https://x.com/Av1dlive/status/2048800691943309698/analytics)
- [github.com/codejunkie99/brain](https://github.com/codejunkie99/brain)
- [Apr 21](https://x.com/hwchase17/status/2046308913939919232)
- [36K](https://x.com/hwchase17/status/2046308913939919232/analytics)
- [Apr 25](https://x.com/taranjeetio/status/2047788130623971410)
- [2.1K](https://x.com/taranjeetio/status/2047788130623971410/analytics)
- [github.com/codejunkie99/brain](https://github.com/codejunkie99/brain)
- [actor.id](https://actor.id/)
- [https://x.com/hwchase17/status/2046308913939919232Harrison](https://x.com/hwchase17/status/2046308913939919232Harrison)
- [https://www.langchain.com/blog/your-harness-your-memory](https://www.langchain.com/blog/your-harness-your-memory)
- [https://www.langchain.com/blog/the-anatomy-of-an-agent-harness](https://www.langchain.com/blog/the-anatomy-of-an-agent-harness)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [12:25 AM · Apr 28, 2026](https://x.com/Av1dlive/status/2048800691943309698)
- [127.5K Views](https://x.com/Av1dlive/status/2048800691943309698/analytics)
- [View quotes](https://x.com/Av1dlive/status/2048800691943309698/quotes)
---
*导出时间: 2026/4/28 12:51:20*
---
## 中文翻译
# 2026 年人人必备的 AI Agent 记忆栈(构建者指南)
**作者**: Avid
**日期**: 2026-04-20T19:23:58.000Z
**来源**: [https://x.com/Av1dlive/status/2048800691943309698](https://x.com/Av1dlive/status/2048800691943309698)
---

## 这是一个没人告诉 AI 构建者的真相。
你不需要构建自己的模型
## 你需要构建的只是
一个存在于所有模型之下的记忆层。
> TLDR;如果你不想读完全文,把这个链接给你的 agent 并向它提问:➡️ github.com/codejunkie99/brain
## 核心论点:记忆应该是可移植的
一个在 Claude Code 或 Cursor 中都相同的存储。一个能在会话重置、模型弃用、工具切换后依然存活的索引。一个让所有 harness 都能对话同一个大脑的协议。
Harrison Chase 在 4 月 21 日用三行话精准地表达了这一点:
> **Harrison Chase@hwchase17**: [原文链接](https://x.com/hwchase17/status/2046308913939919232)
>
> 记忆将成为最大的锁定
> 每个人都知道这一点,所以都在争先恐后地抢占先机
> 记忆应该是开放的!
>
> 
Mem0 的 Taranjeet 在 4 月 24 日进一步深化了这一观点:
> **Taranjeet@taranjeetio**: [原文链接](https://x.com/taranjeetio/status/2047788130623971410)
>
> Chamath 说得对:被锁定在单一基础模型上正迅速成为企业的瓶颈。
> 但在更深一层,另一种锁定正在形成:记忆锁定。
> 需要可替换的不仅仅是模型。让 agent 变得有用的上下文也需要是
我同意这两位的观点。我只是再往前推了一步。
记忆不仅应该是开放的。不仅应该是可移植的。它还应该是类型化的、可索引的、可审计的、可同步的、安全的。Agent 值得拥有比一个 markdown 块和一个无法检查的向量存储更好的东西。
我花了一个半月多一点的时间构建了正是这样的东西:
- 一个基于 git 的事件日志。每个笔记一次提交。
- 一个 SQLite FTS5 派生索引,带 BM25 排序和前缀匹配。
- 一个 MCP stdio 服务器,向任何兼容的 agent 暴露五个工具。
- 一个 CLI 和一个全屏 TUI,供人类参与其中。
本文讲述了我如何构建它,以及为什么每个部分都存在。
如果你只是想安装它:github.com/codejunkie99/brain。可与 Claude Code、Cursor、Codex、OpenClaw、Hermes 或任何支持 MCP 或 shell 的工具配合使用。
如果你想了解你正在安装的是什么,请继续阅读。⬇️
## 项目的结构
七个 Rust crate。五个适配器目录。约 9,000 行代码。143 个回归测试。路径上的一个二进制文件。
我不是从零开始设计这个的。我是在尝试了不奏效的布局之后收敛到这个方案的。
关键洞察——也就是 Harrison 的帖子为我清晰呈现的东西——是分离:
- Git 是事实的来源。
- SQLite 是一个一次性索引,可按需从 git 重建。
- 编排位于两者之上,处理两阶段写入。
- 适配器是薄薄的胶水层,让每个 harness 都调用同一个共享的大脑。
这意味着:
- 明天换掉 harness → 不损失任何东西。
- 明天换掉模型 → 不损失任何东西。
- 完全丢失索引 → 几秒钟内从 git 重建。
唯一积累价值的东西就是 git 日志。而那只是一个纯 JSON 文件夹。
## 为什么 git 是正确的存储
每种替代方案都有一个致命缺陷:
- 仅用 SQLite。速度快,但现在你得自己构建复制、冲突解决和审计跟踪。你重建了半个 git,而且做得很糟糕。
- Markdown 文件。对人友好,对 agent 不友好。解析慢。更新有竞争问题。没有自然的写入单元。
- DuckDB 或 PostgreSQL。对单用户记忆来说过于复杂。也无法帮助你在笔记本电脑之间同步。
- Git。每个事件一次提交,每个事件一个文件。免费的审计日志。通过 push/pull 实现免费同步。免费的合并语义。免费的 diff 可视化。通过任何 git 托管服务实现免费备份。
写入单元是提交,它是原子性的。每个事件都有一个永不改变的永久 OID。
下面是 brain-store/src/repo.rs 中一次追加的样子:
带指数退避的重试循环和幂等键预检查使它在并发写入者下保持持久。一个天真的“直接提交”版本会在两个 agent 在同一毫秒内写入时第一次就挂掉。
## 为什么用 SQLite FTS5 做检索
Git 是真相,但 git 搜索很慢。git log -S "authlib" 可以工作,但在一个有 10,000 个事件的仓库上需要几秒钟,因为它要重新 diff 每次提交。
所以我加了一个派生索引。brain-index crate 维护一个 SQLite FTS5 索引,包含三个加权列:
- 标题:10 倍权重
- 正文:基线权重
- 标签:5 倍权重
BM25 对结果进行排序。写入时:一条 INSERT OR REPLACE。搜索时:在 100,000 个事件的情况下,top-5 命中不到一毫秒。
两个关键的表(位于 brain-index/src/schema.rs 中):
关键细节:索引是派生的。如果它损坏了、schema 升级了,或者你故意删掉了它,brain doctor --deep 可以一次性从 git 重建它。
Git 历史是唯一重要的真相。索引只是一个缓存。
我最初尝试的是 embeddings。更慢。更复杂。需要模型依赖。对于一个事件数少于 100,000 的单用户系统,FTS5 上的 BM25 产生了几乎无法区分的检索质量。
简单的加权公式胜出了。
## 10 种事件类型
记忆不是一回事。偏好不是教训。断言不是事实。它们需要不同的检索规则、不同的可见性语义、不同的更新策略。
每个变体都有自己的 payload 结构:
- Claims 携带 schema_type + key + content + supersedes,用于链感知检索。
- Prefs 有 category + key + value。
- Redacts 携带原因并触发投影回滚。
类型化才是重点。current_pref("auth", "provider") 是对一个物化投影的 SQL 查询来返回当前值——而不是在 markdown 块上做字符串匹配。
类型化的记忆才是可查询的记忆。
每个事件还携带元数据:event_id(UUIDv7,可按时间排序)、actor.kind + actor.id + actor.harness、chain_id、time_observed、time_recorded、layer、authority、signature_state、classification、schema_version、idempotency_key。
`actor.harness` 字段是实现跨工具归因的关键。Claude Code 写的笔记在内容层面上与 Cursor 的笔记无法区分。harness 字段告诉你这条笔记是由哪个工具产生的。
## Redact 是回滚,而不是擦除
当你 redact 当前的 Pref tip 时,天真的做法是删除 projection 行。这会导致 current_pref 为空——意味着用户丢失了所有先前值。
正确的行为是回滚。Redact Pref B → projection 恢复其前驱 Pref A。就像内存版 的 git revert。
在 brain-index/src/ingest.rs 中:
`ORDER BY time_recorded DESC, event_id DESC` 子句存在的原因是同一毫秒内的插入会以非确定性的方式选取前驱。UUIDv7 的随机尾部用于打破平局。
## 秘密预过滤器
每个序列化的事件在接触 git 之前都会经过一个 RegexSet 扫描。
- 18 个模式
- NFKC Unicode 规范化,以击败全角形近字
- 零宽字符剥离,以击败走私的 ZWSP 字符
在 brain-store/src/secrets.rs 中:
扫描分三遍进行:
1. 扫描原始输入。
2. 剥离零宽字符后重新扫描——阻止 s\u{200B}k-ant-... 走私。
3. NFKC 规范化(将全角 ASCII 和数学字母数字折叠为普通 ASCII),再次剥离零宽字符,重新扫描。
`pem-private-key` 模式是一道兜底防线。
- 粘贴到 Observe.content 字段中的 GCP 服务账号 blob 会被 JSON 转义:`"type":"service_account"` 变成 `\"type\":\"service_account\"`,从而破坏结构化的 `gcp-service-account` 模式。
- 但 `-----BEGIN PRIVATE KEY-----` 没有引号,经过 JSON 转义后保持不变,兜底防线就能捕获它。
这是我在第 11 轮 codex 评审中学到的。
提交主题也会被清洗。对于 Observe、Lesson 和 Redact(三种自由格式变体),主题使用 event_id 而不是回显用户文本。
否则,一个漏过预过滤器的密钥会被提升到 `git log --oneline` 中。
## 我没想到自己会需要的伪造防御
提交级别的篡改比我想象的更难防御。三种低成本的防御,全部在一个名为 `event_from_commit` 的函数中:
- 尾部块解析。`event_id:` 尾部仅从提交消息的最后一个段落(最终空行之后)解析。以 `\n\nevent_id: <forged>` 结尾的用户笔记无法走私尾部。
- Blob-尾部交叉校验。blob 内部的 event_id 字段必须与尾部的 event_id 以及文件名匹配。防止“尾部指向错误 blob”的伪造。
- 父树中文件名跳过。每个 git 提交都携带累积的 `events/` 树。伪造的提交复用旧事件的文件名会虚增 event_count 并污染 find_by_idempotency_key。
- 修复:如果 `events/<id>.json` 存在于任何父树中,则此提交没有引入该事件。完全跳过它。
伪造在实践中只有当别人能写入你的 brain 目录时才重要。但这只是一段 20 行的防御代码,测试也很简单。
我运行了 13 轮对抗性 codex 评审。这类攻击不断出现,直到第三种防御落地。
说实话:这无法阻止拥有 shell 访问权限的坚定攻击者。它防止的是意外以及从恶意镜像拉取的被篡改提交。这就是威胁模型的意义所在。
## 两阶段写入与追赶式对账
Git 提交很慢(SSD 上 10ms)。SQLite 索引写入很快(1ms)。天真的单事务方法会迫使每次写入都受限于两者中较慢的那个。
替代方案:
1. 先提交到 git(真相源,持久化)。
2. 摄入到索引(尽力而为,快速)。
3. 如果进程在两步之间死亡,catch_up_index 会在下次打开时进行对账。
这就是该方法在生产环境中的样子 ⬇️
三条对账路径:
- 快速路径。水位线与 HEAD 匹配且事件计数一致。无需操作。
- 孤儿路径。索引中有 git 没有的事件。历史被重写或导入了外部索引。从 git 完全重建。
- 重放路径。索引中是 git 的严格子集。从第一个缺失事件开始向前重放,依靠摄入的幂等性来保持 projection 排序正确。
“从第一个缺失事件向前重放”这个细节花了第 5 轮 codex 评审才弄对。
显而易见的版本——“只摄入缺失的事件”——当缺失事件与已索引事件交错时会出错。
你会用过期数据覆盖 current-tip projections。
从最早的缺失事件开始向前重放可以保持 projection 排序的单调性。
## MCP 服务器:工具描述比实现更重要
Brain 暴露了五个 MCP 工具。一旦接入,每个兼容的 AI 工具都能获得它们。
在 brain-mcp/src/lib.rs 中:
智能体会阅读描述。它根据描述决定是否调用某个工具。
一个写着“拿不准就调用我”的描述,比一个写着“返回前 5 个匹配项”的描述被调用得更频繁。
每个工具描述都包含何时调用,而不仅仅是它做什么。这就是杠杆点。
## FTS5 查询重写
FTS5 的默认行为是精确 token 匹配。快速输入 "fast" 时即使你有关于 fastapi 的笔记也返回空结果。
这是一个致命的 UX 缺陷。
修复位于 brain-index/src/query.rs。
如果查询没有显式运算符,则按分词器边界拆分并为每个 token 追加 `*`。裸词变成前缀查询。
现在:
- `ask fast` → `fast*` → 匹配 fastapi
- `ask fastapi-users` → `fastapi* users*` → 匹配任一 token
- `ask cargo build` → `cargo* build*` → 隐式 AND,找到同时包含两者的笔记
- `ask "exact phrase"` → 原样通过
- `ask auth*` → 原样通过(不会重复加星号)
FTS5 解析错误在搜索路径中被静默捕获并返回空结果,而不是以 500 错误冒泡。来自 MCP 客户端的一个多余的 `foo(bar` 不会导致会话崩溃。
## 通过 git push 和 pull 同步
Brain 是一个 git 仓库。同步就是 git push 和 git pull。我把两者都封装成了 CLI 命令。
调用外部 git 可以免费获得 SSH 密钥、HTTPS 凭证管理器、2FA、macOS 钥匙串以及企业 SSO。
无需配置。无守护进程。无云端。Git 完成所有工作。
## 跨工具测试
一旦所有五个 harness 都指向同一个 ~/.brain,Claude Code 写入的一条笔记就能被 Cursor、Codex、OpenClaw 和 Hermes 看到。
这是我用来验证安装是否成功的测试:
三个不同的工具。三个不同的进程。一份共享记忆。
这就是我想要的东西。
## 构建过程中发生了什么
第 1 周。构建核心:类型、存储、索引。有两天什么都编译不过,因为 libgit2 的 Repository 是 !Send 的,而我需要把它穿透 async 代码传递。最后在编排层用 spawn_blocking 解决,而不是去和生命周期搏斗。
第 2–3 周。构建 MCP 服务器。第一版的工具描述写着“保存一条笔记”。如果不主动提示,agent 从来不会调用它。我把描述改写为指令式的,附带明确的“何时必须调用”清单。agent 开始在无人提示的情况下主动使用它们了。
> 对于 MCP 来说,工具描述比工具实现更重要。
第 3 周也是我差点放弃的一周。brain ask fast 什么都没返回,尽管我有十条提到 fastapi 的笔记。我重建了三次索引。我写了一个测试,摄入 100 个事件并断言检索结果。测试通过了。CLI 依然什么都没返回。
然后我更仔细地读了 FTS5 的文档。默认行为是精确分词匹配。当晚我就写了查询重写器。到第二天早上,系统的可用性提升了 10 倍。
第 4–5 周。Codex 评审第 5 到第 11 轮。十二处修复,从密钥预过滤器绕过,到 commit-trailer 注入,再到水印竞态条件。新增了 30 个回归测试。
模式总是一样的:codex 指出一段五行代码,我心想“这没问题”,然后我写个测试试图打破它,结果测试真的打破了它。
第 6 周。为全部五个 harness 构建适配器。这大部分工作是编写系统提示模板,以及让每个 harness 的 include 语法都正确。花了半天时间和 Cursor 的 .mdc frontmatter 搏斗(要让 alwaysApply 可靠生效,你需要字面量 ["**/*"] 通配符)。
第 7 周。编写了 brain push、brain pull、brain remote。花了三十分钟,因为 git 包办了一切。
第 8 周。TUI 打磨。按天分组、工具图标、过滤快捷键。起因是我意识到自己有来自四个工具的 200 条笔记,却没办法按工具查看它们。
第 9 周。我撞墙了。源污染 bug。每条笔记都匹配每个查询。brain ask brain 返回所有笔记。brain ask fastapi 也返回所有笔记。
> 我不想夸大其词。这个系统并不神奇。它甚至算不上特别聪明。它的特点是一致性。
> 它提交每一条笔记。索引出错时它会重建索引。它拒绝写入密钥。它不会有“糟糕的日子”,忘记上周学到的东西。
> 这种一致性经过数月的累积,会产生某种在质上不同于无状态 agent 的东西,即使底层模型是同一个。
## 需要注意的问题
索引漂移。
- 问题:索引里有 git 中没有的事件,或者反之。搜索返回幽灵结果或漏掉真实笔记。
- 解决方案:brain doctor --deep。一次性从 git 重建索引。
source 字段导致的 FTS 污染。
- 问题:某个元数据字段被索引进了正文列。每个查询都匹配每条笔记。
- 解决方案:对进入 FTS 的字段使用显式允许列表。用一个理应匹配不到任何内容的分词做测试。
分离 HEAD(detached HEAD)下的写入。
- 问题:落在分离 HEAD 上的写入会在下次分支切换时蒸发。
- 解决方案:在打开时和追加时都拒绝分离 HEAD。Brain 会拒绝写入,直到你重新挂载。
并发写入者竞态。
- 问题:两个 agent 在同一毫秒提交,其中一个在 HEAD 比较交换中失败。
- 解决方案:带指数退避的重试循环。幂等键预检查防止重试时重复落地。
升级时的 schema 不匹配。
- 问题:新版 brain 升级了 FTS schema。旧的索引文件不兼容。
- 解决方案:重建自愈。在打开时检测到不匹配,删除旧的 sqlite 文件,从 git 重建。
密钥泄漏到提交主题中。
- 问题:提交主题中的自由文本会被提升到 git log --oneline 中,并且是永久的。
- 解决方案:对 Observe、Lesson、Redact 进行主题清洗。使用 event_id,绝不回显用户文本。
## 我会采取不同做法的地方
第一天就写检索测试。如果当时有一个“一条完全没提到 brain 的笔记不应该匹配 ask brain”的测试,源污染 bug 早就被抓住了。我直到第 9 周才写这个测试。
> 编写验证用户体验的测试,而不仅仅是单元正确性。
每 500 行就做一次对抗性评审,而不是每 5000 行。我把 13 轮 codex 评审攒到了最后。如果我每完成一个大功能就做一轮评审,大多数修复本会是 5 行的改动,而不是 50 行的重构。
一开始就用类型化错误。StoreError::InvalidState { detail: String } 把六种不同的状况塞进了一个基于字符串的变体。调用方无法区分可恢复错误和数据损坏。
让工具描述从第一天起就是指令式的。泛泛的描述(“保存一条笔记”)不会被调用。告诉 agent 何时该调用的描述会被主动使用。这是我所做的杠杆率最高的一次改动。
在 API 稳定之前不要拆分仓库。我曾在代码稳定之前尝试把 brain 从 agentic-stack 父仓库中拆出来。重新同步了三次。应该再等等。
## 结论
现在这个系统为一个人、在一组工具之间保存记忆。但工具来来去去。模型会被弃用。harness 会得到新的 IDE 插件,旧的则会慢慢烂掉。你的技能会随着项目演进。
唯一能随岁月积累价值的是记忆:你做过什么决定、尝试过什么、什么失败了、什么成功了、你绝不会再做什么。
- 模型,只要更好的东西发布,你随时可以更换。
- 技能和协议,你可以随着工作演进随时重写。
- 记忆,你无法替代。它编码了你特定的错误、你特定的决定、你特定的工作方式。
拥有你的记忆。拥有你的索引。把它们保存在纯文本文件和 git 中,任何公司都无法从你手中夺走。
## 致谢
本文的核心论点灵感来自 Harrison Chase(LangChain CEO),并由 Mem0 的 Taranjeet 进一步打磨,他在我意识到自己需要"memory lock-in"(记忆锁定)这个表述之前,就已经把这个说法提了出来。
我在一个下午构建了第一版。完整的技术栈花了三个月。从那以后,它每周都在变得更好,而无需我亲自改动。
## 免责声明
本文由作者独立调研和撰写,由 Minimax-M2.7 编辑。缩略图取自 Pinterest。
Harrison Chase "memory should be open!"(记忆应当开放!)— https://x.com/hwchase17/status/2046308913939919232Harrison
Chase:"Your Harness, Your Memory" — https://www.langchain.com/blog/your-harness-your-memory
Vivek Trivedi:"The Anatomy of an Agent Harness" — https://www.langchain.com/blog/the-anatomy-of-an-agent-harness
## 相关链接
- [Avid](https://x.com/Av1dlive)
- [@Av1dlive](https://x.com/Av1dlive)
- [127K](https://x.com/Av1dlive/status/2048800691943309698/analytics)
- [github.com/codejunkie99/brain](https://github.com/codejunkie99/brain)
- [4月21日](https://x.com/hwchase17/status/2046308913939919232)
- [36K](https://x.com/hwchase17/status/2046308913939919232/analytics)
- [4月25日](https://x.com/taranjeetio/status/2047788130623971410)
- [2.1K](https://x.com/taranjeetio/status/2047788130623971410/analytics)
- [github.com/codejunkie99/brain](https://github.com/codejunkie99/brain)
- [actor.id](https://actor.id/)
- [https://x.com/hwchase17/status/2046308913939919232Harrison](https://x.com/hwchase17/status/2046308913939919232Harrison)
- [https://www.langchain.com/blog/your-harness-your-memory](https://www.langchain.com/blog/your-harness-your-memory)
- [https://www.langchain.com/blog/the-anatomy-of-an-agent-harness](https://www.langchain.com/blog/the-anatomy-of-an-agent-harness)
- [升级至 Premium](https://x.com/i/premium_sign_up)
- [2026年4月28日 上午12:25](https://x.com/Av1dlive/status/2048800691943309698)
- [127.5K 次浏览](https://x.com/Av1dlive/status/2048800691943309698/analytics)
- [查看引用](https://x.com/Av1dlive/status/2048800691943309698/quotes)
---
*导出时间: 2026/4/28 12:51:20*