feat(fork): /fork — 会话分身(并行分身模式,Codex + Claude 原生 fork) - #704
Open
xiaoxueSunn wants to merge 8 commits into
Open
feat(fork): /fork — 会话分身(并行分身模式,Codex + Claude 原生 fork)#704xiaoxueSunn wants to merge 8 commits into
xiaoxueSunn wants to merge 8 commits into
Conversation
Non-destructive sibling of transferSession: fork a running session into a second independent botmux session at a different anchor, source untouched. - forkSession() (worker-pool): reuse transferSession front guards, drop all destructive steps (freeze/kill/delete/rewrite source), mint a child session and forkWorker(resume=true) it. - Native CLI fork threading: adapter buildArgs gains forkSession flag -> Claude --fork-session, Codex fork subcommand. One-shot Session.pendingForkSession marker cleared once the child's own cliSessionId is persisted. - Capability gate isForkCapableSession: Claude family + Codex terminal only; codex-app / Codex-under-RPC / remote backends refused. - lineage title + forkedFrom provenance on the child. Command wiring (/fork case + picker card) and real-env acceptance still pending. Co-Authored-By: Claude <noreply@anthropic.com>
Single-bot fork-to-new-group entry (fork is single-session, so unlike /relay --create there is no multi-bot leader election / peer coordination): - /fork case in command-handler: owner check, capability gate (isForkCapableSession) with typed refusal, resolve the single @-bot (must be this bot, since fork copies this session's transcript), createGroupWithBots, then forkSession into the new chat. Source session left untouched. - cmd.fork.* i18n (zh + en): no_bot / no_sender / no_session / not_owner / no_mentions / resolve_failed / unsupported_backend / mid_turn / not_started_yet / adopt_not_forkable / created / failed. - No-arg picker path guides to --create for now (picker is a follow-up). Co-Authored-By: Claude <noreply@anthropic.com>
Real-env test (seed0630) surfaced two issues: - Orphan group bug: /fork --create ran createGroupWithBots before checking the source was a real, resumable session, so a refusal (e.g. /fork typed at the group top-level while the session lives in a 话题) left an empty orphan group. Move the real-session + idle guards ahead of group creation. - Clearer refusal: new cmd.fork.no_source_here tells the user to invoke /fork inside the thread the session lives in, not the group top level. Co-Authored-By: Claude <noreply@anthropic.com>
…e-/fork message
- Orphan-group cleanup: if forkSession fails after createGroupWithBots (narrow
TOCTOU race), best-effort disbandChat the just-created empty group so no
failure path leaves an orphan chat; if disband fails (ownership transferred
to the user), tell them explicitly via cmd.fork.orphan_group_left.
- Clearer bare /fork: the no-arg picker path is not built yet, but the old copy
reused cmd.fork.no_session ("must be invoked in a thread with a session"),
which is misleading when the user DOES have a session. New cmd.fork.picker_pending
says the picker is pending and points at /fork --create.
Co-Authored-By: Claude <noreply@anthropic.com>
Word it as a product message (in-place fork not supported here, use --create, in-group Branch coming later) instead of exposing "not built yet". Co-Authored-By: Claude <noreply@anthropic.com>
Fork copies THIS session, so the invited bot is always the current bot. No @mention now defaults to the current bot (common "fork myself to a new group" case — no need to @ the bot you are already talking to). An explicit @mention that resolves to a DIFFERENT bot is refused (cmd.fork.wrong_bot). Co-Authored-By: Claude <noreply@anthropic.com>
The daemon-side cli_session_id handler cleared the one-shot marker, but the worker process writes the sessions file directly in persistCliSessionId and, having reloaded the row from disk with the marker still set, its write raced and clobbered the clear — leaving pendingForkSession=true on the child, so a later refork would re-fork the parent instead of resuming the child. Clear it in the same worker-side write that sets cliSessionId. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
这个 PR 做什么
给 botmux 加
/fork:把一个正在跑的会话,在当前上下文节点复制出一条带完整上下文的独立会话,源会话原封不动继续。上下文复制交给 CLI 原生 fork(Claude--fork-session、codex fork),botmux 只做「后端能力适配 + 与现有框架的套壳关联」,不碰上下文文件。本 PR 只交付并行分身模式(Fork):
/fork --create <群名>建新群放分身。原地断点(Branch,非话题群 close-换-fork)是自创用法、有破坏性,另立 PR 并先走产品讨论。设计文档(含完整设计、能力门控、边界、真实环境测试记录):
https://bytedance.larkoffice.com/docx/YENydJcPgoJMjaxEhfIcMiCbnbc
怎么实现的(优雅 = 复用 transferSession + CLI 原生 fork)
forkSession()(worker-pool.ts)=transferSession(/relay 底层)的非破坏性兄弟:复用它的前置守卫,去掉全部破坏性步骤(不冻结源卡、不 kill 源 worker、不删注册表、不改写源路由),改为 mint 一个 child 会话 +forkWorker(resume=true)拉起。源会话全程不碰。cliSessionId先指向源的 CLI id,首次 spawn 用 CLI 原生 fork 派生新 id,worker 回读后写回 child、清除一次性pendingForkSession标记;之后 refork 正常 resume 自己。--fork-session;Codex 把resume子命令换成fork。/fork进DAEMON_COMMANDS+ command-handler;--create无@默认当前 bot(fork 只能复制给同一个 CLI)。真实环境端到端测试(2026-08-02,隔离单 bot 切到本分支 build)
Codex(seed0630):
/fork --create→ 原生codex fork <源id>→ 独立新 rollout(带forked_from_id)→ 分身在新群答出暗号 → 源 rollout 不动。同源多次 fork 均独立。Claude(Relay-Claude2):
/fork --create→ 原生--fork-session→ child transcript 继承暗号 → 源 17 行不动 → child 拿自己新 id +🔱血缘标题。测试中发现并修复 5 个问题(详见文档附录 C):建群前置守卫顺序、失败时孤儿群清理、裸
/fork提示文案、--create冗余 @self、pendingForkSession清除的 worker/daemon 抢写竞态。仍未验证
影响范围
forkSession()+/forkcommand case + adapter 的forkSession参数 + i18n;对非 fork 路径无行为改变(forkSession标志对普通会话恒为 false)。worker-pool.ts/command-handler.ts/worker.ts/ adapters,已pnpm build通过、Codex+Claude 两条后端路径真机验证。🤖 Generated with Claude Code