乐于分享
好东西不私藏

Claude Code 源码研究 09: Remote Control、Bridge 与 Multi-Agent

Claude Code 源码研究 09: Remote Control、Bridge 与 Multi-Agent

点击上方“慧响” 可以订阅哦!

本文字数: 14178字

阅读时间: 29分钟

本文回答十二个问题:

1. Claude Code 里的 remote-control--remoteassistantssh 到底是不是同一套东西。

2. 为什么这个工程里“远程能力”不是单一路径,而是几种形态并存。

3. useReplBridge() 背后的 bridge,到底是在“把本地会话同步到 Web”,还是在“把 Web 会话接到本地”,还是两者都是。

4. claude remote-control 这个独立入口为什么不只是开一个 socket,而更像在注册一个可调度的本地执行环境。

5. bridgeMain.tsreplBridge.tsinitReplBridge.tssessionRunner.ts 这几层分别负责什么。

6. --remote / --teleport 这种 CCR remote session,和 bridge 模式相比,执行权、状态所有权、任务所有权分别落在哪一边。

7. claude assistant [sessionId] 为什么不是“第二个 REPL”,而是一个纯 viewer client。

8. claude ssh 为什么也是 remote mode,但它的结构和 CCR / bridge 都不同。

9. Claude Code 的 multi-agent 到底有几种实现形态,哪些是同步子代理,哪些是后台任务,哪些是真正意义上的 teammate。

10. AgentTool 为什么更像一个“agent routing plane”,而不是一个简单的工具函数。

11. task system 在 remote 与 multi-agent 设计里到底扮演什么角色。

12. 这一整套 remote + multi-agent 设计,最值得复用的工程模式和最明显的技术代价是什么。

如果上一章研究的是:

Claude Code 如何把能力扩展织进同一个运行时

那么这一章研究的就是:

Claude Code 如何把“执行”本身扩成一个分布式平面,让同一个 REPL 宿主能够接住本地执行、桥接执行、远程执行、SSH 执行,以及多代理协作执行。

这篇也必须很长。

因为到了这一层,已经不能再用“某个目录在做什么”来理解系统了。

remote-control 不只在 src/bridge/*

remote session 不只在 src/remote/*

multi-agent 不只在 src/tools/AgentTool/*

它们一起横跨:

– src/entrypoints/cli.tsx– src/main.tsx– src/screens/REPL.tsx– src/bridge/– src/remote/– src/hooks/useReplBridge.tsx– src/hooks/useRemoteSession.ts– src/hooks/useSSHSession.ts– src/hooks/useAssistantHistory.ts– src/tools/AgentTool/– src/tasks/– src/utils/swarm/*

如果只盯着一个目录看,很容易得出几个错误结论:

1. 以为 remote-control 就是 claude.ai 控制本地 CLI 的那条桥。2. 以为 --remote 只是 remote-control 的另一个 UI 入口。3. 以为 multi-agent 只是在后台起几个 subagent。4. 以为 teammate 和 subagent 只是名字不同。5. 以为 task system 只是一个“进度列表”。

这些理解都不够准确。

更准确的说法是:

Claude Code 把执行拓扑做成了多形态并存的运行时体系。

在这个体系里:

– bridge 是一种“本地环境注册 + 会话桥接 + 控制请求转发”的机制。– CCR remote session 是一种“远程会话拥有执行权,本地 REPL 做客户端”的机制。– assistant viewer 是 CCR 机制的纯观察者形态。– SSH 是“本地 UI + 远程 CLI 执行 + 本地认证代理”的另一种 transport。– multi-agent 也不是单一路径,而是同步 subagent、本地后台 agent、远程隔离 agent、pane teammate、in-process teammate、fork subagent 几条路线并存。

所以这一章的一句话结论可以先写在最前面:

Claude Code 不是在一个本地 REPL 上外挂了几个 remote feature,而是在同一个 REPL 宿主之上,铺了一整张“执行拓扑层”。这个拓扑层既决定代码在哪跑,也决定消息从哪流、权限在哪确认、任务由谁持有、状态由谁恢复。

1. 先给结论

这一章最重要的结论有四个。

1.1 没有一个统一的 “remote mode”

Claude Code 里至少有五种要严格区分的 remote 形态:

1. 交互 REPL 上的 repl bridge   本地 REPL 正常运行,但后台维护一条 bridge 连接,让外部界面可以看到并接入这条会话。

2. 独立的 claude remote-control / claude bridge / claude rc 入口   不是简单开启某个桥接标志,而是显式注册一个本地环境,并根据工作项按需生成 Claude 子会话。

3. CCR remote session,也就是 --remote / --teleport 对应的云端会话   执行发生在远端,本地 REPL 只是 WebSocket + HTTP 客户端与交互宿主。

4. claude assistant [sessionId] viewer 模式   是 remote session 的只读/观察者变体,不拥有标题控制、不中断远端 agent,只是展示与补页历史。

5. claude ssh <host> 模式   UI 在本地,Claude CLI 在远端 Linux 主机执行,通过 SSH child process 和认证隧道协作。

这五种模式都涉及“远程”,但运行权、状态所有权、任务所有权、权限确认流都不一样。

1.2 没有一个统一的 “multi-agent”

Claude Code 至少实现了六种 agent 派生形态:

1. 同步 foreground subagent   当前 agent turn 内联调用 runAgent(),把结果同步返回给父 agent。

2. 本地异步后台 agentregisterAsyncAgent() 生成 local_agent task,异步执行并通过 task notification 回传。

3. 远程隔离 agentAgentTool 选择 isolation: "remote" 时,通过 remote session 启动,并登记成 remote_agent task。

4. worktree 隔离 agent   仍可能是本地 agent,但执行目录被切到新的 git worktree。

5. process-based teammate   通过 tmux / iTerm2 pane 或单独 Claude 进程启动,具有团队身份与消息通道。

6. in-process teammate   不起新系统进程,直接在同一 Node.js 进程内,以 AsyncLocalStorage 与 task state 做逻辑隔离。

再加上一个非常特别的分支:

7. fork subagent   不是一般意义上的“另一个 agent 类型”,而是带 forked context 语义的特化子代理路径。

所以,multi-agent 在 Claude Code 里不是一个 feature。

它是一整个 execution routing plane。

1.3 REPL 不是功能层,而是宿主层

src/screens/REPL.tsx 在这一章里会显得尤其关键。

因为无论是:

– 本地执行– repl bridge– remote session– assistant viewer– ssh session– direct connect– task panel– permission queue

最后都要收敛到 REPL 宿主之中。

它不是“聊天界面”。

它是一个能接纳多种 transport、多种消息源、多种任务宿主的运行时前端。

1.4 task system 是 remote 与 multi-agent 的共同抽象层

这一点非常重要。

如果没有 task system,Claude Code 会退化成一组互不相干的执行路径:

– 背景 agent 一套逻辑– 远端 agent 一套逻辑– teammate 一套逻辑– bridge 一套逻辑

task system 的作用就是把这些异构执行体,重新压回统一的用户表面:

– local_agent– remote_agent– in_process_teammate– 以及其它 shell / workflow / monitor 任务

这意味着 Claude Code 真正统一的并不是“底层怎么跑”,而是“跑完以后怎么在主会话里被管理、显示、通知、恢复、归档”。

2. 先把几个最容易混淆的 remote 概念拆开

如果这一章不先做去混淆,后面所有分析都会互相打架。

2.1 五种 remote 形态的总表

形态
典型入口
执行发生位置
UI 所在位置
会话状态主要所有者
典型 transport
repl bridge
claude --remote-control

 或会话内 bridge
本地 CLI
本地 REPL + 外部桥接客户端
本地 REPL,会话桥可复用
bridge transport / ingress
standalone bridge
claude remote-control
本地子 CLI 会话
外部客户端或桥接控制面
bridge 环境 + 子会话
poll + ingress + child process stream-json
CCR remote session
claude --remote

 / --teleport
远端会话宿主
本地 REPL
远端 session
HTTP + WebSocket
assistant viewer
claude assistant [sessionId]
远端会话宿主
本地 REPL
远端 session
HTTP history + WebSocket
SSH mode
claude ssh host
远端 Linux 主机
本地 REPL
远端 CLI 进程,本地 UI 持状态
SSH child process + local auth proxy

表面看起来它们都叫“远程”。

但真正的差异点不在“网络有没有跨机器”,而在以下五个问题:

1. Claude 主执行循环到底在哪边。2. 谁持有主要 transcript。3. tool permission request 在哪边产生,又在哪边确认。4. 任务状态由哪边维护。5. 本地 REPL 是执行者、控制器、镜像端,还是纯 viewer。

2.2 不要把 bridge 和 remote session 混为一谈

这是最常见的误解。

从名字看,bridge 好像只是 remote session 的连接层。

但源码显示它们不是一个层级的东西。

bridge 更像:

– 让某个本地环境对外“可被接入、可被调度、可被绑定会话”的协议层与生命周期层。

而 CCR remote session 更像:

– 已经存在于远端基础设施中的会话,本地 REPL 只是它的客户端。

也就是说:

– bridge 重点是 把本地执行环境暴露出去– remote session 重点是 把远端执行会话接进来

一个是 outward bridge。

一个是 inward client。

两者都可以被 REPL 承载,但语义完全不同。

2.3 不要把 assistant viewer 当成另一个 remote mode

claude assistant [sessionId] 不是普通意义上的“再开一个远程 REPL”。

它的结构更接近:

– 对运行中的远端 agent 会话做 attach– 拉取最近历史– 向上滚动时增量补页– 实时订阅新事件

但它不会像普通 remote session 那样接管标题,不会像活跃控制端那样负责 interrupt,也不会把自己当作会话主人。

所以 assistant viewer 的关键语义不是 remote,而是 viewerOnly。

2.4 不要把 SSH 当成 bridge 的 transport

源码中 claude ssh 有自己独立的一整套路径。

它不是 bridge 的一个 transport backend。

它更像:

– 本地启动一个 SSH child process– 在远端检查 / 部署 Claude CLI– 建立 Unix socket 的反向代理,让远端 CLI 可以借本地认证– 本地 REPL 通过 SDK message 适配器消费远端输出

也就是说,SSH 模式不是把 bridge 搬到 SSH 上。

而是在做“远端执行 CLI,本地保留 UI 和权限交互”。

3. 从入口层看:remote 和 bridge 是一等公民,不是后挂功能

src/entrypoints/cli.tsx 和 src/main.tsx 给出的第一条证据就是:

这些模式都不是会话启动后才决定的“附加选项”。

它们在 argv 早期解析阶段就被识别、剥离、分流。

3.1 cli.tsx 的 fast-path 已经把 bridge 与 daemon 特判出来

src/entrypoints/cli.tsx 里有几个非常直白的 fast-path:

– --daemon-worker– claude remote-control– legacy alias: claude remote / claude sync / claude bridge– claude daemon

这说明:

1. bridge 不是普通 slash command。2. daemon 不是普通后台 feature。3. remote-control 已经被视作独立 entrypoint。

换句话说,在 Anthropic 的产品设计里,这些能力已经足够重要,重要到不应该经过完整主程序初始化后再决定。

3.2 main.tsx 对 assistantssh--remote 的早期处理更说明问题

src/main.tsx 里还有几条很有代表性的早期 argv 逻辑:

– claude assistant [sessionId]– claude ssh <host> [dir]– --remote– --teleport– --remote-control / --rc

这些早期分流背后隐含着一个非常强的架构判断:

不同执行拓扑需要不同的初始化路径。

例如:

– claude ssh 需要在主 CLI 正常 argument handling 之前,先识别 host 与目标目录。– claude assistant 需要在普通 REPL 初始化前决定 viewerOnly 模式。– --remote 需要先创建远程会话,再决定本地 REPL 是不是带初始 prompt 的客户端。– --remote-control 需要在 AppState 初始化时就决定 replBridgeEnabled

这不是 UI 选项层面的差异。

这是 runtime topology 层面的差异。

3.3 main.tsx 给不同 remote 路径打不同标签

源码里还能看到一些非常关键的环境与 client tagging:

– CLAUDE_CODE_ENTRYPOINT === 'remote'– CLAUDE_CODE_ENVIRONMENT_KIND === 'bridge'– session source tag remote-control

这些标记说明系统不只是“运行不同代码路径”,还在:

– 明确记录当前会话来自什么拓扑– 让后端服务知道它看到的是什么类型的 session– 让 analytics / policy / resume / reconnect 使用同一套拓扑识别信息

也就是说,remote / bridge 不是临时分支,而是产品级会话类型。

4. REPL bridge:正常本地会话上的“外连桥”

先看最容易被低估的一条线:useReplBridge()

很多人看到这个 hook,第一反应会是:

“它是不是只是把聊天记录同步到某个外部界面?”

如果只看名字,很容易这么想。

但真实情况更复杂。

4.1 useReplBridge() 的职责不是单向镜像

src/hooks/useReplBridge.tsx 所做的事情,至少包含四层:

1. 当 AppState.replBridgeEnabled 为真时,初始化 bridge 连接。2. 把本地 REPL 新产生的 user / assistant 消息写入 bridge。3. 把 active bridge handle 发布成全局可访问对象。4. 在需要时接住远端控制请求。

这里最重要的是第 4 点。

如果只是单向镜像,它不需要成为一个全局 handle。

也不需要在 bridgeMessaging 里专门处理 control_request

这说明 repl bridge 不是纯同步。

它是一条真正可交互的控制通道。

4.2 initReplBridge.ts 不是核心,而是 REPL 包装层

src/bridge/initReplBridge.ts 的意义在于:

– 它从 REPL / bootstrap 侧收集上下文– 处理 auth、gates、cwd、git context、title 等准备逻辑– 再把真正的核心初始化委托给 initBridgeCore()

这意味着 bridge 架构是分层的:

1. REPL wrapper 层   理解当前交互会话、当前工作目录、当前标题、当前 gate。

2. bridge core 层   负责环境注册、连接、控制消息、teardown、transport 生命周期。

这层分离的价值非常大。

因为一旦 core 不依赖完整 REPL bootstrap,它就能被别的宿主复用。

这正是后面 runBridgeHeadless() 可以复用它的原因之一。

4.3 replBridge.ts 里真正抽出来的是“bootstrap-free bridge core”

src/bridge/replBridge.ts 暴露出的不是简单的 UI helper。

它更像:

– 一个无需依赖 main.tsx 与交互入口的 bridge 运行核心

从设计上看,这一层已经把核心 bridge 生命周期抽出来了:

– 环境注册– 会话建立– ingress / transport 配置– control request / response– 消息写入– teardown

这一层甚至显式考虑了 daemon caller 的复用场景。

也就是说,作者清楚地知道:

bridge 不是 REPL 特性。

REPL 只是 bridge 的一种宿主。

4.4 bridge 的一条关键动作:system/init

useReplBridge() 成功连上 bridge 之后,会写一条很关键的初始化消息。

这条 system/init 并不是一个无关紧要的“hello”。

它本质上在对外声明当前本地会话的可控制面。

这里至少包含几个重要内容:

– 当前可用 slash commands,但会经过 isBridgeSafeCommand 过滤– 当前 skill 衍生出来的 command 集合– plugin 信息会做红化或裁剪– MCP clients / tools 也不会完整暴露

这告诉我们两个事实。

第一,bridge 的目标不是把整个本地运行时完全原封不动抛给外部客户端。

第二,bridge 对外暴露的是一个经过裁剪的控制面。

也就是说,remote-control 在产品语义上从一开始就不是“完全远程桌面”,而是“安全边界内的会话桥接”。

4.5 为什么 replBridgeHandle 要做成全局句柄

src/bridge/replBridgeHandle.ts 维护 active handle 的意义在于:

– bridge 相关逻辑并不都在 React 组件树内部– 某些工具、命令、后台逻辑也需要访问当前 bridge 会话

这本质上是一个经典运行时模式:

– React tree 负责宿主渲染与大部分状态订阅– 但某些 transport / command / tool path 必须能在树外部拿到 bridge 通道

一旦需要树外访问,就说明这条 bridge 已经不是 UI 附件。

它是全局运行时基础设施。

4.6 KAIROS / assistant mode 下的 perpetual bridge 特别能说明问题

源码里有个非常值得注意的注释:

– assistant mode 下 bridge 可以成为 perpetual session– claude.ai 可以跨 CLI 重启看到一条连续对话

这意味着 bridge 不只是“当前 CLI 生命周期里的镜像连接”。

它还在承担一种更长期的会话身份锚点。

这也是为什么源码里会涉及:

– bridge pointer– session ID publish– reconnect– persistent bridge state

换句话说,repl bridge 已经开始触及 session continuity,而不仅仅是 transport continuity。

5. standalone bridge:claude remote-control 本质上是在注册本地执行环境

如果说 useReplBridge() 代表的是:

在已有本地 REPL 上外挂一条可交互桥

那么 claude remote-control 代表的就是:

直接把本地环境作为一个 bridge host 启起来。

这两者看起来相关,但不是同一级抽象。

5.1 bridgeMain.ts 的主语不是“会话”,而是“环境”

src/bridge/bridgeMain.ts 给人的第一印象可能是很重。

原因正是因为它处理的不是单一会话,而是整个环境级别的生命周期。

从摘要里能看到它维护了很多环境级结构:

– active sessions map– start times– work IDs– compat session IDs– ingress tokens– timers– worktrees– titled sessions

这些东西如果只是为了同步一个本地会话,是不需要的。

它们之所以存在,是因为 standalone bridge 正在做更高一级的事情:

– 向外注册一个 environment– 接受工作分派– 为每个工作项生成或绑定 Claude session– 维护多个会话并发与回收

也就是说,remote-control 这套设计不是“把本地 Claude 暴露给网页”。

而更像:

把本地机器上的某个目录、repo、branch、认证上下文、容量与 spawn 策略,一起注册成一个可消费的执行环境。

5.2 BridgeConfig 揭示了 bridge 的真正对象模型

src/bridge/types.ts 非常关键。

因为 BridgeConfig 和 SpawnMode 基本直接定义了这个系统到底在抽象什么。

几个最重要的字段与类型是:

– SpawnMode = 'single-session' | 'worktree' | 'same-dir'– BridgeWorkerType = 'claude_code' | 'claude_code_assistant'– BridgeConfig 中包含:  – cwd / dir  – branch / repo  – maxSessions  – spawnMode  – bridgeId  – environmentId  – API / session ingress URL  – sessionTimeout

仅从这些字段就能看出,bridge 抽象的不是 socket。

bridge 抽象的是:

– 一个位置明确的工作环境– 一个带容量限制的会话池– 一个带 spawn 策略的执行宿主– 一个与后端服务协作的运行时实例

5.3 三种 spawn mode 说明 bridge 在管理“会话生产方式”

SpawnMode 的三个值特别说明问题:

1. single-session   一次只跑一个会话,结束即结束。更接近早期单会话桥。

2. same-dir   多个 session 共用当前目录。速度快,但容易互相踩工作树。

3. worktree   每个 session 进入独立 git worktree。隔离最好,但成本更高。

这里你会发现,bridge 的重点根本不在“消息怎么发”。

而在:

– 新会话如何诞生– 会话之间如何隔离– 环境容量怎么控制– 本地 repo 如何被多 session 安全消费

这已经是 job scheduler / execution fabric 的问题了。

5.4 bridgeMain.ts 的 first-run 交互暴露了产品判断

bridgeMain.ts 里关于 same-dir 和 worktree 的切换逻辑、首启选择逻辑、w 键切换逻辑非常有代表性。

它说明作者并没有把 spawn mode 当作内部实现细节。

相反,他们把它视为用户需要理解、也值得调参的运行时策略。

这背后的产品判断很明确:

– 多 session 已经不是边缘功能– 工作目录隔离会直接影响真实生产使用– 用户需要在效率与隔离之间选 tradeoff

如果只是一个“能让网页连上 CLI”的 feature,不会做到这个程度。

5.5 worktree 在 bridge 里的意义比在 agent 里更大

在 agent 场景里,worktree 更像一种隔离执行目录的技巧。

但在 bridge 场景里,worktree 其实承担的是:

– 环境级多租隔离

即使这不是传统 SaaS 语境下的多租户,它也已经在解决同类问题:

– 多个工作项同时落到同一 repo– 如何避免相互污染– 如何把“当前 shell 目录”与“实际执行 worktree 目录”区分开

源码里还有 bridgePointer 去扫描 worktree sibling 的逻辑。

这说明 bridge 系统已经把“同一 repo 下多个 worktree 都可能属于同一逻辑环境”的情况考虑进去了。

6. sessionRunner.ts:bridge 最关键的一层不是网络,而是子会话生成

standalone bridge 里最值得注意的,其实不是 poll loop,而是 sessionRunner.ts

因为这里直接回答了一个核心问题:

bridge host 到底是怎么执行新会话的?

答案不是:

– bridge host 自己内嵌一个 query loop

而是:

– bridge host 再起一个 Claude CLI 子进程,并通过 SDK / stream-json 协议跟它对接。

6.1 createSessionSpawner() 说明 bridge 不是第二套执行引擎

src/bridge/sessionRunner.ts 里的 createSessionSpawner() 是 bridge 架构的关键锚点。

它揭示了一个非常重要的工程决策:

bridge 不自己重写 Claude 会话执行器。

它复用现有 CLI,可把子 session 当成标准化子进程来驱动。

启动参数里几个关键信号非常明显:

– --print– --sdk-url– --session-id– --input-format stream-json– --output-format stream-json– --replay-user-messages

这些参数组合说明 bridge host 的做法是:

1. 起一个标准 Claude CLI 子进程。2. 让它不要进入本地 TUI,而是进入 SDK / 流式 JSON 模式。3. 由 bridge host 负责把远端工作、权限控制、外部消息流,转换成对子进程的协议输入。4. 再把子进程产生的活动、工具申请、输出事件映射回 bridge 环境。

这非常重要。

因为它让 bridge 与普通 CLI 的执行语义尽量保持一致。

也意味着:

– 新功能一旦进主 CLI,bridge child session 就能继承更多能力– 不需要维护第二套 Claude 行为实现– 协议层与执行层分开演进

6.2 CLAUDE_CODE_ENVIRONMENT_KIND=bridge 暴露了会话自我认知

子进程环境变量里有一项非常关键:

– CLAUDE_CODE_ENVIRONMENT_KIND=bridge

这意味着被桥接生成出来的 Claude session 自己知道:

– 我不是一个普通本地交互式 CLI 会话– 我处在 bridge 环境之中

这个标签的意义至少有三层:

1. 让 session 上报或分析时可区分来源。2. 让某些逻辑知道当前权限确认、会话恢复、会话来源应该走 bridge 分支。3. 让后端与上层控制面可以识别这个 session 的出身。

6.3 control_request 证明 bridge 处理的是“权限交互穿透”

sessionRunner.ts 会专门检测 child NDJSON 中的 control_request

这说明什么?

说明 bridge host 并不是把子会话纯粹当成黑盒命令执行器。

它必须理解某些高级协议事件,尤其是:

– 子 session 请求工具权限确认– 可能还有模型设置等控制请求

这说明 bridge 架构必须完成一件比“stdout 透传”高级得多的工作:

把子会话的交互式控制需求,提升为 bridge 层可以转发和响应的控制消息。

这也是为什么 bridgeMessaging.ts 专门围绕 control_request / control_response 设计。

6.4 replay-user-messages 是会话一致性的一个重要信号

启动参数里 --replay-user-messages 也很值得注意。

它表明 bridge 并不只是从某个瞬间开始实时接管。

它还关心:

– 子会话在建立时,是否需要重播之前用户消息以恢复上下文连贯性

这类设计通常只出现在把会话 continuity 当成一等问题的系统里。

如果是一次性短作业执行,其实完全不需要。

这再次说明 bridge 关注的是“可持续会话”,而不仅是“远程执行一个 prompt”。

7. runBridgeHeadless():bridge 的 daemon/headless 形态不是 REPL 的附属品

src/bridge/bridgeMain.ts 里还有一个非常关键的部分:

– runBridgeHeadless()

这部分几乎是在明说:

bridge 已经被做成一个可以脱离 TUI 的长期工作进程。

7.1 headless bridge 的定位很明确

源码注释里已经指出:

– 这是给 remoteControl daemon worker 的非交互入口– 不依赖 readline/TUI/process.exit– 配置来自 caller(例如 daemon.json)– auth 可以来自 supervisor IPC

这说明 headless bridge 不是一个“顺便支持”的隐藏模式。

它是 standalone bridge 的正式部署形态。

7.2 BridgeHeadlessPermanentError 暴露了 supervisor 协议思维

BridgeHeadlessPermanentError 的存在也很有意思。

因为它不是一个简单异常类型。

它实际上在表达一种 supervisor 协议:

– 有些错误不值得重试– 例如 trust 未接受、worktree 不可用、HTTP 非 HTTPS

这说明 headless bridge 的设计者已经把它当成一个会被 supervisor 管理、被重启、被看门狗照看的 worker 进程。

也就是说,这不是命令行工具的小技巧。

这是服务化思维。

7.3 headless bridge 同样构建 BridgeConfig

在 runBridgeHeadless() 里,仍然会构建完整的 BridgeConfig

这意味着 daemon 形态和交互形态共享的是同一个 bridge 核心抽象,而不是两套平行实现。

这类“同一 core,多种宿主”的设计在整套 Claude Code 代码里反复出现:

– REPL 宿主– headless daemon 宿主– remote viewer 宿主– SSH 宿主

这也是为什么这个工程越来越像运行时平台,而不是终端 App。

8. CCR remote session:本地 REPL 作为远端会话的客户端

现在转到另一条必须严格区分的主线:--remote / --teleport

8.1 这条线的本质不是 bridge,而是“远端 session 已经存在”

src/main.tsx 针对 --remote / --teleport 的处理逻辑非常清楚:

– 先创建或恢复远端会话– 再用 createRemoteSessionConfig(...) 构建配置– 本地 REPL 以 remote session client 身份启动

这里的关键差异是:

本地这边不再是执行宿主。

它只是:

– 展示宿主– 输入宿主– 权限确认宿主– 远端事件消费宿主

执行权落在远端会话。

8.2 RemoteSessionManager 的形态说明它是个完整客户端,不是 hook helper

src/remote/RemoteSessionManager.ts 的结构很值得注意。

它不是 React hook。

而是一个可独立使用的 session manager。

它负责至少这些事情:

– 建立 WebSocket 订阅– 通过 HTTP POST 发送用户消息– 接收并维护 permission request– 发送 permission response– 支持 interrupt– disconnect / reconnect

这说明在 CCR remote session 里,本地 REPL 与远端 session 之间不是一次性 RPC 关系。

而是一条长期双向控制连接。

8.3 SessionsWebSocket 是远端会话的事件主通道

src/remote/SessionsWebSocket.ts 则把 transport 层再单独抽掉。

这层的特征包括:

– 订阅 URL 明确以 session id 为中心– 每次连接都用新 token 鉴权– 有 reconnect delay / max reconnect attempts / session not found retry budget– 对某些 close code 做特殊处理,例如 unauthorized 或 compaction 期间的特殊状态

这些设计很像一个标准实时客户端 SDK。

而不像“给 REPL 用的临时封装”。

这说明 remote session 能力本身已经被抽象成一个可重用 transport client。

8.4 sdkMessageAdapter 揭示了本地 REPL 与远端协议并不是同一种消息体系

这一点非常关键。

src/remote/sdkMessageAdapter.ts 的存在说明:

– 远端 session 发来的,是 SDK 风格消息– 本地 REPL 消费的,是内部消息模型

所以中间必须有一层 adapter,把:

– assistant 消息– stream 事件– system / result / tool progress– compact 边界– viewer-only 需要显示的 tool_result / user text

重新映射到 REPL 能理解的事件世界。

这实际上透露了一个很深的架构事实:

REPL 并不是为远端协议原生设计的。

相反,是 remote layer 主动适配 REPL 的内部消息模型。

这也再次证明 REPL 是宿主,remote 是插件化接入层。

8.5 useRemoteSession() 把远端会话真正接到 REPL 里

src/hooks/useRemoteSession.ts 是 REPL 集成层。

这层最值得注意的点有几个。

第一,它会处理 echo filtering。

也就是说,本地刚发出去的用户消息,不能在远端回推回来时再原样重复显示。

这说明本地输入与远端广播之间存在典型的双写回流问题。

第二,它会处理 remote init。

即:

– 远端会话初始化消息可以带回 remote slash commands– 本地 REPL 再据此过滤本地 commands 表面

这很重要。

因为它说明在 remote session 模式下,本地 REPL 的用户命令表面也不是完全本地自治的。

它必须服从远端会话告诉它“这里能做什么”。

第三,它会维护 remoteBackgroundTaskCount

这是一个非常关键的实现细节。

因为在 remote session 模式下,后台任务不在本地 AppState.tasks 里真正运行。

任务发生在远端 daemon / remote runtime 上。

所以本地只能根据事件,例如:

– task_started– task_notification

来近似维护一个远端任务计数或展示状态。

这和本地 task system 的对象持有方式完全不同。

第四,它会把 remote permission request 接回本地 ToolUseConfirm 队列。

也就是说,即使执行在远端,用户的权限确认体验仍然尽量落回本地 REPL。

这就是 Claude Code 在这条链路上的核心设计之一:

执行权可以远端化,但人机交互权尽量保持在本地。

9. claude assistant [sessionId]:远端会话的纯 viewer 客户端

如果说 --remote 是控制端。

那 claude assistant [sessionId] 更像旁路观察端。

9.1 viewerOnly 是这个模式的真正语义

RemoteSessionConfig 里有一个决定性的字段:

– viewerOnly?: boolean

在 main.tsx 里构造 assistant 模式时,会显式传入:

– viewerOnly = true

这个字段不是 UI 小开关。

它影响了很多核心行为。

9.2 viewerOnly 改变的不是显示,而是权责边界

从 useRemoteSession.ts 和 RemoteSessionManager.ts 的行为看,viewerOnly 至少意味着:

– 不负责 session title 更新– 不在 Ctrl+C / Escape 时向远端发 interrupt– 不启动某些 timeout warning– 更偏向把远端活动“展示出来”,而不是主导它

也就是说,viewerOnly 的本质不是“只读控件”。

而是:

不把自己当作该会话的主动控制端。

9.3 assistant viewer 的历史不是本地 transcript,而是 API 分页

src/hooks/useAssistantHistory.ts 与 src/assistant/sessionHistory.ts 组合起来,非常完整地说明了 assistant viewer 的历史机制:

– 首屏走 fetchLatestEvents(anchor_to_latest=true)– 向上滚动时走 fetchOlderEvents(before_id=...)– 每页返回 raw events,再通过 convertSDKMessage() 转成 REPL 消息

这非常重要。

它说明 assistant viewer 并不会像普通本地 session 那样依赖本地 transcript 文件或内存消息数组。

它是:

– 远端历史为真源– 本地按需分页拉取– 本地只做视图层合成

这进一步说明 assistant viewer 不是另一个 agent runtime。

它只是一个远端 runtime 的 viewer shell。

9.4 claude assistant 这条路径证明 REPL 可以退化成纯客户端

这点非常关键。

在前几章里,我们已经看到 REPL 作为宿主时非常重:

– 持状态– 承载 query– 承载 tasks– 承载 bridge– 承载 commands

但 assistant viewer 证明了另一件事:

REPL 也可以在极限情况下退化成一个展示壳。

这说明 REPL 的核心能力,不是“自己一定要跑 agent loop”,而是“能承载 agent loop 的交互表面”。

这就是平台型宿主与业务型页面的区别。

10. claude ssh:本地 UI,远端 CLI,认证从本地反向隧道

再看第三条很容易被误判的 remote 路线:SSH。

10.1 claude ssh 不是简单 remote shell

src/main.tsx 对 claude ssh <host> [dir] 的说明非常明确:

– 先 probe 远端– 如有需要部署 binary– 使用 SSH 启动远端 Claude– 建立 Unix socket 的 -R 反向转发,让 API auth 通过本地机器

这不是“ssh 到远端然后跑个命令”的朴素封装。

它更像一个有完整产品设计的远端执行模式。

10.2 SSH 模式的核心价值是“远端算力 / 文件,本地身份 / UI”

这条设计最漂亮的地方在于它切分了两个经常绑死在一起的东西:

1. 执行环境   远端 Linux 主机,远端文件系统,远端工具链。

2. 用户身份与交互表面   本地认证,本地 REPL,本地权限确认。

这意味着用户不需要:

– 在远端机器上重新登录 Claude– 在远端重建完整本地交互环境

只需要把执行搬过去。

10.3 useSSHSession() 说明 SSH 在 REPL 里也是外部会话接入

src/hooks/useSSHSession.ts 是一个很好的架构信号。

因为它和 useDirectConnect()useRemoteSession() 是同一层级的 hook。

这说明 REPL 宿主看待 SSH 的方式不是:

– 进入一个特别模式后重写整个 UI

而是:

– 再接入一种新的外部会话 provider

在这一层里:

– 远端 child process 输出 SDK 消息– 本地通过 sdkMessageAdapter 转换– permission request 被转成与本地 UI 相兼容的确认流

这跟 CCR remote session 很像的一点是:

– 执行可以在外面– 但权限确认与交互体验仍尽量被收回本地

10.4 SSH 再次证明 remote 并不等于 cloud

Claude Code 里的 remote 不是单指“云端 Claude Code 服务”。

它也可以是:

– 你自己的 Linux 主机– 你自己的 repo– 你自己的远端工具链

这让整个 execution topology 的边界更广:

– cloud-hosted remote session– locally-bridged environment– ssh-hosted remote CLI

它们不是谁替代谁。

而是在覆盖不同的执行主权场景。

11. REPL 作为统一宿主:remote path 都是在往 REPL.tsx 插 provider

到这里就能更好理解 src/screens/REPL.tsx 的意义了。

11.1 REPL.tsx 同时接多种 remote provider

从代码结构看,REPL 同时接入:

– useRemoteSession(...)– useDirectConnect(...)– useSSHSession(...)– useAssistantHistory(...)– useReplBridge(...)

这非常像一个宿主应用在接多个 transport / runtime backend。

其中最关键的是:

– 它们不是彼此深度耦合– 它们大多通过 hook 返回的状态与回调接入 REPL 统一消息表面

所以 REPL 更像一个:

– message host– input host– state host– permission host– task host

11.2 activeRemote 这种设计说明 remote 是插槽,不是流程重写

REPL 会在:

– ssh remote– direct connect– remote session

之间选择活跃远程提供者。

这个选择模型非常说明问题。

如果 remote 只是一个布尔开关,根本不需要 provider 抽象。

只有当系统明确知道:

– 会有多种外部执行提供者– 它们共享部分 UI 合约– 但 transport 与生命周期不同

才会出现这种宿主层插槽设计。

11.3 handleRemoteInit() 说明 command surface 也会被远端协商

REPL 在收到 remote init 后,会根据远端会话给出的 slash commands 过滤本地命令表面。

这件事虽然看起来像 UX 细节,实际上透露出一条非常强的系统原则:

在外部执行模式下,本地 UI 展现的能力表面必须与远端 runtime 的真实能力对齐。

换句话说,本地 REPL 不能假装自己拥有一切。

这就是为什么 remote path 下很多 command / tool / permission surface 都会重新裁剪。

12. 从 remote 转到 multi-agent:Claude Code 不是只有“子代理”,而是一个 agent 派生体系

现在转向第二条主线:multi-agent。

如果只是看 AgentTool 的名字,容易误以为:

– 这是给模型调用的一个“帮我开个子 agent”工具

但源码展开以后,会发现它远比这复杂。

12.1 AgentTool 的输入字段已经暴露出它在做 routing

根据摘要,AgentTool 支持的输入字段包括:

– description– prompt– subagent_type– model– run_in_background– name– team_name– mode– isolation– cwd

其中最能说明问题的是这几项:

– run_in_background– name– team_name– mode– isolation

这些字段已经不是“开一个子 agent”所必需的最小输入。

它们对应的是不同执行拓扑:

– 后台还是前台– 子代理还是 teammate– 本地还是远端– 当前目录还是 worktree

也就是说,AgentTool 的本质不是 spawn。

而是 route。

12.2 一个工具里出现多条完全不同的 agent 路径,说明这里已经是 execution plane

根据 AgentTool.tsx 的主分支,至少有这些路线:

1. teammate spawn2. fork subagent3. remote isolation4. worktree isolation5. async local background6. sync foreground runAgent

如果一个工具内部只是在做参数解析,不会出现这种结构。

它之所以这么复杂,是因为它承担的不是能力本身,而是:

– 给模型提供一个统一入口– 再由运行时决定“这个请求应该变成哪种 agent 执行形态”

这非常像调度器入口,而不像普通工具。

13. 六类 agent 形态逐一拆开

这一节很关键。

因为如果不拆清楚,就会把后台 agent、remote agent、teammate 全部混成一锅。

13.1 同步 foreground subagent:最接近“传统子代理”

这是最容易理解的一类。

当没有选择后台、没有选 teammate、没有走 remote isolation、没有走特殊 fork 路径时,AgentTool 会直接调用 runAgent()

这种模式的特点是:

– 当前父 agent 等待它完成– 子 agent 的结果同步返回– 执行仍在当前总体会话上下文之内

从产品语义上,这最像我们通常理解的:

– “帮我开一个子代理去做一件事,然后把结果带回来”

13.2 本地异步后台 agent:local_agent

当选择后台运行时,路线会切到:

– registerAsyncAgent(...)– 再通过生命周期逻辑异步执行

这一类的核心特点是:

– agent 不阻塞当前父 turn– 会被登记为 task– 完成后通过通知机制告诉主会话

这里非常重要的一点是:

它不是简单 setTimeout 或 fire-and-forget promise。

它是正式 Task

– 有任务 ID– 有状态– 有 retain / evict / diskLoaded 等语义– 有通知格式– 可以与父任务/父 session 建立谱系关系

13.3 远程隔离 agent:remote_agent

当 isolation: "remote" 时,逻辑会进入另一条完全不同的路线:

– 先做 eligibility check– 通过 remote session / teleport 等机制启动远端 agent– 再登记成 remote_agent task

这类 agent 的几个核心特征是:

– 执行不在本地– 结果通过远端会话事件回流– task 展示是本地的,但 task 实体主要在远端推进

这类设计非常有意思。

因为它说明 multi-agent 已经和 remote execution 深度耦合。

在 Claude Code 里,agent 派生不是纯本地计算结构。

它也可以是分布式执行结构。

13.4 worktree 隔离 agent:本地 agent 的目录级隔离

createAgentWorktree(...) 这条路径说明:

即使不把 agent 发到远端,也可以给它一个隔离出来的 git worktree。

这类 agent 的价值主要有两个:

1. 避免同一 repo 上多个 agent 同时改同一 working tree。2. 让 agent 可以在更安全的环境里试验性修改文件。

需要特别注意的是:

worktree 不是一种 agent 类型。

它是一个与 agent 类型正交的隔离轴。

也就是说:

– 你可以有 foreground worktree agent– 也可以有 background worktree agent– 某些 fork path 也可能结合 worktree

13.5 process-based teammate:pane / child process 团队成员

这一类 agent 已经不再像普通 subagent。

它有更强的身份语义:

– name– team_name– 颜色– 团队上下文– 消息传递

spawnMultiAgent.ts 揭示了这套体系的几个特点:

– 可以使用 tmux backend– 可以使用 iTerm2 backend– 若不在 tmux 中,也可以建 claude-swarm session– 每个 teammate 都可以启动一个新的 Claude 进程或 pane– 启动命令会带:  – --agent-id  – --agent-name  – --team-name  – --agent-color  – --parent-session-id  – 可选 --plan-mode-required  – 可选 --agent-type

这已经不是“开个子 agent”。

这是把 Claude Code 扩成一个 swarm 式的多实体协作界面。

13.6 in-process teammate:同进程多代理,而不是多进程多代理

这条线尤其值得单独强调。

因为它很容易被忽略。

src/utils/swarm/spawnInProcess.ts 与 src/tasks/InProcessTeammateTask/* 说明,Claude Code 并没有把 teammate 绑定死在 tmux / iTerm2 / 子进程模型上。

它还支持:

– 在同一个 Node.js 进程内创建 teammate– 使用 AsyncLocalStorage 与 teammate context 逻辑隔离– 把 teammate 作为 task 登记– 让它在同一进程里跑自己的连续 prompt loop

这非常重要。

因为它说明作者把“teammate”抽象成了逻辑实体,而不是 OS 进程实体。

进程只是实现之一。

13.7 fork subagent:一种特殊的上下文分叉语义

FORK_AGENT 路径说明 Claude Code 还支持另一种特殊子代理:

– 它不是普通 agent definition– 而是带 forked context / forked subagent memory 语义的一条路线

这类设计通常解决的问题是:

– 子代理需要共享父上下文的大部分内容– 但又要保留某种隔离、差异化或快照式的分叉执行语义

它本质上是“子代理”与“会话分叉”之间的折中模型。

14. runAgent():所有 agent 形态背后的通用执行内核

虽然 AgentTool 很像 routing plane,但真正执行 agent 的还是 runAgent()

14.1 runAgent() 说明多代理不是另一套简化版 Claude

src/tools/AgentTool/runAgent.ts 支持的东西很多:

– agent-specific MCP server 初始化– parent / child context– forked subagent context– transcript 记录– hooks– allowedTools / permission mode

这意味着 subagent 并不是一个“比主 agent 弱很多的小号模型调用”。

相反,它基本也是完整 Claude 执行循环的一个变体。

14.2 agent 可以携带自己的 MCP server,这是非常强的设计

initializeAgentMcpServers() 尤其值得注意。

这意味着 agent definition 不只是 prompt 和 model 选择。

它还能带自己的工具世界。

这会带来几个非常强的效果:

1. 一个 agent 可以是领域特化执行体,而不只是 prompt persona。2. agent 可以按需引入额外外部能力,而不污染主会话工具池。3. inline MCP 定义还能在 agent 完成后清理掉,避免运行时泄露。

这让 Claude Code 的 agent 更像:

– 带专属能力上下文的临时执行角色

而不是简单的“换个系统 prompt 再问一次模型”。

14.3 transcript 与 lineage 说明 agent 被当成长期对象而不是函数调用

runAgent() 会记录 transcript、metadata、hook 状态,这说明在系统设计上,agent 不只是一次函数调用。

它还是:

– 可恢复的– 可观察的– 可归档的– 带 lineage 的执行实体

这正是后面 task system 能把它们统一管理的前提。

15. LocalAgentTaskRemoteAgentTaskInProcessTeammateTask:task system 如何把异构 agent 重新压平

前面说过,task system 是这章的共同抽象层。

现在展开讲。

15.1 LocalAgentTask:后台 agent 不是 promise,而是具名对象

src/tasks/LocalAgentTask/LocalAgentTask.tsx 的状态定义显示:

– 类型为 local_agent– 有进度 tracking– 有 queued messages– 有 retain / diskLoaded / evictAfter– 有 isBackgrounded

这些字段加在一起,说明本地后台 agent 被视为:

– 一个会长期存在、需要被 UI 管理、可能被恢复或驱逐的任务对象

而不是普通 JS 异步函数。

15.2 enqueueAgentNotification() 表明 task 不是内部对象,而是用户可见契约

本地 agent 完成时会生成结构化 <task-notification> 消息。

这意味着 task system 和主消息流之间不是隔离的。

任务会显式重新写回用户看得见的会话消息世界。

这正是为什么:

– 用户不用轮询后台 agent– 主会话也不需要一直阻塞等待

Claude Code 借 task-notification 机制,把“后台完成”重新映射回主线程叙事。

15.3 RemoteAgentTask:远端 agent 拥有自己的对象模型,不只是远端计数

src/tasks/RemoteAgentTask/RemoteAgentTask.tsx 很能说明 remote multi-agent 已经产品化了。

它并不是一个极简任务壳。

它包含:

– type: 'remote_agent'– sessionId– command– todoList– log– pollStartedAt– remoteTaskType– 甚至一些扩展类型:  – ultraplan  – ultrareview  – autofix-pr  – background-pr

这非常重要。

因为它说明 remote agent 不是单一 feature。

它已经成为远端任务平台的统一任务壳。

15.4 RemoteAgentTask 的 eligibility check 显示远端代理不是“想发就发”

摘要中提到的 precondition error 也很关键,例如:

– 未登录– 没有 remote environment– 不在 git repo– 没有 git remote– GitHub app 未安装– policy blocked

这些限制说明 remote agent 已经不是一个 purely technical execution choice。

它同时受:

– 产品权限– 仓库上下文– 账号绑定– 远端基础设施能力

共同约束。

这就是为什么远端 multi-agent 更像产品能力,而不是单纯工程实现。

15.5 InProcessTeammateTask:teammate 也是 task,而不是单纯 UI 视图

这点也特别重要。

即使是 in-process teammate,系统也不会把它只当作:

– 一个独立 message pane

而是把它显式登记成:

– in_process_teammate task

并为它提供:

– shutdown request– append message– inject user message– 根据 agentId 查找 teammate task

这说明 task system 不是“后台任务区”。

它实际上是整个多执行体系统的统一索引层。

16. spawnMultiAgent.ts:teammate 体系其实是另一套编排系统

如果说 AgentTool 管的是 agent routing。

那 spawnMultiAgent.ts 管的就是 teammate orchestration。

16.1 这里的第一原则是“后端可替换”

spawnMultiAgent.ts 会先做 backend 检测:

– tmux– iTerm2– in-process fallback

这说明 teammate 系统从一开始就没被绑死在某个 UI 平台上。

它要解决的是:

– 如何把多个协作 Claude 实体放出来

至于:

– 用 pane– 用进程– 用同进程逻辑实体

都是实现细节。

16.2 process-based teammate 保留了强烈的终端原生气质

tmux / iTerm2 backend 这一部分非常 Claude Code。

因为它不是在浏览器里做一个“多 agent 面板”。

而是延续了终端工作流:

– 当前 tmux window 里切 pane– 若不在 tmux 中,就建 claude-swarm session– iTerm2 原生分屏则走单独 backend

这和很多云端多 agent 产品完全不同。

它的立足点仍然是:

– 用户正在一个真实终端工作环境里写代码

16.3 in-process fallback 说明作者更看重“能力存在”,而不是“表现形态一致”

当 pane backend 不可用时,系统会回落到 in-process teammate。

这条 fallback 非常说明 Claude Code 的工程取向:

– 宁可牺牲一点“每个 teammate 都一个真实 pane”的可见性– 也要保住多 teammate 能力本身

也就是说,这个系统优先保护的是功能语义,而不是表现形式。

17. AgentTool 的几个关键路由判断值得单独讲

为了真正理解 multi-agent,这里要把 AgentTool 几个特别关键的判断讲透。

17.1 kairosEnabled 会强制 agent async

这条判断很有代表性。

assistant / kairos 模式下,agent 会被强制异步。

原因不是“体验更好”这种泛泛解释。

而是:

– daemon / assistant 输入队列必须保持响应– 如果 agent 同步阻塞,会卡住宿主输入流

这说明 agent 调度方式不是本地偏好问题。

它必须服从宿主 runtime 的响应性需求。

17.2 remote isolation 总是 backgrounded

这条规则也非常合理。

一旦 agent 被送到远端跑,它就天然更像 task,而不是当前 turn 内联子调用。

如果还强行做同步等待:

– UI 会变复杂– reconnect / detach / resume 语义会更难统一

所以 remote isolation 直接落成 task,是一个非常清醒的架构决定。

17.3 teammate spawn 不是子代理升级版,而是另一种协作结构

当存在 team_name && name 时,流程会走 spawnTeammate(...)

这本质上说明:

– teammate 不是 anonymous subagent– 它有身份,有团队语义,有长期消息面,有可能长期存活

这和一次性 subagent 是两种不同对象。

17.4 fork path 说明“上下文克隆”是明确的一等需求

如果用户或模型没有指定特定 subagent type,而 fork gate 打开,就可能默认走 FORK_AGENT

这透露出一个很重要的产品需求:

– 用户并不总是想选一个明确 agent 类型– 但系统希望提供一种“从当前上下文分叉出去做事”的捷径

这让 fork subagent 更像:

– 当前 agent 的分身

而 არა:

– 团队里的另一个固定角色

18. 从权限角度看:remote 与 multi-agent 都在做“执行权分离,但确认权回收”

这一节把前面几条线合在一起看,会更清楚。

18.1 bridge 中的 control_request

在 bridge child session 里,工具权限申请会变成 control_request

这表示:

– 执行发生在子会话– 但权限确认权要抬升到 bridge 层

18.2 remote session 中的 permission request

在 CCR remote session 中,也是类似:

– 远端会话触发权限请求– 本地 RemoteSessionManager 与 useRemoteSession() 把它接回本地 UI– 用户在本地确认

18.3 SSH 中的 permission UI 仍尽量留在本地

SSH 模式也沿用同样模式:

– 远端 CLI 发出需要确认的动作– 本地 UI 以与本地工具近似的方式展示确认流

18.4 这是一条非常一致的产品原则

综合起来看,Claude Code 的产品原则很清楚:

– 执行权可以下沉、外移、远端化、子进程化– 但高风险确认权尽量收回到用户当前主交互表面

这条原则是整个分布式执行体系能成立的关键。

否则用户会很快丧失对系统的可控感。

19. 从状态角度看:remote 与 multi-agent 都在做“状态归属分层”

这一节继续从更抽象的角度总结。

19.1 本地 foreground session

这类最简单:

– transcript 在本地– tasks 在本地– title 在本地– commands / tools 在本地 runtime

19.2 repl bridge

这里开始出现分层:

– 主状态仍主要在本地– bridge 维护对外可接入会话身份– 外部客户端可以消费或协助控制该状态

19.3 standalone bridge

这里又提升一层:

– environment state 在 bridge host– child session state 在 Claude 子进程– 外部控制面通过 bridge API / ingress 间接操控

19.4 CCR remote session

这里状态归属更明显在远端:

– session state 在远端– 本地只保留 UI projection、权限对话状态、少量回显控制状态

19.5 assistant viewer

这里本地更进一步退化为:

– 历史分页缓存– 当前 UI 滚动与显示状态

19.6 remote agent

这里又有 task 级别分层:

– 真正执行状态在远端– 本地持一个映射后的 task 壳与通知表面

这个“状态归属分层”视角特别能帮助理解整个系统。

因为 Claude Code 从来没试图用一套状态模型平推所有执行形态。

它做的是:

– 允许状态真源在不同地方– 再靠 adapter / task / bridge / viewer,把这些状态投影回主交互表面

20. 这套设计最强的地方:Claude Code 把“在哪跑”与“怎么交互”解耦了

这一章到这里,可以开始总结优点。

我认为这套设计最强的点,不是 remote feature 多。

而是它系统地把两件常被绑死的事情解耦了:

1. 执行在哪里发生2. 用户主要在哪里交互

在很多工具里:

– 本地执行就本地 UI– 云端执行就网页 UI– SSH 执行就 SSH shell UI

Claude Code 没这么做。

它让:

– 本地 REPL 可以承载远端云会话– 本地 REPL 可以承载 SSH 会话– 外部客户端可以接入本地 bridge 会话– 多 agent 可以是本地、远端、同进程、异进程

一旦做出这种解耦,产品形态就会非常灵活。

21. 第二个强点:复用现有 CLI,而不是为 remote / multi-agent 重写第二套运行时

sessionRunner.ts 用 child CLI + stream-json,runAgent() 复用 agent 执行循环,SSH 也通过 SDK message 接回来。

这说明 Claude Code 一直在避免一件危险的事情:

– 为每个新拓扑重写一套 Claude 解释器

它更倾向于:

– 保持一个主 CLI / query runtime– 用不同 transport、adapter、宿主、task 壳,把它放到不同拓扑里

这是一个很成熟的工程选择。

因为它极大降低了行为漂移风险。

22. 第三个强点:task system 成为所有异步 / 分布式执行体的统一用户表面

如果没有 task system,这套设计会很快失控。

因为用户会看到:

– 有的 agent 在消息流里完成– 有的 agent 在远端列表里完成– 有的 teammate 在 pane 里跑– 有的后台任务没有统一入口

现在不是这样。

Claude Code 尽量把这些执行体统一还原成:

– 可查看– 可通知– 可恢复– 可归档– 可关联 lineage

的任务对象。

这也是为什么 task system 在这套产品里不是“附属功能”,而是中央整流器。

23. 代价一:概念边界非常多,学习成本高

这一章研究下来,最大的代价非常清楚。

就是概念边界真的很多:

– bridge– repl bridge– standalone bridge– remote session– assistant viewer– ssh session– direct connect– subagent– async agent– remote agent– teammate– in-process teammate– fork subagent– worktree isolation

这些名词不是 marketing 术语,而是源码里真实存在的不同拓扑。

这让系统很强。

但也让理解成本陡增。

23.1 许多 feature 名字彼此接近,但权责不同

例如:

– remote-control– --remote– assistant

都带 remote 感,但执行权完全不同。

对于读源码的人,如果不先建立拓扑脑图,很容易把几个分支误当成同一系统的变体。

24. 代价二:REPL 宿主越来越像总装配器

虽然前面一直说 REPL 是宿主,这是它的优点。

但反过来看,这也是一种集中复杂度。

因为 REPL 需要同时接住:

– 本地 query loop– 远端 session provider– ssh provider– assistant viewer history– repl bridge– permission queue– task panel– command filtering

这意味着 REPL 层天然会变得很重。

它必须知道很多“我当前到底是哪个拓扑”的判定。

这种集中化设计虽然能让用户体验统一,但也会让维护者面对一个越来越像 runtime orchestrator 的大组件。

25. 代价三:状态真源分散,恢复与一致性会变难

当系统允许:

– 本地 transcript 为真源– 远端 session 为真源– bridge environment 为真源– task sidecar 为真源

之后,恢复与一致性问题就会自然膨胀。

这就是为什么源码里会出现大量与这些主题相关的机制:

– reconnect– pointer– session id compat– lazy history fetch– remote background task count– task metadata restore

它们都在解决同一个根问题:

当执行不再只发生在一个进程里时,怎样仍然给用户一条连续可理解的会话叙事。

26. 代价四:权限确认流必须跨层穿透

分布式执行最难的地方之一,从来不是传消息,而是传“需要人确认”的消息。

Claude Code 在:

– bridge– remote session– ssh

里都显式处理了这一点。

这当然是优点。

但从工程成本看,这也意味着:

– 任何新的执行拓扑,只要想成为一等能力,就得把 permission request/response 流打通

也就是说,权限确认已经成为所有新 transport、新 agent mode 的硬依赖接口。

27. 这一章最值得复用的工程模式

如果把这一章抽象成可复用模式,我会选下面五条。

27.1 用宿主 + provider,而不是为每种执行拓扑复制 UI

REPL 承载多个 remote provider 的思路非常值得学。

27.2 用 child standard runtime,而不是为桥接重写第二套执行引擎

sessionRunner.ts 的 child CLI + stream-json 是很漂亮的复用方式。

27.3 允许状态真源分散,但强制把用户表面统一回 task / message host

这是这套系统能保持可用性的关键。

27.4 把高风险确认权回收到主交互表面

这对 remote AI coding 产品几乎是必须的。

27.5 把 teammate 抽象成逻辑实体,而不是进程实体

in-process teammate 的存在非常有启发性。

它说明多代理协作的核心不是“多窗口”,而是“多身份、多上下文、多通信通道”。

28. 这一章最值得警惕的技术债

如果从长期维护角度看,我会重点警惕三种技术债。

28.1 模式数量继续增长时,命令与能力裁剪矩阵会爆炸

现在已经有:

– local– bridge– remote– viewerOnly– ssh– assistant– kairos– daemon

一旦 commands / tools / permissions / tasks 的行为都依赖这些模式,就会逐步形成复杂矩阵。

28.2 远端事件适配层越多,消息语义漂移风险越高

有了:

– sdkMessageAdapter– bridge message layer– task notification mapping

之后,一旦内部消息协议进化,就必须保证多条 adapter 路线同步更新。

28.3 task system 会被越来越多职责吸附

现在 task system 已经不只是后台列表。

它承担的是多执行体统一表面。

随着产品功能继续扩张,task system 很容易继续膨胀成超大中心。

29. 回答开头的十二个问题

现在回到开头。

29.1 remote-control--remoteassistantssh 不是同一套东西

它们共享的是:

– 本地 REPL 作为宿主– 远端或外部执行体需要被投影回主交互表面

但执行权、状态归属、transport、权限确认路径都不同。

29.2 远程能力是多形态并存,而不是单一路线

Claude Code 显然没有押注一种 remote 架构。

它同时支持:

– outward bridge– inward cloud session client– pure viewer– ssh execution

29.3 repl bridge 既是同步,也是控制入口

它不是单向镜像层。

它会处理初始化、安全裁剪、控制消息、全局 handle 与会话 continuity。

29.4 standalone bridge 是环境注册器 + 会话生成器

claude remote-control 的主语不是会话,而是本地环境。

29.5 bridge 核心层与 REPL 包装层是分开的

initReplBridge.ts 负责 REPL 上下文准备,replBridge.ts / core 负责真正 bridge 生命周期。

29.6 CCR remote session 的执行权在远端

本地 REPL 是客户端与 permission host。

29.7 assistant viewer 是纯观察者壳

它不拥有会话执行控制权,只负责展示与分页历史。

29.8 SSH 是本地 UI + 远端 CLI 执行

它与 bridge / remote session 都不同,是另一种 transport 设计。

29.9 multi-agent 不止一种

至少包括:

– sync subagent– async local agent– remote agent– worktree-isolated agent– process-based teammate– in-process teammate– fork subagent

29.10 AgentTool 是 execution routing plane

它并不只是在开子 agent。

它在决定 agent 该以哪种拓扑诞生。

29.11 task system 是异构执行体的统一用户表面

这是整个系统没有碎掉的原因。

29.12 这套设计的核心价值是“执行位置与交互位置解耦”

而核心代价是概念增殖、状态分层、一致性恢复复杂化。

30. 给这一章一个最终结论

如果要用一句最压缩的话概括这一章,我会写成:

Claude Code 并不是一个只能在本地终端里跑单一 agent 的工具,而是一个能够把同一套 agent runtime 投射到多种执行拓扑中的终端宿主。bridge 负责把本地环境暴露出去,remote session 负责把远端会话接进来,SSH 负责把执行搬到另一台机器,multi-agent 则把一次执行扩成一组协作执行体,而 task system 负责把这些异构执行体重新收敛成统一的用户表面。

这背后体现出的产品观非常明确:

– Claude Code 不是把“终端聊天”做强一点。– 它是在把“终端里的 agent runtime”做成一个可桥接、可远程、可团队化、可后台化的执行平台。

31. 下一篇应该看什么

这一章分析完以后,顺理成章的下一篇就应该进入:

– context budget– compact– snip– transcript– memory– session restore

也就是:

当执行拓扑已经被扩成这样以后,Claude Code 是怎么避免上下文爆炸、怎么维持会话连续性、怎么做 memory/session persistence 的。

所以下一篇应该进入:

10-memory-compaction-context-and-session-persistence.md

因为到了这一章以后,你会发现另一个核心问题已经浮出来了:

当系统允许本地、远端、桥接、后台 agent、viewer、SSH 全部共存时,

上下文和会话连续性到底如何不崩。

那正是下一篇的主题。

慧响精英荟开张啦~扫码加入,更有限时免费进入慧响星球的福利哦~(如二维码过期,请寻找最新文章底部或后台留言)

雁过留名 请记得点击赞、在看哦~