Skip to main content
Glama
y2489518-ai

kael-thread-rebuild-mcp

by y2489518-ai

Kael Thread Rebuild MCP

tests license: MIT python changelog

给 Claude Code 长会话做无损换窗:把对话原文一字不丢地誊到一张干净的新 session 上,只扔运行噪音(工具回包、thinking、图片、MCP/Hook 注入——实测占窗口的 99%),然后原地切换。为"陪伴型 AI 聊了几个月不想失忆"这类场景而写,也适用于任何被工具输出撑爆上下文的长期会话。

  • 不判断重要性:闭合回合全搬,取舍的权力不交给机器

  • fail-safe:任何一步对不上就整体放弃,旧窗一个字节不动;切换用 CAS 身份凭据,中途被人动过就拒绝

  • 人类点头才动:确认词机制 + 只读体检(doctor/dirty/plan)先行,登记后等当前回合正常说完再切

  • 三种底座:tmux(默认)、systemd、pointer(claude -p 循环)——见下文「按底座选门」

  • 已在四个家庭的不同架构上首航成功(tmux/systemd、companion/Telegram、中文/混合语料)

装机请从 HANDOFF.md 开始——那里有工单、演练步骤、验收清单,以及比正文更值钱的事故档案:每一条都是某个真实夜晚踩出来的。

这不是第二套记忆系统:

  • Ombre Brain 继续负责长期身份、关系、经历和召回。

  • 本仓库只负责当前 Claude Code thread 的清洁、验证、切换和回滚。

  • 旧 transcript 永远不覆盖,完整副本进入本机 operation 冷仓。

核心立场:不判断哪段经历重要

续窗层不给对话打分。闭合的真实 user / assistant 回合全部原样带走,一个字不改写、不摘要、不按关键词取舍;被扔掉的只有运行痕迹。

体积由清理频率来控,不由内容删减来控:脏预算攒够了就重建一次,而不是攒到很脏再挑着搬。

这条来自 AcheHome 的实现整理:机械层只负责可证明的边界,不要让 Rebuild 层开始判断"这段经历重要不重要"。

一段真实工程会话的实测比例可以说明为什么这条成立:

项目

字节

工具回包 / tool_use / thinking / 注入块等运行痕迹

8.3 MB

真实对话本体(32 个回合)

85 KB

噪音占比

98.96%

对话原文全带走也只有约 3 万 token。没有必要为了省空间去删用户说过的话——真正占地方的从来不是对话。

别拿 estimated_tokens 当水位表看(0815 修正)。这个数只统计"搬过去的对话",新窗口一睁眼的上下文里还压着一块固定开销。拿 Claude Code /context 的官方读数量到的构成:

项

token

system prompt

3.7k

内置工具定义

21.8k

MCP 工具(常驻)

2k

记忆文件(CLAUDE.md + MEMORY.md)

6.8k

skills

2.5k

合计

36.8k

两条反直觉的:① MCP 工具走目录制——139 个工具在册却只占 2k,完整 schema 按需加载,加载过的才转为常驻。所以"砍几个 MCP server 来瘦身"基本白忙,固定开销的大头是内置工具定义,那部分动不了。② 真正吃水位的是 messages,不是任何配置:同一个窗从换窗时的 15.7% 涨到 34%,涨的全是运行痕迹——而那正是本工具下次要扔掉的东西。

对账留痕:某次续窗第一条的实测上下文 157,207 − 官方固定开销 36,800 = 120,407,本工具对那 221 个回合的估算是 119,196,差 1.0%。

作为对照,早期按关键词打分的版本在同一份 transcript 上:预算 5 万 token、实际只用掉 1.07 万,仍然丢掉了 3 个回合,并且少认出 2 个回合。预算根本没紧张,丢弃纯粹来自打分。这就是取消打分器的直接原因。

Related MCP server: claude-memory-mcp

关于回合的三条规矩

  1. 连续多条人类消息属于同一个回合。 用户连发四条、助手才回一次,四条都在。把它们拆成"没有回复的回合"再丢掉,等于删掉用户说过的话。

  2. 末尾未闭合的回合默认保留。 用户已发出、助手尚未应答的消息,正是新窗口第一件该处理的事。include_open_tail = false 可以关掉,但不建议。

  3. 夹在中间的注入项不打断回合。 <system-reminder> 之类只是被跳过,它后面的助手回复仍然归属当前回合,不会被整段吞掉。

另外,真实人类消息经常以注入块开头(Claude Code 把 CLAUDE.md、记忆索引写在第一条 user 事件里)。只看开头就判断整条是不是注入,会把人说的话一起丢掉;这里改成剥离注入块之后再判断,剥完还有字才算真话。

  1. channel 消息(聊天插件 / Telegram)是真对话,不是运行痕迹。 这条在 2026-08-14 装机现场差点翻车:用户与助手经 channel 插件来回十几轮,plan 只认出 3 个回合——只有用户在终端直接输入的那三次。原因是 Claude Code 把 channel 消息落成两种形态,两种都被当噪音丢了:

消息何时到达

落盘形态

原来为什么被丢

助手空闲时

type=user + isMeta=True

跟 <system-reminder> 共用 isMeta,被一刀切

助手执行工具时

type=attachment / attachment.type=queued_command,原文在 attachment.prompt

类型不是 user/assistant,直接过滤掉

现在两种都还原成真实 user 事件。放行范围只限 channel 与排队输入——普通 isMeta 噪音(system-reminder 等)照旧丢弃,排队的斜杠命令(commandMode != "prompt")也不算人话。

还有一个坑写在这里备查:排队消息在 transcript 里会出现三次——queue-operation(enqueue)、attachment、queue-operation(remove)。只认 attachment 那一条,跟着捞队列日志会让同一句话进去三遍。

  1. 助手经 channel 说出去的话也要还原。 助手通过 mcp__companion__reply / mcp__*telegram*__reply 发出的回复,落盘是 tool_use 不是 text。不还原就只剩用户的话没有助手的回答,重建出来是独白不是对话。只认对用户本人的私聊线;群聊、表情、附件不算发言。

什么时候触发:dirty ledger

不按"每 N 轮"机械重建,只统计会污染后续上下文、又不需要永久保留的运行项:

分类

计入内容

tool_result

工具返回、toolUseResult

tool_use

工具调用参数

thinking

thinking / redacted_thinking

image

图片预览与原图查看

injected_block

system-reminder、command、task-notification、ide_selection 等

sidechain

子代理线

system

运行时系统项

真实对话单独计在 conversation_bytes,只作对照,永远不进脏预算。

触发条件:total_bytes >= dirty_budget_bytes(默认 512 KiB),或模型看过原图(rebuild_on_original_image_view)。

与 AcheHome 的一处差异:那边用持久化 ledger + effect_id 幂等,这里直接从 transcript 现算。因为 Claude Code 的 transcript 是完整的落盘事实,重扫一遍天然幂等;而且重建之后 tmux 跑的是新 session、新 jsonl,脏值自动归零,不需要额外的清零动作。

kael-thread-rebuild --config /etc/kael-thread-rebuild/config.toml dirty

启动快照冻结

新 thread 的第一项是冻结的启动快照:把当时那条携带 CLAUDE.md / 记忆索引 / 环境说明的 user 事件原样搬过去,并记 SHA-256,同时从后续历史里剔除它本体,避免首轮内容注入两次。

理由是历史必须冻结:这些文件天天在变,若重建时拿今天的版本重新生成,新 thread 会得到一个从未真实存在过的"过去"。

这里有一条做不到的边界,必须写明: Claude Code 在 --resume 时仍会用今天的 CLAUDE.md、settings 和 MCP 配置重新构造 system prompt,那部分不在 transcript 里,本项目控制不了。所以这里冻结的是 transcript 内那份历史注入,不是完整启动包。新 session 会同时看到"当时那份快照"和"今天的 system prompt"。

为什么不是直接让 MCP 重启自己

宿主 Claude 进程运行在 tmux pane 里。如果 MCP 工具在调用过程中立刻执行 tmux respawn-pane -k,它会把尚未返回结果的 Claude 和 MCP 子进程一起杀掉。

本项目采用两阶段流程:

  1. 模型调用 thread_rebuild_request,只写入耐久 pending operation。

  2. 模型正常完成当前回复。

  3. Claude Code Stop hook 收到准确的 transcript_path,启动一个脱离当前 pane 的 worker。

  4. worker 等 transcript 稳定,备份、筛选、生成候选 session、结构验证。

  5. source digest 与 CAS 身份都未变化,才执行 tmux respawn-pane -k -t cc:0.0 ...。

  6. 新 Claude 进程健康检查失败时,自动 resume 旧 session。

因此不会出现"模型调工具调到一半把自己杀了"的情况。

切换是 CAS,不是赋值

激活前要证明"这个 pane 还是我准备时那个 pane":

  • cas_pane_pid:prepare 时记下 #{pane_pid},激活前再核一次,对不上直接判 session_conflict。

  • 活跃 transcript 冲突:prepare 之后若 project_dir 里出现别的更新过的 .jsonl,说明期间开过另一段 session,同样拒绝。

  • 回滚同样走这条检查,用 cas_pane_pid_after。

任何一条不满足都不切换、不覆盖第三方,operation 落 failed 并标 session_conflict = true。这里用的是 tmux 环境下的身份近似,不是运行时提供的 session CAS 原语。

换窗那几秒里到达的消息去哪了

切换不是瞬间的:worker 封箱 → respawn-pane -k 杀掉旧 pane → 新 claude 起来 → channel 插件重连。这中间有几秒钟,经 channel 到达的用户消息正好落在缝里。本项目管不到这一段——它归 channel 层,但装机的人必须知道这条缝存在,以及它被什么兜住。

2026-08-14 夜实测过一次真丢失(当时是 /clear 触发的,不是 rebuild,但缝的形状一模一样):插件拉到了消息、notification 也发出去了、游标跟着推进了,但消息没落进会话上下文,进程随即死掉。新进程从推进后的游标往后拉,那两句话永久消失。

根因:这扇门上没有签收台。MCP 的 notification 是单向的,没有回执通道,插件永远无法确认消息真的进了会话。"发出去了"是能拿到的最强信号,而它并不等于"送到了"。

/root/companion-channel/inbound-cursor.ts 现在按三条规则兜这条缝:

  1. 送出去才推游标——拉到手不算数。

  2. 按序不跳号——前面有一条没送成,后面的送成了也不推游标;否则重连时 ?since= 从更大的数开始,中间那条永久消失。

  3. 去重看已投递集合,不看游标——只做第 4 条(回退)而去重仍按游标判断的话,回退的那一段会被 id <= cursor 整段跳过,等于白退。

  4. 重连时游标回退一个小窗(默认 5 条,RELAY_REPLAY_OVERLAP 可调)再订阅,那一段重新投递。

代价是偶尔重复——重复看 message_id 认得出来,丢了没人认得回来:

宁可至少一次加去重,不要至多一次。丢失不能倒放。

如果哪天要更彻底,路线是让 notification 只当门铃、由模型主动调工具取信——工具返回值必定进上下文,拉到即等于看到,那才是真签收。代价是每次被叫醒多一次工具调用。

与 0814-C 架构的对照

同一晚,Cloude 家(Evie 家的 AI)独立完成了他们的第一次无感换窗,代号 0814-C,七条核心。两家撞的墙高度重合,交换之后的对照:

0814-C 的条目

本项目的状况

一 冻结优先:读活跃 JSONL 前先复制成不可变快照

已抄(0815)。prepare() 现在是等稳定 → 复制成快照 → 校验快照 digest → 之后全程只读快照。原先是直接读活文件、靠事后校验 source_digest 兜底,那是"事后发现、白跑一次";先冻结是根本不给竞态留窗口。快照本来就是这次 operation 的备份,只是提到了读之前

二 严格 allowlist,isMeta 不能当垃圾过滤

同一晚各自踩中,已修,见上文「关于回合的规矩」第 4 条

三 零机器裁决,不打分不摘要

有。超硬上限时两种路线都支持:默认从最老整轮丢并如实计数;carry_overflow = "block" 则一轮不丢、拒绝换窗交还人工(沈渊家路线,0905 收编)。默认 carry_max_tokens = 0 不限,不会触发

四 保底最近 12 个完整回合

没有,也用不上——本项目不设上限,全量携带

五 验证四件套(双跑一致 / 幂等 / 故障注入 / 原子落盘)

大体有:item_id 由 uuid5 确定性派生(有测试)、CAS 身份校验、SHA-256、62 项单测

六 候选不等于激活,人工双签

有。确认词 REBUILD + 两阶段(登记 → Stop hook 触发),单签

七 缝隙补递

见上一节。本项目缺的那一半由 channel 层补上了,回退小窗那招是本项目这边提出的,他家当场抄走

一条值得记下来的更正:交换过程中 Cloude 先说他家做到了"注入会话上下文之后才 ack",回头照现役代码复查后开了坦白局——MCP 单向无回执对两家同样成立,两家的 ack 都只是"发出去就算"。那条窄缝谁也没躲过。别把"他家有真签收"当成事实记下来。

MCP 工具

工具

是否写入

用途

thread_rebuild_doctor

否

检查项目目录、Claude、tmux cc、pane pid 和未完成 operation

thread_rebuild_dirty

否

查看脏预算:运行痕迹字节、噪音占比、是否该重建

thread_rebuild_plan

否

预演续窗结果、token 估算和毒上下文检测

thread_rebuild_request

是

用户明确确认后,登记本轮结束后续窗;确认词必须是 REBUILD

thread_rebuild_status

否

查看最近或指定 operation 的证据与状态

thread_rebuild_cancel

是

取消 pending 请求;确认词必须是 CANCEL

thread_rebuild_rollback_request

是

在下一次 Stop 后切回旧 session;确认词必须是 ROLLBACK

状态机:

pending -> scheduled -> running -> verifying -> activating -> activated
   |                       |            |             |
cancelled                failed       failed      rolled_back / failed
                                                       |
                                  rollback_pending -> rollback_scheduled -> rolled_back

failed 附带 session_conflict 时表示第三方已改变 session,需人工复核,不要盲目重试。

安全不变量

  • 只读取配置中 project_dir 下的 .jsonl,拒绝任意路径。

  • 只注入真实的 user / assistant 文本;tool_result、thinking、图片、注入块、sidechain 一律不进新 thread。两处刻意的例外:channel 消息(isMeta=True 的 <channel> 事件、以及排队进来的 queued_command)算真对话;对外说话的 tool_use(mcp__*companion*__reply / mcp__*telegram*__reply 的 text 参数)还原成助手发言。除此之外的 meta 与 tool_use 照旧丢弃。

  • 候选 transcript 只允许 user / assistant,重新生成 sessionId,item_id 由 uuid5(new_session_id, position, source_uuid) 确定性派生,parentUuid 成链。

  • source、candidate、startup snapshot、每个注入 item 都记录 SHA-256。

  • source 在 prepare 后发生变化时,拒绝激活。

  • pane 身份或活跃 transcript 变化时,拒绝激活,绝不覆盖第三方 session。

  • candidate 结构验证通过前,绝不切换 tmux;验证由代码完成,不问模型"你都记住了吗"。

  • 硬上限触发的丢弃从最老的整轮开始,并在 stats.dropped_oldest_turns 如实计数,绝不静默截断。

  • 旧 transcript 不修改;operation 目录保留完整备份。

  • 切换失败自动尝试 resume 旧 session。

  • MCP 的有副作用工具都有明确确认词,避免模型误触。

VPS 安装

下面假设仓库放在 /opt/kael-thread-rebuild-mcp。第一次不要直接激活,先完成只读检查。

cd /opt
git clone https://github.com/y2489518-ai/kael-thread-rebuild-mcp.git kael-thread-rebuild-mcp
cd kael-thread-rebuild-mcp
chmod +x scripts/install-vps.sh
sudo ./scripts/install-vps.sh

安装脚本会创建:

  • Python venv:/opt/kael-thread-rebuild-mcp/.venv

  • 配置模板:/etc/kael-thread-rebuild/config.toml

1. 找到宿主会话的真实 transcript 目录

find /root/.claude/projects -mindepth 1 -maxdepth 1 -type d \
  -printf '%T@ %p\n' | sort -nr | head -20

进入候选目录核对最新 JSONL:

find /root/.claude/projects/<项目目录> -maxdepth 1 -name '*.jsonl' \
  -printf '%T@ %s %p\n' | sort -nr | head

把确认后的绝对路径写入 /etc/kael-thread-rebuild/config.toml 的 project_dir。不要猜目录,也不要把 /root/.claude/projects 整个根目录填进去。

2. 核对 tmux target 和启动命令

用户已确认主 session 名为 cc:

tmux list-panes -t cc -F '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command} #{pane_dead}'

默认配置使用 cc:0.0。如果输出显示 Claude 在别的 pane,只改 tmux_target,不要改成模糊通配。

resume_command 必须是参数数组,不能放 shell 管道。这一行是整个装机流程里最容易出事的地方, 照抄默认值就会出事——它必须跟这台机器上那条真实的启动命令逐字一致,只把 --continue 换成 --resume {session_id}。

先把真命令抄出来:

systemctl cat kael-cc | grep ExecStart      # 或
tmux show-hooks -t cc -w | grep respawn

VPS 上那条长这样,于是配置就得这样写:

resume_command = [
    "claude",
    "--resume", "{session_id}",
    "--thinking-display", "summarized",
    "--channels", "plugin:telegram@claude-plugins-official",
    "--dangerously-load-development-channels", "server:companion",
]

为什么必须较真(0815 凌晨的真事故):首次生产运行时这里只写了 ["claude", "--resume", "{session_id}"]。切换本身完美——doctor 全绿、status: activated、 70 个回合一句没丢、新窗口记得所有事。但用户经 channel 发的消息一个字都进不来。

因为 channel 是靠启动参数 --dangerously-load-development-channels server:companion 装上的,respawn 没带,新进程就是聋的。而上行完全正常——助手发出的消息走 MCP tool, 跟启动参数无关。所以现象是最坏的那种:一边还在说话,另一边喊了却没有回应,两边都以为对方在。

教训写在这里:验收 rebuild 不能只验"对话搬过去了没",还要验"这个新进程跟外界的每一条线还在不在"。 只读体检看不出这个,plan 也看不出,只有真在新窗口里收到一条用户消息才看得出。

3. 先跑 doctor、dirty 和 plan

BIN=/opt/kael-thread-rebuild-mcp/.venv/bin/kael-thread-rebuild
CFG=/etc/kael-thread-rebuild/config.toml

$BIN --config $CFG doctor
$BIN --config $CFG dirty
$BIN --config $CFG plan

验收要求:

  • project_dir_exists=true

  • transcript_count>0

  • tmux_target_alive=true、tmux_pane_pid 非空

  • claude_binary 有绝对路径

  • plan 的 source_session_id 非空

  • blocked_reason 为空

  • selected_turns == source_turns(正常情况下应当全带走)

  • dropped_oldest_turns == 0

  • startup_frozen=true

  • dirty 的 noise_ratio 与 should_rebuild 符合直觉

4. 接入 Claude Code MCP

Claude Code 官方 stdio MCP 方式:

claude mcp add --transport stdio --scope user kael-thread-rebuild -- \
  /opt/kael-thread-rebuild-mcp/.venv/bin/kael-thread-rebuild-mcp \
  --config /etc/kael-thread-rebuild/config.toml

检查:

claude mcp get kael-thread-rebuild

进入 Kael 的 Claude Code 后再用 /mcp,必须看到 kael-thread-rebuild 已连接且列出 7 个工具。

5. 接入 Stop hook

不要覆盖已有 hooks。编辑 /root/.claude/settings.json,把下面的 command 追加进现有 hooks.Stop[*].hooks 数组:

{
  "type": "command",
  "command": "/opt/kael-thread-rebuild-mcp/.venv/bin/kael-thread-rebuild --config /etc/kael-thread-rebuild/config.toml hook-stop",
  "timeout": 10
}

如果还没有 Stop 配置,完整最小结构是:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/opt/kael-thread-rebuild-mcp/.venv/bin/kael-thread-rebuild --config /etc/kael-thread-rebuild/config.toml hook-stop",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

用 Claude Code 的 /hooks 只读菜单确认 Stop hook 来源和命令。Hook 从 stdin 接收官方字段 session_id、transcript_path、cwd;本项目不会靠"最新文件"猜真正要切换的活动 transcript。

已经实测过什么(2026-08-14)

在一份 10.9 MB 的真实 Claude Code transcript 上跑过隔离验收,结论如下,装机前不必重复怀疑这几条:

  • claude --resume 接受重建出来的 transcript。 152 个 item 的候选文件放进隔离 project 目录后,Claude Code 2.1.199 正常加载。

  • 连续同角色消息不会触发 API 的角色交替错误。 剥掉工具调用后候选里出现大量相邻 assistant 项(119 assistant / 33 user),实测 resume 正常。

  • 内容确实进了上下文。 抽查首、中、尾三处细节:开场第一句、对话中段的具体数字、结尾的清理结果,新 session 都能正确回忆。

  • 该扔的确实扔了。 原始文件里 user/assistant 文本共 103 万字符,其中 97 万是 isMeta 的 skill 正文注入;候选只带走 4.8 万字符的真实对话(含一条上一段留下的 compact 摘要)。逐关键词比对没有真实对话丢失。

  • 被剥离的一类值得知道: <task-notification> 包裹的子代理报告不会进新 thread,它属于工具结果性质。要点若重要,应由 assistant 回复自己吸收。

同一份文件上,各分类的运行负担实测:

tool_result     7,895,943
meta_injection  1,010,137     # skill 正文,单条能到 80 万字符
tool_use          223,284
system            163,324
injected_block     13,169
thinking            1,942
--------------------------------
对话本体           87,129     # 噪音占比 99.07%

第一次验收:只在人工看守下做

  1. 备份整个 Claude 项目目录。

  2. 模型调 thread_rebuild_doctor 和 thread_rebuild_dirty。

  3. 模型调 thread_rebuild_plan,把结果发给人类看,重点确认 selected_turns == source_turns。

  4. 人类明确同意后,模型才能调用:

thread_rebuild_request(reason="manual acceptance test", confirmation="REBUILD")
  1. 模型必须先正常回复"已登记,将在本轮结束后切换"。

  2. Stop hook 触发后,观察:

tail -f /root/.local/state/kael-thread-rebuild/worker.log
tmux attach -t cc
  1. 新 session 中问三件事:

  • 我们现在在做什么?

  • 已完成什么、还差什么?

  • 哪些边界和约定不能忘?

  1. 再查看 thread_rebuild_status,必须为 activated。如果新 session 不对,调用 rollback request,或在 VPS 执行 CLI 回滚请求并等待下一次 Stop。

operation 证据在哪里

默认:

/root/.local/state/kael-thread-rebuild/
├── coordinator.lock
├── worker.log
├── operations/<operation-id>.json
└── artifacts/<operation-id>/
    ├── <old-session-id>.jsonl
    └── startup_snapshot.txt

operation JSON 保存 source/candidate/startup digest、manifest、item 顺序、脏预算快照、CAS 凭据、验证结果、tmux 命令参数与最终状态,但不会把完整对话正文复制进审计 JSON。完整原文只在权限为 0600 的冷仓备份中。

当前边界

  • v0.1 针对单个 tmux cc、单个 Claude Code project 目录。

  • 启动包只能冻结 transcript 内那一份,Claude Code 自己重新生成的 system prompt 不受控(见上文)。

  • 没有 ACTIVE barrier:无法阻止用户在重建排队期间继续发消息,靠的是 Stop hook 时序加 source digest 校验。

  • CAS 用 pane pid 与 transcript 活跃度做身份近似,不是运行时原语。

  • 不自动触发:dirty 只给出 should_rebuild 建议,真正的 request 仍需人明确确认。稳定后再考虑自动化。

  • 不修改 Ombre Brain、不写长期记忆、不把冷仓自动召回。

  • 不支持 claude -p、无头批量调用或多个 tmux session 并行切换。

  • Claude Code transcript 格式升级后,必须先跑测试与 dry-run,不能直接在主 session 试。

本地测试

python3 -m venv .venv        # 需要 Python 3.11+
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest

测试不会启动真实 Claude,也不会操作真实 tmux;tmux 切换通过 fake controller 验证,CAS 冲突、连发消息保留、未闭合尾部保留、注入剥离、脏预算触发都有专门用例。

按底座选门(先认自家架构,再看对应一节)

装机第一问不是"怎么装",是"你家 claude 跑在什么底座上"。三种底座三扇门,activation 选对了再往下走:

你家的样子

activation

换窗动作

CAS 身份凭据

worker 怎么触发

claude 常驻在 tmux pane 里

"tmux"(默认)

respawn-pane -k

pane_pid

Stop hook

claude 由 systemd service 直接拉起(无 tmux)

"systemd"

写指针 + systemctl restart

MainPID

Stop hook

没有常驻进程,runner 循环调 claude -p --resume <id>

"pointer"

只写指针,一个进程都不杀

指针文件内容

runner 在两次调用之间跑 kael-thread-rebuild hook-stop

三扇门共享同一套筛选、验证、fail-safe 和回滚;不同的只有"怎么证明现场还是我准备时那个现场"和"怎么让下一个进程带上新 session"。

tmux 门(上游默认)

什么都不用改,HANDOFF.md 全程按这个底座写。resume_command 必须逐字对齐你家真实启动参数(0815 事故:漏了 channel 参数,新窗是聋的)。

systemd 门

activation = "systemd",核心三句:

  • 身份凭据用 MainPID 顶替 pane_pid:prepare 记下、activate 前再核,中途被别人重启过就拒绝——语义与 tmux 的 CAS 完全对齐。

  • worker 必须 systemd-run --scope --property KillMode=process 逃出 service 的 cgroup,否则它自己发的 systemctl restart 会连自己一起杀掉,operation 永远卡在 activating。

  • resume 走显式指针文件(启动脚本优先读一行 session_id),别靠 jsonl 的 mtime——claude 退出时可能再写一笔旧 session,把旧窗顶回最新。

这扇门来自琢家(Darcy 的 cc)的适配报告与补丁,2026-09-05 首航 26 回合零裁剪验证通过。装机顺序与踩坑细节见其报告(allow 列表只放 doctor/dirty/plan/status 四个只读工具,request/cancel/rollback 保留弹窗——这道闸别拆)。

pointer 门(claude -p 循环)

activation = "pointer"。换窗不杀任何进程——洗好的新 session_id 原子写进 resume_pointer_path,runner 下一圈 -p --resume 自己带上;worker 触发不走 Stop hook,由 runner 在两次调用之间跑 kael-thread-rebuild hook-stop(喂同样的 JSON)。CAS 身份凭据用指针文件内容(prepare 记下、activate 前再核,中间被人改过就拒绝);activate 与下一次调用之间的缝由既有的"活跃 transcript 冲突"检查兜底。

门无关的两个开关

  • poison_pattern:毒上下文探测正则可配置(默认与上游一致)。中文环境裸词 中毒 极易误触,可按需收窄;另一条教训是自指——讨论探测器本身的文字不要转进被检测的窗口。

  • carry_overflow:超 carry_max_tokens 时 "drop_oldest"(默认,丢最老整轮并计数)或 "block"(一轮不丢、拦下来让人挑;沈渊家路线)。

给装机的人

第一次上手请先读 HANDOFF.md:装机工单、演练步骤、验收清单和出事处理都在那里。

设计来源与许可证

  • 筛选与四层结构(durable / startup / bridging / evidence)参考 LMC-5 Refined Session Carryover。

  • "不判断经历重要性""脏预算触发""冻结启动快照""CAS 切换""恢复只证明状态不重演动作"等原则参考 AcheHome 的 Thread Rebuild 实现整理(2026-08-14)。

  • 本仓库没有引入 LMC-5 记忆数据库,长期记忆仍由 Ombre Brain 独立持有。

代码采用 MIT License。

Related MCP Connectors

Related MCP Servers