teleportToRemote session_context 与 outcomes

这页回答什么

teleportToRemote() 最终发出去的 create session 请求里,session_context 到底装了什么?sourcesseed_bundle_file_idoutcomesenvironment_variablesreuse_outcome_branchesgithub_pr 各自是什么意思?

关键文件:

  • src/utils/teleport.tsx
  • src/bridge/createSession.ts
  • src/bridge/types.ts
  • src/skills/bundled/scheduleRemoteAgents.ts

一句话结论

teleportToRemote() 真正交给后端的不是一个简单 prompt,而是一份 session bootstrap specsession_context 负责描述远端 session 的初始执行世界:代码来源、期望 git 产出、模型、环境变量、PR 关联,以及是否复用现有 outcome branch。

其中最关键的是两层:

  • sources / seed_bundle_file_id: 远端如何拿到代码
  • outcomes: 远端完成后希望把 git 结果产出到哪里

1. 自动分支下最终的 session_context

自动 environment 选择路径里,teleportToRemote() 最后构造的是:

const sessionContext = {
  sources: gitSource ? [gitSource] : [],
  ...(seedBundleFileId && { seed_bundle_file_id: seedBundleFileId }),
  outcomes: gitOutcome ? [gitOutcome] : [],
  model: options.model ?? getMainLoopModel(),
  ...(options.reuseOutcomeBranch && { reuse_outcome_branches: true }),
  ...(options.githubPr && { github_pr: options.githubPr })
}

然后包进:

{
  title,
  events,
  session_context: sessionContext,
  environment_id
}

这已经说明一个重要事实:

远端 session 的创建协议不是“只带 events”,而是 events + session_context + environment_id 三件套。


2. sources:远端从哪里获得仓库内容

结构

gitSource 形状是:

{
  type: 'git_repository',
  url: `https://${host}/${owner}/${name}`,
  revision,
  ...(options.reuseOutcomeBranch && {
    allow_unrestricted_git_push: true
  })
}

语义

sources 是给远端容器的 输入代码来源

它告诉后端:

  • clone 哪个 repo
  • 以哪个 revision 作为起点
  • 是否允许向某些分支直接 push

revision 是什么

它不是 outcome branch,而是:

  • options.branchName
  • 否则 getDefaultBranch()

也就是说:

revision 决定远端 session 从哪个 base ref checkout 开始工作。


3. seed_bundle_file_id:当 sources 不够用时,改用 bundle seed

如果 GitHub source 不可行,且 bundle fallback 成功,会写入:

session_context.seed_bundle_file_id = seedBundleFileId

这表示:

远端不是从 repo URL clone,而是从上传好的 git bundle 文件作为 seed 来初始化仓库。

这条路径的重要性很高,因为它覆盖了:

  • 本地 repo 有 .git,但无可用 GitHub clone 权限
  • GitHub App / token preflight 失败
  • 只想把本地状态原样投过去

所以 sourcesseed_bundle_file_id 是一组互补机制:

  • sources 偏“远端自己拉代码”
  • seed_bundle_file_id 偏“本地把代码种子送过去”

4. outcomes:不是输入,而是远端 session 期望产出的 git 落点

gitOutcome 在代码里构造成:

{
  type: 'git_repository',
  git_info: {
    type: 'github',
    repo: `${owner}/${name}`,
    branches: [sessionBranch]
  }
}

这和 sources 很不一样。

sources 解决

  • 从哪拿代码

outcomes 解决

  • 做完之后 git 结果应该往哪条 branch 产出

也就是说:

outcomes 更像 session 的“预期交付物声明”,不是输入 checkout 配置。

这也是为什么它叫 outcome,而不是 destination source。


5. sessionBranch 的来源:通常不是输入 branch,而是新生成的 outcome branch

自动路径里:

  • options.title && options.reuseOutcomeBranch,直接复用
  • 否则 generateTitleAndBranch(...)
  • 最后 sessionBranch = options.reuseOutcomeBranch || generated.branchName

所以默认情况下:

  • source 的 revision 可能是默认分支 / 显式 base branch
  • outcome 的 branches[0] 则通常是新生成的 claude/...

这说明 remote session 的 git 语义本来就是两层:

  1. 从哪条 base branch 开始
  2. 最终改动要落到哪条 outcome branch

这点非常关键,不能把 revisionoutcomes.branches 混成一个东西。


6. reuseOutcomeBranch:让 remote 直接复用调用方现有分支

当传入 reuseOutcomeBranch 时,会发生两件事:

A. source 侧

gitSource 增加:

allow_unrestricted_git_push: true

B. session_context 侧

增加:

reuse_outcome_branches: true

再加上:

  • sessionBranch = options.reuseOutcomeBranch

组合起来的效果是:

远端 session 不再新建一个 claude/... outcome branch,而是把当前 session 的 git 输出直接复用并推回调用方指定分支。

所以这里不是单一 flag 生效,而是:

  • source 允许 push
  • session_context 表明要复用 outcome branch
  • outcomes 指明 branch 名

三者一起构成“直接写现有分支”的协议。


7. github_pr:把 session 绑定到某个 PR 上下文

若调用方提供:

githubPr: { owner, repo, number }

session_context 里会带:

github_pr: { owner, repo, number }

代码注释写得很直接:

Backend uses this to identify the PR associated with this session.

也就是说这不是本地逻辑消费字段,而是显式传给后端,让远端 session 带上 PR 语义。

它很可能影响:

  • session 关联展示
  • review / code review 类型工作流
  • 后端如何理解这次 remote 任务的目标对象

8. model:session_context 里直接指定远端模型

自动路径里:

model: options.model ?? getMainLoopModel()

说明 remote session 默认会继承当前主模型,除非调用方显式覆盖。

这也再次证明:

远端 session 创建不是只把 prompt 送走,而是把执行配置一起送走。

模型本身就是 session bootstrap spec 的一部分。


9. 显式 environmentId 分支:session_context 更简,但多了 environment_variables

当传入显式 environmentId 时,构造的是另一版:

session_context: {
  sources: gitSource ? [gitSource] : [],
  ...(seedBundleFileId && { seed_bundle_file_id: seedBundleFileId }),
  outcomes: [],
  environment_variables: envVars
}

和自动路径相比有几个明显差异:

有的

  • sources
  • seed_bundle_file_id
  • environment_variables

没有的

  • model
  • gitOutcome
  • reuse_outcome_branches
  • github_pr

这意味着显式 env 分支更像:

把代码和运行变量定向投送到某个特定环境里执行,而不是完整走一套标准 remote coding branch outcome 流程。

尤其是:

  • outcomes: []

这点很说明问题,表示该流程不强绑定标准 git outcome 产出。


10. environment_variables:明确是 write-only,主要用于容器内执行环境注入

注释里写得很清楚:

  • merged into session_context.environment_variables
  • API layer 是 write-only
  • Get/List 响应里会被 strip 掉

显式 env 分支里还会自动注入:

CLAUDE_CODE_OAUTH_TOKEN: accessToken

然后再 merge:

  • options.environmentVariables

这说明:

environment_variables 不是普通元数据,而是真正交给远端容器/运行时使用的环境变量输入。

并且它有个安全特征:

  • 可写入创建请求
  • 不会在后续 GET/LIST 里原样回显

11. eventssession_context 是互补关系

在 create-session body 里,events 主要承载:

  • 初始 user message
  • 可选 control_request,比如 set_permission_mode

例如:

{
  type: 'event',
  data: {
    type: 'control_request',
    request: {
      subtype: 'set_permission_mode',
      mode,
      ultraplan
    }
  }
}

以及:

{
  type: 'event',
  data: {
    type: 'user',
    message: { role: 'user', content: initialMessage }
  }
}

所以两者分工很明确:

events

  • session 一开始就写入的消息流/控制流

session_context

  • session 的静态启动配置

如果硬要比喻:

  • session_context 是容器启动规格
  • events 是启动后立刻喂进去的第一批输入

12. createSession.ts 侧验证:bridge 也在用同一套协议

src/bridge/createSession.ts 里也构造了近似结构:

session_context: {
  sources: gitSource ? [gitSource] : [],
  outcomes: gitOutcome ? [gitOutcome] : [],
  model: getMainLoopModel(),
}

这说明 session_context.sources/outcomes/model 不是 teleport 私货,而是更底层的 Sessions API 通用创建协议

也就是说:

  • teleport remote agent 在用它
  • bridge remote-control 也在用它

这进一步增强了判断:

session_context 是 Claude Code / CCR session 创建的标准 bootstrap contract。


13. types.ts 侧验证:environment_variables 会进入 work secret

src/bridge/types.tsWorkSecret 里出现了:

environment_variables?: Record<string, string> | null

这很关键,因为它说明:

session_context.environment_variables 不是创建后就丢了,而是会进入后续 worker / environment runner 能看到的 secret 载荷里。

所以这个字段不是 UI 装饰,而是实际运行面会消费的配置。


14. scheduleRemoteAgents.ts 侧验证:定时 remote agent 也是围绕同类 session_context 组织的

scheduleRemoteAgents.ts 的 prompt 模板里也明确让 trigger body 组织:

  • environment_id
  • job_config.ccr.session_context
  • sources
  • allowed_tools
  • events

这说明“远端 session = environment + session_context + events”这套思路,不只用于即时 teleport,也用于 scheduled remote agents。

所以可以把它理解成一套通用 CCR session 描述语言。


15. 最简字段语义表

输入代码相关

  • sources: 远端从 repo URL 获取代码
  • seed_bundle_file_id: 远端从 bundle seed 初始化代码

输出 git 相关

  • outcomes: 远端 session 期望把结果产出到哪些 repo branch
  • reuse_outcome_branches: 复用已有 outcome branch,而不是新建
  • allow_unrestricted_git_push: source 侧允许直接 push

执行环境相关

  • environment_id: 跑在哪个 environment
  • environment_variables: 容器内环境变量注入
  • model: 远端 session 用哪个模型

工作对象关联

  • github_pr: 把 session 绑定到某个 PR 语义上下文

启动输入相关

  • events: 初始控制请求 + 初始用户消息

当前结论

teleportToRemote() 最关键的产物可以概括成一句:

它不是创建“一个带 prompt 的远端会话”,而是在创建“一个有代码输入来源、有 git 结果目标、有运行环境和控制事件的远端执行单元”。

其中最需要分清的是:

  • sources 是输入
  • outcomes 是输出
  • events 是启动后的第一批消息/控制流
  • environment_id + environment_variables 是运行底座

把这四层分清,remote session 的协议就清楚了。


下一步最值得继续追

  1. 深挖 createAndUploadGitBundle() 如何编码 WIP / stash seed
  2. 细化 foreground -> background 热切换为什么要重新启动 async lifecycle
  3. 画一页 task-notification -> queued command -> attachment -> next query 精细序列图