← Back to home@oranchenjie

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