cairn
跨 agent / 跨文件夹的工作记忆 CLI。一条命令还原"上次做到哪", 一条命令留下路标。存储是人可读的 Markdown,零依赖, 支持 Claude Code / VS Code Copilot / DSH / 任何认 AGENTS.md 的 agent。
- Stars
- 1
- Language
- Python
- Created
- Oct 16, 2026
- Updated
- Oct 16, 2026
Introduction
Cairn
cairn /kern/ · n. 登山者在岔路上垒起的一堆石头,给后来的人指路。 它不修路,只在关键处留一个记号:这边走过,那边是死路。
跨 agent、跨文件夹的工作记忆。 一条命令还原现场,一条命令留下路标。
纯 Python 标准库 · 零依赖 · 存储是人可读的 Markdown · 不在你的项目里留任何文件
你遇到的问题不是「没有记忆」
是记忆锁在某个 agent 的某个会话里:
| 症状 | 代价 |
|---|---|
| 换个 agent(Claude Code ↔ Copilot ↔ Codex ↔ DSH) | 背景重讲一遍 |
| 换个文件夹 | 上下文归零 |
| 隔三天回来 | 忘了做到哪,也忘了上次为什么否掉某个方案 |
| 同一个坑 | 踩第二次、第三次 |
| 上下文越聊越糊 | agent 开始编数字,而你没法核对 |
cairn 把「上次做到哪」从会话里捞出来,变成一份谁都能读的全局文件。
30 秒看懂
cairn init # 在项目里跑一次,登记
cairn boot # 以后每次开工:还原现场
cairn wrap # 每次收工:留下路标
cairn boot 打出来的是这样(真实格式,内容为示例):
============================================================
cairn · 开工上下文 项目 支付网关(pay-gw)
你是被交接的 agent。读完下面内容,先复述再动手。
不要改文件,不要开始干活 —— 复述完停下等用户确认。
============================================================
项目路径:D:\work\pay-gw
--- NOW.md(当前状态,最重要)------------------------------
**最后更新**:2026-03-14
## 在干嘛
把 refund 的幂等键从 `order_id` 换成 `order_id + attempt`。
线上有个重复退款的 case,复现脚本已经写出来了。
## 下一步
打开 `src/refund/handler.py:88`,那里 `_idem_key()` 还是旧的拼法。
## 不要碰
`db/migrations/` —— 下周要合并另一个分支的迁移,现在动会冲突。
--- LOG.md(最近 5 条)-------------------------------------
- 16:20 | 复现了重复退款 | `pytest tests/test_refund.py -k idem` 稳定红 | 确认不是偶发
- 15:02 | 排除了并发窗口 | 加了 `asyncio.sleep(0.5)` 仍然重复 | 不是竞态
--- DECISIONS.md -------------------------------------------
| 2026-03-11 | 幂等键不用 Redis 去重 | 少一个外部依赖,且要能离线重放 | Redis SETNX 方案 |
--- GOTCHAS.md ---------------------------------------------
- **本地跑迁移会把 dev 库清空**
→ 原因:`make migrate` 里写死了 `DROP SCHEMA`
→ 对策:用 `make migrate-safe`
============================================================
复述这 4 句,然后停下:
① 这个项目在做什么? ② 上次做到哪?
③ 下一步是什么? ④ 哪些坑不能踩?
============================================================
这就是「上次做到哪」。 不用谁再讲一遍。
安装
需要 Python 3.8+(只用标准库,不需要 pip install 任何东西)。
git clone https://github.com/oranchenjie/cairn.git $env:USERPROFILE\.cairn\src
cd $env:USERPROFILE\.cairn\src
.\install.ps1
装完新开一个终端(PATH 生效),然后:
cd <你的项目>
cairn init # 登记(项目里已有 _ai/ 的话会自动迁移)
cairn boot # 看看它能不能说出「上次做到哪」
可选参数:
.\install.ps1 -SkipPath # 不动 PATH
.\install.ps1 -Python "C:\...\python.exe" # 指定 Python
.\install.ps1 -DshHome "\\wsl$\...\.dsh" # DSH 跑在 WSL 里时看这里
.\install.ps1 -SkipVSCode -SkipClaude -SkipDsh # 只装 CLI
不碰系统 PATH、不碰注册表、不装任何包。 出问题了就把
~/.cairn和~/.vscode/extensions/oranchenjie.cairn-*删掉。
存储长什么样
~/.cairn/
index.json 项目索引:路径 → slug
projects/<slug>/
NOW.md ← 当前状态。每次收工**整体重写**,≤60 行
LOG.md ← 工作日志。只追加,最新在上
DECISIONS.md ← 决策 + **否决了什么**
GOTCHAS.md ← 踩过的坑:症状 → 原因 → 对策
STATE.md ← 环境 / 路径 / 命令 / **实测数字**
为什么拆成 5 个? 因为 NOW 要短(每次重写)、LOG 要长(只追加),
混在一个文件里两边都做不好。
为什么是 Markdown? 你能用眼睛看、能 git diff、能 grep、坏了能手动改一行。
它不是黑盒 —— 这一条直接排除掉了数据库和向量库。
项目根里一个文件都不留。 项目识别靠 index.json 里登记的路径,
从当前目录往上比对。所以你在任何子目录里跑 cairn 都能认出来,
而不会在你的仓库里凭空多出一个未跟踪文件。
早期版本会在项目根写一个标记文件。它能工作,但代价很实在 —— 那是个裸的未跟踪文件,会一直挂在
git status里。现在标记只读不写: 老项目不用重新登记,init会顺手清掉遗留的,doctor会把残留的报出来。代价说清楚:新登记的项目不再有标记,所以把项目文件夹改名或搬走之后认不出来了 —— 到新位置重新
cairn init一次即可。
命令
| 命令 | 干什么 |
|---|---|
cairn boot | ★ 会话开头:打印开工上下文(有硬上限,不会撑爆 context) |
cairn wrap | ★ 会话结尾:打印收工清单 + 当前规模 |
cairn init [dir] | 登记项目,自动迁移已有的 _ai/ |
cairn now / --set - | 读 / 整体重写 NOW.md |
cairn log "..." | 追加日志(没证据会警告,纯空话直接拒写) |
cairn decide "..." --why "..." --rejected "..." | 记决策 |
cairn gotcha "症状" --cause "..." --fix "..." | 记坑 |
cairn state / --set - | 环境 / 路径 / 命令 / 实测数字 |
cairn find <kw> [--all] | 全文搜索(--all 跨所有项目) |
cairn doctor | 体检:超长的 NOW、没证据的日志、没写否决项的决策 |
cairn status | 所有项目一览 |
cairn forget <slug> [--purge] | 注销(默认留记忆,--purge 连记忆一起删) |
cairn prompt --agent <X> | 生成喂给某个 agent 的接入提示词 |
now 和 state 都支持 --set - 从 stdin 读,长内容一律走它。
它怎么保证「记了有用」
大多数「记忆」工具死于同一个原因:记了一堆三个月后没人看得懂的东西。 cairn 在三个地方设了闸。
① 没证据的不让写。 cairn log 会检查内容里有没有数字 / 文件名 / 路径 ——
纯空话(「优化了一下」)直接拒写并以退出码 2 结束:
$ cairn log "优化了一下"
[x] 这条又没证据又是空话(如「优化了一下」),不写。
改成:干了什么 | 命令/文件名/数字 | 影响
确认要写就加 --force。
② 「否决了什么」才是决策的重点。 DECISIONS.md 的意义不是记你选了什么,
而是记你没选什么、为什么 —— 否则三天后(包括你自己)会把已经否掉的方案再提一遍。
没写 --rejected 会收到提醒。
③ 记忆不能自己变成负担。 boot 的输出有硬上限(NOW 70 行、LOG 5 条、
GOTCHAS 12 行、STATE 45 行)。上下文本身是稀缺资源,
一个把所有日志塞进 prompt 的「记忆工具」是在帮倒忙。
各 agent 怎么接
一份实现(core/cairn.py),N 个壳。所有适配器都只做转发 ——
所以不会出现「插件说的和 CLI 说的不一致」。
| Agent | 机制 | 自动吗 |
|---|---|---|
| Claude Code | 插件:SessionStart hook 自动注入 cairn boot + 3 个斜杠命令 + 1 个技能 | ✅ 装了就行 |
| VS Code Copilot | 扩展注册 7 个 languageModelTools + 8 个命令 | ✅ 装了就行 |
| DSH(DeepSeek Harness) | skill(按需加载)+ $DSH_HOME/AGENTS.md(自动注入) | ✅ 装 skill 就行;全自动要贴一次片段 |
| Codex / Zed / Windsurf / Amp / Jules / Junie / Devin / Kilo | 都认 AGENTS.md | ✅ 把 adapters/generic/AGENTS.snippet.md 贴进去 |
| Cursor | .cursor/rules/*.mdc | ⚠️ 要建文件 |
| Cline | .clinerules/ | ⚠️ 要建文件 |
| Aider / Gemini CLI | 默认不读 | ❌ 要改配置 |
细节见 adapters/<名字>/README.md。
为什么不做成 MCP,也不做成数据库
这两条路都认真考虑过,都否掉了。
MCP server —— 每个 agent 要单独装、单独配。你换工具就得重配一遍, 反而更浪费时间。而「有终端」是所有 agent 的公共能力,CLI 一个命令谁都能调。
数据库 / 向量库 —— 你不能用眼睛看、不能 git diff、不能手动改一行、不能 grep。
出问题时是黑盒,调试成本远大于收益。而且「记忆」这件事,
可核对性比检索能力重要得多。
所以:存储是 Markdown,接口是命令行。
设计取舍(以及否决了什么)
| 决策 | 为什么 | 否决了什么 |
|---|---|---|
存储用全局目录,不用 repo 内的 _ai/ | 你要的就是「跨文件夹」 | 每个 repo 一份 _ai/ |
| 项目识别用 index.json 路径匹配 | 往用户仓库里塞文件不可接受 —— 它会在 git status 里裸奔 | 项目根的标记文件(降为只读兼容) |
| 拆成 5 个文件 | NOW 要短、LOG 要长,混一起两边都做不好 | 一个 NOTES.md |
| 认不出项目就报错,绝不回退 | 在 C:\ 里跑能面无表情地吐出另一个项目的内容 —— 比报错可怕得多 | 「只有唯一项目就用它」的回退 |
| 日志拒写空话 | 没证据的日志三个月后等于没写 | 什么都让写 |
boot 输出有硬上限 | 记忆不能自己变成负担 | 把整个 LOG 塞进去 |
| VS Code 扩展用纯 JS | 装机成本 = 拷文件夹,没有 npm/tsc/vsce 三层失败点 | TypeScript + 打包 |
| 适配器不自己实现逻辑 | 一份实现,不会出现说法不一致 | 在 TypeScript 里重写一遍 |
| slug 必须校验 | forget "../../x" --purge 曾能删掉 projects/ 之外的任意目录。这工具是给 agent 用的,提示词注入足以触发 | 信任传入的项目名 |
目录
~/.cairn/
src/ ← 源码(本仓库的全部内容)
core/cairn.py 唯一的核心实现(纯标准库)
install.ps1 部署:CLI→bin、扩展→VS Code、插件→Claude Code、skill→DSH
make-vsix.ps1 打包 .vsix
selftest-vscode.js 无头验证扩展的项目识别(不用启动 VS Code)
adapters/
vscode/ 纯 JS 扩展,无构建
claude-code/ Claude Code 插件
dsh/ DSH 适配
generic/AGENTS.snippet.md 给所有认 AGENTS.md 的 agent
README.md 本文件
bin/ ← 部署出来的 CLI(别手改,会被 src 覆盖)
claude-plugin/ ← 部署出来的 Claude Code 插件
projects/<slug>/ ← ★ 你的记忆数据(这里才是活数据)
⚠️ 不要在这个仓的上级目录(
~/.cairn/)里git init。 那会把projects/里你的私人工作记忆一起提交上去。 仓库根是src/。.gitignore里也列了这些名字作为兜底,但别依赖它。
为什么源码不住在项目目录里。 源码曾经放在某个项目的 agent-memory/ 下,
那是个坑:你把那个项目删了/移了,工具本身就跟着没了。
工具是跳项目的,它的家也应该是跳项目的。
开发
改了 core/cairn.py 之后:
cd $env:USERPROFILE\.cairn\src
.\make-vsix.ps1 # 先打包(它会自动把 core 同步到 adapters/vscode/vendor/)
.\install.ps1 # 再部署
改了扩展代码必须先 make-vsix.ps1。 install.ps1 只安装现成的 .vsix,
不会重新打包 —— 忘了这一步的后果是 CLI 更新了、扩展还是旧的。
.ps1 文件必须带 UTF-8 BOM。 PowerShell 5.1 在无 BOM 时按 ANSI(GBK) 读,
中文会乱,严重时连解析都过不去。检查前三个字节是不是 EF BB BF。
License
MIT