提供方约束
编排记录意图和状态,但并不知道是哪个提供方(provider)在运行某个对话线程(thread)。提供方的协议、账户归属、权限和能力,都属于适配器(adapter)边界。归一化应在那里完成,而不要把对提供方的检查散布到编排过程和各个客户端(client)里。
驱动(driver)的种类用来标识一种集成;实例用来标识一套配置及其账户生命周期。工作按实例路由,这样,使用同一个驱动的两个账户就不会共享可变的提供方会话(session)或模型目录状态。要新增驱动,请从添加提供方开始。
进程与账户隔离
opencode 驱动会探测已安装的版本,并运行 1.x 或 2.x 运行时。OpenCode 的 MCP 注册以目录为范围,而 T3 的 MCP 连接以对话线程为范围,因此同一目录中的对话线程不能共用同一个 T3 MCP 条目。
- 1.x 为每个对话线程使用一个由 T3 管理的聊天服务器,因此对话线程不能互相替换对方的连接。模型目录工作和文本生成工作可以共用归该实例所有的辅助进程,这个辅助进程会在空闲一段时间后关闭。参见1.x 适配器。
- 2.x 用每个实例一个服务器为所有目录提供服务。每个对话线程注册自己的
t3-code-<thread>MCP 条目,提供方会话的权限规则会拒绝其他所有对话线程的条目。参见2.x 适配器。
外部的 OpenCode 服务器仍然归外部所有,要让配置变更生效,可能需要从外部重启。OpenCode 会为整个项目(project)保存「始终」批准授权。自动发出的完全访问回复使用 once,这样它们就不能在共享服务器上扩大受监督对话线程的权限。在 2.x 上,覆盖整个会话的批准还会回复 once,并成为 T3 在该会话上自己的规则。
Pi 以 RPC 模式运行用户自己安装的 pi,并自行完成原生扩展、软件包和项目信任的发现。T3 只注入自己带命名空间的 MCP 桥,因此 Pi 会话的表现与在 Pi TUI 中一致。Pi 的会话文件支撑着原生恢复、回滚,以及同一实例内的对话线程分叉。分叉要在目标目录里使用 Pi 的 CLI,因为 RPC 会话切换会保留源会话的 cwd。切换提供方时,仍然使用可移植交接摘要。参见适配器。
Antigravity 按实例分开账户配置档,同时在环境(environment)内共享已安装的可执行文件。它强制使用基于文件的凭据存储,否则原生 macOS 钥匙串中的条目会在各个实例之间共用。启动环境会清掉环境里原有的 Google 凭据,因此一个实例不能在无提示的情况下使用另一个账户或计费项目。智能体还会在该配置档下解析用户的全局技能目录,于是该配置档会把这两个目录链接回用户真实的 ~/.gemini;位于那里的 MCP 服务器、钩子和规则不会进入该配置档。参见配置档隔离。
Antigravity 安装器的存活时间长于客户端连接,也长于提供方实例的重建。发布物不可变,由一个原子指针为新进程选定版本。正在运行的进程对自己的版本持有租约。更新和移除必须尊重这些租约,而不能在智能体仍在运行时替换它使用的可执行文件。
设置不得成为健康检查的副作用
打开一个提供方会话,可能会启动 MCP 服务器、运行钩子,或打开登录浏览器。Grok 探测正是出于这个原因才避免认证和创建会话。Antigravity 同样把已认证的模型目录会话留给显式的设置或模型刷新;后台检查只做初始化。
Antigravity 登录属于发起它的那次 T3 认证会话。客户端会把返回 URL 带回环境,因为提供方的环回监听器可能在另一台机器上。只转发本流程所拥有的、尚未完成的那次回调;回调 HTTP 请求成功,并不能证明提供方认证已经完成。令牌交换和存储由原生进程负责。
远程环境上的托管 ChatGPT 登录,可以在本地主端完成。主端交接使用一份临时的凭据存储,以及目标环境的环境 ID。它会先交换并核验代码,再转移已经签发的客户端注册信息和令牌。只有目标端会持久化并刷新该会话;如果主端也留下一份刷新会话,就会和刷新令牌轮换发生竞态。没有本地主端时,客户端使用远程回调的完成流程。
Antigravity 退出登录时,会先关闭对新进程的准入,并停掉现有进程,然后才清除账户元数据。否则,辅助进程或已恢复的会话仍可能留着旧账户。缓存的模型列表不能证明当前仍有访问权限;如果权威的模型目录是空的,就必须清掉旧列表。
Antigravity 的文本生成辅助进程会拒绝工具请求,但原生钩子和 MCP 配置可能在提示词之前就已经运行。带有这类配置的配置档会在启动前被拒绝。写在提示词里的指令,以及对工具的拒绝,并不会因此形成原生沙箱。参见辅助进程约束。
提供方更新只通过拥有该安装的安装器运行
只有当解析得到的可执行文件路径能够证明这次安装归它所有时,包管理器才会运行。Homebrew 和 npm 靠真实路径来证明(符号链接已被展开):brew --prefix 下带版本号的 keg 或 cask,或者 <prefix>/lib/node_modules/<pkg>/(Windows:node_modules 旁边的 shim)。原生安装器的布局,以及 pnpm、Bun、Yarn 和 Vite+ 的全局目录,既可以按已解析路径匹配,也可以按它的真实目标匹配,因为这些安装器会把真实文件或它们自己的符号链接放在那里。Volta 由其 volta-shim 链接加上该软件包的 image 目录来证明。如果没有任何一项能够证明,就改为运行 Pi 和 Grok 自己的更新器(pi update --self、grok update),因为它们各自会自己识别安装器;运行器的版本检查会抓住那种退出码为 0、实际上却没有更新的更新器。Claude、Codex 和 OpenCode 的更新器只处理它们自己的原生布局,因此除非该布局已被证明,这些提供方就只能手动更新。Mise 最先接受检查,因为 mise x 包装器可能正好位于原生安装器的路径上,而 MISE_DATA_DIR 可以把安装放到任何位置,所以会询问 mise which(前提是 mise 在 PATH 上)。每个驱动都会给出自己固定的 mise 工具名;mise 必须解析出该二进制文件,并在以 latest 请求到的、该工具的一份活动安装里列出它,这样才会得到 mise upgrade <tool>。T3 Code 仍会启动配置中的那条路径,因此该路径必须是 shim,或者位于该工具的 latest 链接之下,升级时会把这个链接改指到新位置:版本目录(包括被 mise activate 放到 PATH 上的那个)会一直运行旧的二进制文件,直到 mise 把它修剪掉。mise 所涉及的其他情况(固定版本路径、被锁定的工具、包装器,以及 mise 工具会跨进 2.x 的 OpenCode 1.x)都只能手动处理,因此 T3 Code 从不修改 mise 的配置。版本号由 mise latest 提供,因为 minimum_release_age 这类设置可能让 mise 落后于 npm。Homebrew 前缀无法核验的 Grok cask 也只能手动更新:grok update 会另外装上一份。npm 更新会固定 --prefix,因为 npm(它在 PATH 上)可能属于另一个 Node,而不是拥有该提供方的那个 Node。Homebrew 拿 brew info 来比较,因为 cask 会比 npm 晚几个小时;原生安装和 npm 走同一条版本线,所以对它们来说注册表仍然是权威来源。用 Chocolatey 安装的 Claude 只能手动更新:它的软件包目录只有管理员可以写,而通过 shim 运行 claude update 会在别的位置装上一份原生副本。最新版本来自 choco search,卡片上会显示 choco upgrade claude-code,但并不会去运行它。参见解析器。
归属关系按实例缓存,并在更新真正运行之前立刻重新读取。如果锁键从给出更新建议之后已经改变,运行器会拒绝这次更新;只有在重新检查之后,该提供方仍然安装着,并且版本可读、已经是当前版本时,才报告成功。
协议陷阱
Codex 的异步问题以通知的形式到达,回答方式是再发一条用户消息。并没有一个待发送的、挂起中的 RPC 响应。适配器把它们持久化为 user_input_request 回合(turn)条目,以及带有 responseCapability: { type: "message" } 的运行时请求。它们的执行节点不会阻塞这次运行。Web、桌面端和移动端使用各自平常的问题面板;回合结束、提供方退出或服务器重启之后,这些请求仍然处于待处理状态。
runtime-request.respond 读取已经持久化的请求和问题条目,校验必填答案,并在同一个事务里提交解决结果和一条用户消息。再次执行同一命令(command)时,会返回它的命令回执,而不会把答案再发送一次。常规的消息路径会开始或恢复一次运行,或者排在正在进行的工作后面,又或者在适配器支持时进行转向(steer)。阻塞式问题仍然走提供方的实时响应路径。不要仅仅因为某个请求不在最近的历史窗口里,就认为它已经消失。
原生的 /goal 状态属于提供方,并被镜像到提供方的对话线程上。上一个目标回合完成后,Codex 会在数毫秒之内自行开始下一个目标回合,因此适配器会让这次运行保持打开,并把该回合加进去。一次运行因此可以拥有多个原生回合。那些不会启动 Codex 回合的 /goal 命令,会落在一个没有原生引用的提供方回合上,原生回滚不得把它们计算在内。Claude 的 SDK 模式不会发出目标事件;它的适配器从会话记录里的合成命令输出,以及 Stop 钩子的反馈中读取目标状态。
能力必须描述提供方实际上能做的事。Antigravity 可以捕获工作区检查点(checkpoint),但不能回滚自己的对话。因此,检查点边界会在触碰文件之前拒绝还原。原生的权限选项 ID 和问题选项 ID 在归一化之后也必须保留下来;显示用的标签不一定是合法的回复。
附件与已存储的历史
附件放在项目工作区之外。附件边界会为对话线程校验并认领上传;适配器再为这些位于环境本地的文件选择原生输入格式。提示词里出现路径,并不等于授予了文件系统访问权。提供方的沙箱和批准规则必须继续有效;为了绕过它们而把上传复制进项目,会改变这条边界。
文件附件带来了一项重放兼容上的限制。只支持图像的客户端无法解码带有文件的消息;只支持图像的服务器只要重放一条这样的事件,就可能让整个环境启动失败。版本铺开和降级,都必须把已经持久化的历史和当前客户端的支持范围一起考虑进去。
提供方诊断
原生事件(event)日志会保留生命周期事件、响应和失败。令牌增量以及重复的原始帧,会在适配器复制或脱敏载荷之前被过滤掉。这个过滤器同时接受旧版原生事件和 v2 协议信封;解码失败仍然可以通过诊断帧看到。
日志载荷有 64 KiB 的编码预算。过大或嵌套过深的载荷会变成结构性摘要,其中保留路由标识符、方法、状态和错误字段。遍历在脱敏和序列化之前就设有上限,因此记录一条大型响应时,不需要复制出好几份完整内容。这些限制只用于诊断;提供方事件的处理方式并不改变。
当 Codex 需要的只是对话线程的身份和更新时间时,它会只用元数据来读取并恢复。它的初始化能力会选择退出 turn/diff/updated:T3 根据检查点来推导差异。如果较旧的提供方仍然发送这些通知,日志记录器会在遍历之前把它们过滤掉。