Remote agent 机制
这页回答什么
Claude Code 里,所谓 remote agent 到底是怎么被发起、托管、轮询、通知回本地会话的?
关键文件:
src/tools/AgentTool/AgentTool.tsxsrc/tasks/RemoteAgentTask/RemoteAgentTask.tsxsrc/utils/teleport.tsxsrc/utils/messageQueueManager.ts
一句话结论
remote agent 不是“换个地方跑 runAgent()”,而是把一次 agent 工作转译成一个远端 session,然后由本地
RemoteAgentTask充当该 session 的代理对象和轮询器。
也就是:
- 发起 remote agent →
AgentTool.call() - 真正创建远端 session →
teleportToRemote() - 在本地注册生命周期对象 →
registerRemoteAgentTask() - 持续轮询远端事件 →
startRemoteSessionPolling() - 完成后通过 task-notification 回灌主会话
这是一套“远程作业控制”模式,不是本地子 agent 的简单变体。
1. 入口仍然是 AgentTool
在 AgentTool.call() 里,先算出:
effectiveIsolation = isolation ?? selectedAgent.isolation
然后判断:
- 如果
effectiveIsolation === 'remote' - 且 remote eligibility 检查通过
- 就进入 remote 路径
核心代码意图很清楚:
checkRemoteAgentEligibility()teleportToRemote({...})registerRemoteAgentTask({...})- 返回
remote_launched
所以 remote agent 在架构上的定位仍然是: 一次 Tool 调用,只是它的执行载体变成远端 session。
2. teleportToRemote() 做的不是执行,而是“把环境送过去”
从调用点能看出来,teleportToRemote() 接收:
initialMessage: promptdescriptionsignalonBundleFail
结合 teleport.tsx 中大量关于 bundle / git source / env / repository 的逻辑,可以推断它的职责是:
A. 判断怎么把当前工作上下文带到远端
可能路径包括:
- git source
- bundle 打包上传
- 空 sandbox
B. 在远端创建一个 session
返回:
session.idsession.title
C. 远端真正的执行入口不是本地继续 query()
而是远端平台接手该 session 的后续运行。
所以 teleportToRemote() 本质更像:
“远端执行环境 provisioning + session creation”
而不是:
“在本地启动一个 remote flavored runAgent”
这点非常关键。
3. RemoteAgentTask 是远端 session 的本地镜像
registerRemoteAgentTask() 会立刻做几件事:
- 生成
taskId - 先创建 output file
- 构造
RemoteAgentTaskState - 注册到
AppState.tasks - 把 metadata 持久化到 session sidecar
- 启动
startRemoteSessionPolling()
它记录什么状态
RemoteAgentTaskState 关注的是:
sessionIdremoteTaskTypecommandtitlelogtodoListpollStartedAtisRemoteReviewisUltraplanisLongRunningremoteTaskMetadata
这说明它关心的核心不是本地推理现场,而是:
一个远端 session 当前演化到了哪一步,以及本地如何把它展示和管理起来。
4. 为什么要写 sidecar
registerRemoteAgentTask() 会调用 persistRemoteAgentMetadata()。
注释写得很直白:
- 这是为了
--resume时能重新连回还在跑的 remote session - status 不直接持久化,本地恢复时重新向远端拉
也就是说 remote agent 设计时就默认:
- 本地 CLI 可能退出
- 会话可能稍后恢复
- 远端任务依然继续存在
这和本地同步 agent 完全不是一个生命周期模型。
5. 轮询器才是 remote agent 的核心
startRemoteSessionPolling() 是整条链真正的心脏。
它每秒钟:
- 从 AppState 取 task
- 调
pollRemoteSessionEvents(task.sessionId, lastEventId) - 合并
newEvents - 把文本追加到 output file
- 判断 session 是否完成 / 失败 / archived
- 更新任务状态
- 必要时发 notification
这里的关键区别
本地 agent 的完成信号往往来自:
- 本地函数返回
- 本地 Promise resolve/reject
remote agent 的完成信号来自:
- 远端 session event stream
- 本地 poller 对事件流做归约后得出结论
所以 remote agent 的控制面其实是:
事件流驱动,而不是函数返回驱动。
6. 轮询器在做“状态解释”,不只是拿日志
pollRemoteSessionEvents() 返回后,本地不是简单显示一下输出就完了。
它会继续解释这些事件:
A. 日志累积
accumulatedLogappendTaskOutput(taskId, deltaText + '\n')
B. session 归档判定
如果 sessionStatus === 'archived':
- 直接认为完成
- 发完成通知
- 清理 output / metadata
C. completion checker
对于特定 remoteTaskType:
- 可注册自定义 completion checker
- 每次 poll tick 都跑一次
- 命中即视为完成
这说明 remote task 框架支持“不同产品形态挂不同完成判定器”。
D. result message 判定
常规 remote agent 会从累积日志中找最后一个 result 事件,作为成功/失败依据。
这意味着 remote session 在协议层已经不是纯文本流,而是 typed SDK event stream。
7. 为什么 remote-review / ultraplan 会更复杂
代码里专门分出了:
isRemoteReviewisUltraplanisLongRunning- review-specific progress parsing
- specialized failure notification
说明 RemoteAgentTask 早就不只是“给 remote agent 用一下”。
它已经演化成一个通用远端任务控制框架,承载多种产品能力。
尤其是 remote-review:
- 会从 hook 事件中提取
<remote-review>tag - 解析 heartbeat 里的 JSON 进度
- 对 idle 状态做 debounce
- 超时 30 分钟失败
所以 remote task 的复杂度来自:
远端平台不是一次性返回结果,而是不断产生半结构化事件,本地要自己决定什么时候算“真的完成”。
8. 完成后怎么回到主会话
一旦 poller 认定完成,会调用:
enqueueRemoteNotification(...)- 或 remote-review 专用 notification
而 enqueueRemoteNotification() 最终走的是:
enqueuePendingNotification({ value: message, mode: 'task-notification' })也就是说,remote agent 的完成不会直接“塞一段字符串给用户”。 它会被包装成标准的 task-notification 消息,再进入统一消息队列。
这一步很重要,因为它说明:
远程任务完成事件,最终会重新变成当前对话循环的一部分输入。
这和后台本地 agent 的通知模型是一致的。
9. AgentTool 在 remote 路径上返回什么
AgentTool.call() 在 remote 路径不会等待结果,而是立刻返回:
status: 'remote_launched'taskIdsessionUrldescriptionpromptoutputFile
这类返回值的语义不是“任务结果”,而是:
“我已经成功把这件事交给远端系统了,后续请通过 task 机制观察。”
这和 async local agent 的 async_launched 非常类似,只是 remote 多了 sessionUrl 和更强的恢复语义。
10. local subagent 和 remote agent 的根本区别
local subagent
- 在本地进程里继续跑
- 最核心执行器是
runAgent() - Task 主要负责后台生命周期
remote agent
- 本地不直接执行 agent loop
- 本地先创建远端 session
- 之后依赖 poller 跟踪远端事件
- Task 不只是生命周期壳,更像远端代理对象
所以 remote agent 不是“runAgent 换个 isolation 参数”。 它是另一条 orchestration 模式。
11. 这套设计透露出的架构判断
我认为这里最有价值的设计判断有三个:
A. 用 Tool 接住 agent 调度
这样无论 local / async / fork / remote,主循环都不用知道细节。
B. 用 Task 接住长期生命周期
这样后台、恢复、通知、UI、输出文件都能统一。
C. 用 event polling 接住 remote 不确定性
远端运行天然不可靠,必须把“完成判定”从单点返回值升级为持续归约。
这三层拆分让 remote agent 虽然复杂,但没有把复杂度直接污染进 query() 主循环。
当前结论
Claude Code 的 remote agent 机制,本质上是:
一次由
AgentTool发起、由teleportToRemote()创建远端 session、由RemoteAgentTask在本地做代理与轮询、最后通过task-notification回灌主对话的远程任务系统。
它不是本地 subagent 的简单外包版,而是明确面向:
- 可恢复
- 可通知
- 可追踪
- 多产品形态复用
的一套远程 orchestration 设计。
下一步最值得继续追
teleport.tsx里 bundle / git source / env 注入的实际决策树pollRemoteSessionEvents()返回的 SDK event schema- remote-review / ultraplan 这两种特殊 remoteTaskType 如何复用同一套轮询框架