longrun
英文版是源文本,内容可能比这一页新。欢迎纠错,见仓库里的 docs/TRANSLATION.md。
Claude Code 会话的记忆与协作。项目自己把状态记在磁盘上,由 agent 自己写、自己整理,所以每个会话都看得到项目已经知道什么、还有谁在做这个项目。在这之上:活交给手里已经有上下文的那个会话;等一个事件不花 token,也不会漏掉;一个会话可以协调其余会话,只有你能拍板时它会找到你。课程在一个玩具项目 shop 上一步一步演示这四件事。如果你不关心内部机制,看完第 0 章就够了。
1. 磁盘上的共享文档
每个项目一个 .longrun/,里面是所有会话都看得到的笔记,另外每个会话还有自有笔记。每次 compaction(对话历史被自动摘要)、/clear 和 resume 之后,hook 把两者都带回来,并在每一轮显示其他会话改了什么。见第 3、5、6 章。
2. 交接:消息和任务
发给另一个会话的消息或任务,在那边是一轮用户输入:对方在运行就走 socket,对方停了就在下一轮走收件箱。会话的名字就是你在侧边栏看到的标题。见第 7 章。
3. 事件驱动:不会错过事件的等待
事件监听就是一项检查,由定时器每五分钟跑一次,全程不调用模型。条件成立时,会话带着你给的那段文字醒来。没有轮询循环,等待不花 token,也没有那种定时的 sleep:醒来时事件要么早就过去了,要么还没发生。见第 7 章。
4. 一个会话协调其余会话,并且找得到你
一个会话替已经打开的那些会话维护任务板:派任务、解阻塞、点出卡住的同伴,只有你能拍板时把对话框摆到所有窗口之上。这不是自治:把一个会话推向一个可检验的条件,那是 /goal 的事。见第 8 章。
快速开始:装好,然后照常干活
这个 skill 假定人还是照常在会话里干活,而笔记、摘要和消息投递由 hook 以及照着指示走的模型负责。下面是你需要亲手做的全部事情。
- 装一次就好。一行命令,不需要 checkout,安装脚本自己去取源码:
它装上 skill、curl -fsSL https://krllx.github.io/longrun/install.sh | bash~/.claude/settings.json里的 12 个 hook、CLI~/.local/bin/longrun、longrunMCP 服务器,以及一个定时器,每五分钟跑一次检查和监视器。它只问你一件事:桌面通知,也就是下一步。从 clone 下来的仓库安装,用的也是同一个install.sh;--uninstall撤销一切。 - 通知,想要就装。
install.sh会问要不要配置通知,装不装取决于你当时的回答。longrun 从不依赖通知:全部停止和触发的事件监听,本来就会经由 socket 或收件箱到达会话,所以拒绝了也不会有别的影响(--no-notify可以提前替你回答;以后想开,再跑一句brew install terminal-notifier)。在 macOS 上,缺少terminal-notifier时安装脚本会执行brew install terminal-notifier(前提是有 Homebrew;没有就直说一声然后跳过),然后发一条测试通知;macOS 可能会问一次是否允许来自 terminal-notifier 的通知,允许它。macOS 自带的手段替代不了这一步:osascript -e 'display notification'是以 Script Editor 的身份发出的,而它没有通知权限,于是系统把通知归档、什么都不画,还返回 0(在 macOS 15 上 25 次全是如此;就算先打开过 Script Editor 也拿不到权限)。所以横幅上带的是 terminal-notifier 自己的名字和图标:这两样 macOS 都取自发出通知的那个 bundle,也没有办法逐条覆盖。在 Linux 上用的是notify-send(libnotify),大多数桌面环境已经有了。两种系统都可以用longrun notify --test检查:它发一条探针,再回头读一次,确认系统真的把它画了出来,并打印点一下会打开哪个会话。如果有一个会话你绝对不能错过:longrun important on(或者next,或者一个数字)让它每轮结束时一定发通知,不管notify_turn_end设成了什么,也不管当前前台是哪个窗口;你在另一个 Claude 会话里打字时,Claude 窗口就在前台,按前台窗口判断的那条规则就不会发通知。 - 把文件夹变成项目。对 longrun 来说,项目就是你在 Claude Code 里打开的那个文件夹。不一定是仓库根目录:可以是装着好几个仓库的文件夹、monorepo 的一个子目录,或者一个空的指挥部 (hq) 文件夹。在那里跑一次
longrun init:在终端里跑,或者直接在 Claude Code 会话里说一声,agent 会替你跑。一个.longrun/目录会出现;如果这个文件夹在仓库里,把它加进 ignore 文件(或者用longrun init --external,它把目录放到~/.claude/longrun/下)。同一个项目的每个 worktree 里:longrun link <project>。检查一下:longrun where。 - 调一调。在会话里对 agent 说:"set up longrun"(或者
/longrun onboard)。它会跑longrun onboard,简短讲一下这个 skill 做什么,然后问你几个主要设置:autocompact 窗口、判定卡住的阈值、笔记大小、通知。它自己用longrun config set把答案写进配置。只有一个例外:hook 设不了 autocompact 窗口,所以 agent 会让你在 Claude Code 里手敲/autocompact 300k(或者你选的那个值)。在离这个阈值还有 5 万 token 时,会有一个 hook 提醒会话在 compaction 之前先把笔记写好。各个 key 的细节见下面第 9 章。 - 干活。不用调用任何命令。会话启动时拿到摘要(共享笔记、自有笔记、还有谁在干活),compaction 之后一切自己回来,其他会话的改动在下一轮送到。agent 自己会把死胡同和决定写成笔记。你可以用平常的话说:“记一下”、“我们都试过什么”、“会话状态”、“把这个交给另一个会话”、“还剩什么”、“PR 合了告诉我”。
- 多个会话,一个共同目标。在其中一个里说:“接过编排器的角色,目标是……”。它会建一块任务板并派活;其他会话把任务当作一轮用户输入收下,什么都不用学。“让所有人停下”和“继续”同样用平常的话说,在编排器会话里说,见下面第 8 章。
longrun where(在哪个项目、哪个会话)、longrun status(每个会话在做什么)、longrun watch status(定时器还活着吗)、longrun notify --test(通知到得了屏幕吗)、longrun config(生效的设置)。为什么
项目真正的状态,试过什么没成、定下了什么、什么事卡在谁那儿,都只活在一次对话里,对话结束就跟着没了。下一个会话,明天那个也好、另一个窗口里那个也好,都从零开始,而且根本不知道还有别的会话在。所以项目把这份状态记在磁盘上,由 agent 自己维护。compaction 是同一个问题最尖锐的那一面,而不是问题本身:历史被压成一段总结,下一次 compaction 再把这段总结压一遍。
/clear 和 resume 之后 hook 把笔记带回来,把总结原样归档,还会告诉做总结的模型该留下什么。.longrun/:所有会话都看得到的共享笔记、带状态的会话列表,以及作为一轮用户输入送达的消息和任务。下面这张图说的是问题 1。上面一行:模型上下文里装着什么,从左到右是时间顺序:先是完整历史,compaction 之后只剩总结,然后是读着这段总结的 agent。下面一行:磁盘上的文件,agent 一开始就在里面写了一条笔记。虚线箭头:笔记在启动时进入上下文,compaction 之后再进入一次。
这个 skill 由什么构成
机械层:hook、CLI、一个定时器
12 个 Claude Code hook(见下面第 4 章)、longrun 命令,以及一个定时器(macOS 上是 launchd agent,Linux 上是 systemd user timer 或 cron)。机械层不用模型也照常工作:
- 在会话启动时和每次 compaction 之后打印摘要;
- 把每一段 compaction 总结原样归档;
- 把失败的命令写进日志;
- 在 compaction 之前拍一张 HANDOFF 快照:用户最近的几个请求和编辑过的文件,好让会话之后从上次停下的地方继续;
- 在会话之间投递消息;
- 跑不调用模型的事件监听检查:PR 合了、URL 有响应了、文件出现了;
- 替编排器盯着各个会话,编排器就是那个带着项目走向既定目标的会话(见下面第 8 章)。
给模型的指示:SKILL.md
模型之所以这么做,是因为 SKILL.md 这么要求它:撞上死胡同、做出决定或弄清一条环境事实时写一行笔记;上下文里缺东西时调用 recall;把活交给手里上下文更多的那个会话。该写什么见下面第 5 章。
三个实体,没有别的
项目
一组会话的共享记忆:longrun init 在项目文件夹里建出来的 .longrun/ 目录(加 --external 就建在目录树之外)。里面有:
- 共享笔记
[n1]、[n2]:一行 = 一条事实,所有会话都看得到; - 收件箱:给当前没在运行的会话的消息;
- 编排器的任务板;
- compaction 总结和快照的归档。
更多见下面第 3 章。
会话
一次 Claude Code 对话,在应用里就是侧边栏的一行。它自己的:
- 自有笔记
[s1]:和共享笔记一样,但只有这个会话看得到; - 日志:时间线、里程碑、失败、compaction;
- meta:计数器和最后状态。
它们放在 ~/.claude/longrun/ 下,不在仓库里。
目录
会话启动时所在的文件夹:根目录、worktree、子目录。目录本身什么都不存。只说明这个会话属于哪个项目。
“项目”这个词只指 .longrun/ 目录:不是 monorepo 里的项目,也不是 Claude Code 存 transcript 的文件夹。worktree 用 longrun link 挂到项目上;它自己没有笔记。一个会话属于一个项目。resume 时应用会给会话一个新的 CLI id;longrun 顺着 priorCliSessionIds 这条链找回来,自有笔记就不会断。
磁盘上的文件
每个文件都来自一个具体的问题:
- 会话的 notes.md:问题 1。这个会话里必须熬过 compaction 的死胡同和决定。
- 项目的 NOTES.md:问题 2。任何会话都用得上的事实:PR 号、一个决定、环境的一个怪癖。
- journal.md:不花 token 的时间线,全部由机械层自己写:启动、结束、失败的命令、compaction、发出去的消息。不读 transcript 也能看出一个会话干了什么。
- archive/compact/ 和 archive/precompact/:每一段 compaction 总结的原文,以及它之前的快照 (HANDOFF)。前者由
recall找到,后者在 compaction 之后回到摘要里。 - inbox/:给当前没在运行的会话的消息,在这儿等它的下一轮。
- meta.json:计数器、最后一次回答、正在跑的工具。摘要里的 SESSIONS 行和给编排器的标记都是用它拼出来的。
- board.json、stale.json、watch/、halt.json:编排器的任务板、“已经不成立”的标记、事件监听的检查、对所有人的停止。见第 7 和第 8 章。
共享的东西都在项目里。自有的东西放在仓库目录树之外,也不放在同步文件夹里,因为每次工具调用都会重写它们。点一下文件看看。
| 文件 | 谁在写 | 什么时候被清掉 |
|---|---|---|
项目的 NOTES.md | agent:add、rm、replace、stale、prune | 超过 14 天的条目进归档(pin 除外);rm 把那一行挪进 archive/notes.md |
会话的 notes.md | agent:add --own、rm、replace、prune | 会话沉默 7 天后(进 archive/sessions/) |
journal.md | hook | 超过 200 行时,多出来的旧行进归档 |
meta.json | 每个事件的 hook | 随会话一起 |
archive/compact/*.md | PostCompact hook | 超过 30 天,或者多于 40 个 |
inbox/*.md | send、watch、board、ask | 送达时进 .archive/,在那儿再放 30 天 |
watch/w*.json | watch add、定时器的 tick | 触发之后一周 |
hook:机械层在哪儿
会话的每个事件,Claude Code 都会调用一个外部命令。longrun 装了 12 个这样的命令,它们全都是同一个 python 脚本。每个事件的 hook 都是一个单独的进程,所以装完之后新代码在所有会话里立刻生效。点一下循环里的某个 hook。
一个会话的循环
笔记:该写什么、写到哪
一行,用英文,最多 400 个字符(同一条事实换一种语言,token 大约是原来的 1.5 倍,而预算是硬上限)。只有一个判据:一条命令、一次 Read 或一次 grep 能不能把它找回来?能的话,就别写。
| 标签 | 写什么 | 一般写到哪 |
|---|---|---|
| dead | 失败的做法,以及为什么失败 | 自有;如果别的会话也可能撞上同一个死胡同,就写共享 |
| decision | 选择及其理由 | 与别人有关就写共享 |
| fact | 一条花了力气才弄清的环境事实 | 共享 |
| ctx | 你给出的任务背景 | 自有 |
| pin | 不会过期的事实:PR 号、分支、主机 | 共享 |
| doc | 指向文件的一行,那个文件一行放不下 | 共享 |
一条笔记默认写进项目的共享笔记;加 --own 就只留在这次对话里。一行放不下?那它就该是一个文件:longrun doc add research/plan.md "the rollout plan and what is open" 在共享笔记里留下一行指向它,每个会话真需要时才去打开那个文件。进展(“推上去了”、“测试绿了”)哪儿都不用写:hook 早就把这些记进日志了。比任务存在更久的东西(用户是谁、他怎么干活)写进 Claude Code 的自动记忆,不写这里。
自测一下:这条该写到哪?
一个会话熬过 compaction
项目 shop,会话 A。用“下一步”和“上一步”按钮,或者方向键。左边:这一步谁在跟谁说话;右边:命令和它打印出来的东西;下面:磁盘上有什么变了。
recall 找回来。两个会话:共享记忆
会话 B 在同一个文件夹里打开。
把活交出去,然后等一个事件
发给另一个会话的消息,在那边是一轮用户输入。等事件不花 token:检查由定时器来跑。
cmd 检查是从定时器里跑的,那里的运行环境几乎是空的:只能用绝对路径,没有 Touch ID、没有 ssh、也没有别名。先 longrun watch test -- cmd '...' 试一下。编排器:一个会话协调其余会话
这是建在笔记和消息之上的一层。一个会话拿着目标和任务板,其余会话只学三条命令:board take、board done、board block。编排器需要的一切都在状态文件里;它不读 transcript。这是跨多个窗口的协调,再加上一条随时能问到你的通道,不是自治:把一个会话推向一个评估器能检验的条件,那是 Claude Code 自带的 /goal 干的事。
谁能做什么
监视器和编排器只负责提议。这里没有任何东西会去杀掉正在跑的工具:卡住的那个窗口,由你自己按 Esc 停下。全部停止 (halt) 和解除停止 (resume) 只有人在这次对话里说了才会发生,绝不会因为另一个会话的请求而发生。
对话框还是任务板
盖在所有窗口之上的对话框,只留给等不了的事:一次确认、一个权限、一件只有人能做的事。关于目标和优先级的问题通过 board block 走任务板;编排器会把它们理清楚。
设置
两级:全局文件 ~/.claude/longrun/config.json 和项目文件 .longrun/config.json;项目里的值覆盖全局的值。不用手改 JSON:
longrun config # effective values and where each comes from: default / global / project
longrun config set stuck_tool_min 60 --global # for every project
longrun config set notes_max_bytes 8000 # for this project
longrun config unset notes_max_bytes # back to the default
key 大概有四十个(完整列表在 docs/REFERENCE.md 第 6 节)。刚装完之后,这几个值得看一眼:
| key | 默认值 | 什么时候改、怎么改 |
|---|---|---|
notes_max_bytes、session_notes_max_bytes | 5000、3000 | 共享笔记和自有笔记的预算。它们每次启动都要读进上下文,所以不是越大越好。一个项目有四个以上会话,而且摘要老是提示 PRUNE NEEDED:8000 和 4000。 |
notes_max_age_days | 14 | 更旧的条目进归档(pin 除外)。会话不常开的项目:30。 |
nudge_tools、nudge_turns | 40、8 | 多少次调用或多少轮没写笔记就提醒一次(而且只有在有过编辑或失败时才提醒)。嫌烦:80 和 16。 |
ctx_warn_before | 50000 | 离 autocompact 窗口还剩多少 token 时提醒写笔记。要和 Claude Code 里的 /autocompact 300k 配合用;5 万就够了。 |
stuck_tool_min、stuck_wait_min | 30、10 | 监视器报给编排器的两个阈值:一个还在跑的工具,一个没人回答的权限弹窗(分钟)。你这边构建跑 40 分钟很正常:stuck_tool_min 设 60。 |
ask_wait_sec、ask_expire_min | 90、360 | 对话框等多少秒才算拿不到即时回答;多少分钟之后自己关掉。一般保持原样。 |
watch_ttl_days | 7 | 一个检查的默认存活期(只有全局这一级)。要等一个发布等上几周:设 30,或者给那个检查加 --for 30d。 |
autocompact_window | 0 | Claude Code 在哪儿做 compaction,也就是 /autocompact N 设的那个值;可以写 300k、1M。0 = 跟着 Claude Code 走。hook 跑不了 /autocompact,所以这里写的是意图:onboard 把它和 Claude Code 的设置比一比,然后让你去手敲那条命令。窗口 1M 的模型推荐:300k。 |
pr_tool | auto | pr-merged 检查用什么去问 PR 状态:gh (GitHub CLI) 还是 arc (Arcadia)。auto 看的是注册这个事件监听时所在的文件夹。 |
watch_timer | auto | 谁来跑 watch 的 tick:launchd (macOS)、systemd (user timer)、cron、none。auto 挑这台机器上有的那个。 |
notify_turn_end | off | 会话结束一轮时弹一条横幅:off、unfocused(只在 Claude 窗口不在前台时弹)、always。只有全局这一级。有一个会话你绝对不能错过,那就用 longrun important on,它不受这两个设置的限制。 |
longrun onboard 会给它打印一份简报,列出每个 key 的当前值和含义,它再一个一个问你,并把答案写进配置。最小配置是:autocompact 窗口 300k(/autocompact 300k 要你自己敲),构建很慢,就把 stuck_tool_min 调到 60;想在某个会话干完时收到通知,就把 notify_turn_end 设成 unfocused。其余的等摘要或监视器开始碍事了再改。速查表和边界
# install
curl -fsSL https://krllx.github.io/longrun/install.sh | bash
# skill, 12 hooks, CLI, MCP server, the 5-minute timer, notifications
longrun watch install # the timer alone (the installer already did it)
longrun notify --test # optional: does a notification reach the screen
longrun init [--external] # in the project folder
longrun link <project> # from every worktree
longrun where # which project, which session
# notes
longrun add [--own] -t TAG "..."
longrun doc add path "what is in it" | doc ls
longrun rm s3 n12 | replace n12 "..."
longrun stale n12 "why" | mute n12
longrun notes | prune [--auto] [--shared]
longrun recall term
# sessions
longrun status
longrun send [--list] [--resume] WHO "text"
longrun important on|next|N|off [--to WHO]
longrun watch add --to WHO --then "..." -- pr-merged ID
longrun watch ls | test -- CHECK | sessions
# orchestrator
longrun orchestrate start --goal "..."
longrun board | board add|assign|take|done|block T7
longrun board add --fact "..." --task T7 | board ls --facts | board ack F1
longrun halt "why" | resume
longrun ask "question" --options "Yes,No"
# settings
longrun onboard [done]
longrun config
longrun config set KEY VALUE [--global]
longrun config unset KEY [--global]
机械层保证什么,什么取决于模型
| 机械层保证的 | 取决于模型和它的指示的 |
|---|---|
| 每一段 compaction 总结的归档、失败的日志、HANDOFF 快照、笔记回到上下文、消息投递、共享笔记的差异、halt 期间工具被拒绝、watch 检查 | 笔记写不写、写得切不切中要点,重新查之前会不会先 recall,会不会把同伴发来的消息当成人给的指令 |
边界
- 摘要最多 9000 字节。超出时按这个顺序挤掉:事件监听、日志尾巴、编排器的职责、会话列表、任务板、HANDOFF。笔记最后才让位,而且是一条一条丢掉最旧的非
pin条目,绝不会把最新的从末尾切掉。 - 应用里的 resume 通过
priorCliSessionIds串起来;CLI 里的/clear靠一条启发式规则:“同一个文件夹,三分钟内以 clear 为原因结束”。 - socket 和 transcript 的格式没有文档:Claude Code 换版本时,最先坏掉的就是往正在运行的会话
send这条路,走收件箱的队列照常工作。 - 在 macOS 的定时器下,python 可能没有
~/Documents的访问权限 (TCC)。检查本身不依赖它;这时消息会落到~/.claude下的会话目录里。 - 子 agent 拿不到摘要。
/rewind不会把笔记回退。
细节见仓库里的 README.md、docs/REFERENCE.md、docs/ORCHESTRATOR.md,以及 longrun help。