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 章。

第 0 章

快速开始:装好,然后照常干活

这个 skill 假定人还是照常在会话里干活,而笔记、摘要和消息投递由 hook 以及照着指示走的模型负责。下面是你需要亲手做的全部事情。

  1. 装一次就好。一行命令,不需要 checkout,安装脚本自己去取源码:
    curl -fsSL https://krllx.github.io/longrun/install.sh | bash
    它装上 skill、~/.claude/settings.json 里的 12 个 hook、CLI ~/.local/bin/longrun、longrun MCP 服务器,以及一个定时器,每五分钟跑一次检查和监视器。它只问你一件事:桌面通知,也就是下一步。从 clone 下来的仓库安装,用的也是同一个 install.sh;--uninstall 撤销一切。
  2. 通知,想要就装。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 窗口就在前台,按前台窗口判断的那条规则就不会发通知。
  3. 把文件夹变成项目。对 longrun 来说,项目就是你在 Claude Code 里打开的那个文件夹。不一定是仓库根目录:可以是装着好几个仓库的文件夹、monorepo 的一个子目录,或者一个空的指挥部 (hq) 文件夹。在那里跑一次 longrun init:在终端里跑,或者直接在 Claude Code 会话里说一声,agent 会替你跑。一个 .longrun/ 目录会出现;如果这个文件夹在仓库里,把它加进 ignore 文件(或者用 longrun init --external,它把目录放到 ~/.claude/longrun/ 下)。同一个项目的每个 worktree 里:longrun link <project>。检查一下:longrun where。
  4. 调一调。在会话里对 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 章。
  5. 干活。不用调用任何命令。会话启动时拿到摘要(共享笔记、自有笔记、还有谁在干活),compaction 之后一切自己回来,其他会话的改动在下一轮送到。agent 自己会把死胡同和决定写成笔记。你可以用平常的话说:“记一下”、“我们都试过什么”、“会话状态”、“把这个交给另一个会话”、“还剩什么”、“PR 合了告诉我”。
  6. 多个会话,一个共同目标。在其中一个里说:“接过编排器的角色,目标是……”。它会建一块任务板并派活;其他会话把任务当作一轮用户输入收下,什么都不用学。“让所有人停下”和“继续”同样用平常的话说,在编排器会话里说,见下面第 8 章。
一条命令做诊断:longrun where(在哪个项目、哪个会话)、longrun status(每个会话在做什么)、longrun watch status(定时器还活着吗)、longrun notify --test(通知到得了屏幕吗)、longrun config(生效的设置)。
第 1 章

为什么

项目真正的状态,试过什么没成、定下了什么、什么事卡在谁那儿,都只活在一次对话里,对话结束就跟着没了。下一个会话,明天那个也好、另一个窗口里那个也好,都从零开始,而且根本不知道还有别的会话在。所以项目把这份状态记在磁盘上,由 agent 自己维护。compaction 是同一个问题最尖锐的那一面,而不是问题本身:历史被压成一段总结,下一次 compaction 再把这段总结压一遍。

问题 1:状态随着对话一起消失死胡同、决定和费劲才弄清的事实,只存在于这个窗口的历史里。最先暴露出来的是 compaction:总结丢掉“我们试过什么、为什么没成”,半小时后 agent 又提出了自己已经回退掉的改法。
问题 2:多个会话各自活在自己的窗口里。一个已经开了 PR,另一个不知道,还在说“PR 还没建”。一个撞过的死胡同,另一个再撞一次。
问题 3:等待“PR 合了告诉我”要么变成轮询循环,每次醒来都烧 token,要么变成一段定时的 sleep,醒来时事件不是早已发生,就是还没到。
问题 4:多个会话,一个目标五个会话在一个项目上,只有人自己知道什么做完了、什么卡住了、下一步是什么。每一次交接都要经过人。
解法 1:磁盘上的文档磁盘上的笔记不会退化,写什么由 agent 自己定。每次 compaction、/clear 和 resume 之后 hook 把笔记带回来,把总结原样归档,还会告诉做总结的模型该留下什么。
解法 2:消息和任务每个项目一个 .longrun/:所有会话都看得到的共享笔记、带状态的会话列表,以及作为一轮用户输入送达的消息和任务。
解法 3:事件监听定时器每五分钟检查一次条件,不调用模型,条件成立就唤醒会话。可靠、及时、还不花钱。
解法 4:编排器一个会话维护任务板、派发任务、点出卡住的那个同伴,只在必要时才通过对话框问人。

下面这张图说的是问题 1。上面一行:模型上下文里装着什么,从左到右是时间顺序:先是完整历史,compaction 之后只剩总结,然后是读着这段总结的 agent。下面一行:磁盘上的文件,agent 一开始就在里面写了一条笔记。虚线箭头:笔记在启动时进入上下文,compaction 之后再进入一次。

模型上下文随时间的变化 1. 历史:试过 X,没成,回退了,改用 Y…… 2. 总结:“做了 Y” 3. agent:“来试试 X 吧!” 会话在磁盘上的笔记文件 (longrun) [s1] dead: X fails: it needs a real Redis, use REDIS_URL=fake:// 一条笔记:[s1] 是它的编号(s = 自有,n = 共享),dead 是“死胡同”标签,后面一行就是事实本身
总结会丢掉原因。磁盘上的一行笔记只花 26 个 token,而且每次 compaction 之后都回来,所以在第 3 步 agent 看得到 X 已经试过了。

这个 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 章。

第 2 章

三个实体,没有别的

项目

一组会话的共享记忆: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 这条链找回来,自有笔记就不会断。

第 3 章

磁盘上的文件

每个文件都来自一个具体的问题:

  • 会话的 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.mdagent:add、rm、replace、stale、prune超过 14 天的条目进归档(pin 除外);rm 把那一行挪进 archive/notes.md
会话的 notes.mdagent:add --own、rm、replace、prune会话沉默 7 天后(进 archive/sessions/)
journal.mdhook超过 200 行时,多出来的旧行进归档
meta.json每个事件的 hook随会话一起
archive/compact/*.mdPostCompact hook超过 30 天,或者多于 40 个
inbox/*.mdsend、watch、board、ask送达时进 .archive/,在那儿再放 30 天
watch/w*.jsonwatch add、定时器的 tick触发之后一周
第 4 章

hook:机械层在哪儿

会话的每个事件,Claude Code 都会调用一个外部命令。longrun 装了 12 个这样的命令,它们全都是同一个 python 脚本。每个事件的 hook 都是一个单独的进程,所以装完之后新代码在所有会话里立刻生效。点一下循环里的某个 hook。

一个会话的循环

启动,然后是一轮又一轮:人的请求、工具调用、回答。上下文满了就 compaction,然后又是 SessionStart。每个节点上都有 hook。
hook 里唯一会进到模型上下文的东西:SessionStart 上的摘要、UserPromptSubmit 上的共享笔记差异和消息、偶尔一次让写笔记的提醒、一条上下文警告,以及 halt 期间被拒绝的工具调用。其余一切都悄悄写进文件。
第 5 章

笔记:该写什么、写到哪

一行,用英文,最多 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 的自动记忆,不写这里。

自测一下:这条该写到哪?

第 6 章,一个场景

一个会话熬过 compaction

项目 shop,会话 A。用“下一步”和“上一步”按钮,或者方向键。左边:这一步谁在跟谁说话;右边:命令和它打印出来的东西;下面:磁盘上有什么变了。

人、对话框会话项目 .longrun会话自有定时器全局
要记住的是:死胡同和决定当场就写,一行放不下的东西留在文件里、笔记里只放一行指向它,compaction 之后一切自己回来,丢掉的细节用 recall 找回来。
第 7 章,两个场景

两个会话:共享记忆

会话 B 在同一个文件夹里打开。

把活交出去,然后等一个事件

发给另一个会话的消息,在那边是一轮用户输入。等事件不花 token:检查由定时器来跑。

cmd 检查是从定时器里跑的,那里的运行环境几乎是空的:只能用绝对路径,没有 Touch ID、没有 ssh、也没有别名。先 longrun watch test -- cmd '...' 试一下。
第 8 章,一个场景

编排器:一个会话协调其余会话

这是建在笔记和消息之上的一层。一个会话拿着目标和任务板,其余会话只学三条命令:board take、board done、board block。编排器需要的一切都在状态文件里;它不读 transcript。这是跨多个窗口的协调,再加上一条随时能问到你的通道,不是自治:把一个会话推向一个评估器能检验的条件,那是 Claude Code 自带的 /goal 干的事。

谁能做什么

监视器和编排器只负责提议。这里没有任何东西会去杀掉正在跑的工具:卡住的那个窗口,由你自己按 Esc 停下。全部停止 (halt) 和解除停止 (resume) 只有人在这次对话里说了才会发生,绝不会因为另一个会话的请求而发生。

对话框还是任务板

盖在所有窗口之上的对话框,只留给等不了的事:一次确认、一个权限、一件只有人能做的事。关于目标和优先级的问题通过 board block 走任务板;编排器会把它们理清楚。

第 9 章

设置

两级:全局文件 ~/.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_bytes5000、3000共享笔记和自有笔记的预算。它们每次启动都要读进上下文,所以不是越大越好。一个项目有四个以上会话,而且摘要老是提示 PRUNE NEEDED:8000 和 4000。
notes_max_age_days14更旧的条目进归档(pin 除外)。会话不常开的项目:30。
nudge_tools、nudge_turns40、8多少次调用或多少轮没写笔记就提醒一次(而且只有在有过编辑或失败时才提醒)。嫌烦:80 和 16。
ctx_warn_before50000离 autocompact 窗口还剩多少 token 时提醒写笔记。要和 Claude Code 里的 /autocompact 300k 配合用;5 万就够了。
stuck_tool_min、stuck_wait_min30、10监视器报给编排器的两个阈值:一个还在跑的工具,一个没人回答的权限弹窗(分钟)。你这边构建跑 40 分钟很正常:stuck_tool_min 设 60。
ask_wait_sec、ask_expire_min90、360对话框等多少秒才算拿不到即时回答;多少分钟之后自己关掉。一般保持原样。
watch_ttl_days7一个检查的默认存活期(只有全局这一级)。要等一个发布等上几周:设 30,或者给那个检查加 --for 30d。
autocompact_window0Claude Code 在哪儿做 compaction,也就是 /autocompact N 设的那个值;可以写 300k、1M。0 = 跟着 Claude Code 走。hook 跑不了 /autocompact,所以这里写的是意图:onboard 把它和 Claude Code 的设置比一比,然后让你去手敲那条命令。窗口 1M 的模型推荐:300k。
pr_toolautopr-merged 检查用什么去问 PR 状态:gh (GitHub CLI) 还是 arc (Arcadia)。auto 看的是注册这个事件监听时所在的文件夹。
watch_timerauto谁来跑 watch 的 tick:launchd (macOS)、systemd (user timer)、cron、none。auto 挑这台机器上有的那个。
notify_turn_endoff会话结束一轮时弹一条横幅:off、unfocused(只在 Claude 窗口不在前台时弹)、always。只有全局这一级。有一个会话你绝对不能错过,那就用 longrun important on,它不受这两个设置的限制。
最省事的办法不是背 key,而是对 agent 说一句 "set up longrun":longrun onboard 会给它打印一份简报,列出每个 key 的当前值和含义,它再一个一个问你,并把答案写进配置。最小配置是:autocompact 窗口 300k(/autocompact 300k 要你自己敲),构建很慢,就把 stuck_tool_min 调到 60;想在某个会话干完时收到通知,就把 notify_turn_end 设成 unfocused。其余的等摘要或监视器开始碍事了再改。
第 10 章

速查表和边界

# 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。