ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Orca SSH 执行边界深度解析:远端/本地职责切分、断线存活模型与 live / unverifiable / exited 判定

2026/9/8 23:56:36 拓冰建站 浏览量
Orca SSH 执行边界深度解析:远端/本地职责切分、断线存活模型与 live / unverifiable / exited 判定 Orca SSH 执行边界深度解析远端/本地职责切分、断线存活模型与 live / unverifiable / exited 判定【免费下载链接】orcaOrca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and remote runtime.项目地址: https://gitcode.com/GitHub_Trending/orca48/orca本文聚焦 Orca 桌面端 SSH 远程执行模型下的核心边界问题哪些工作在远端主机执行、哪些在本地客户端执行断线后远端工作如何存活以及如何严格遵守live/unverifiable/exited三态词汇体系、避免把“联系不上”误报为“已退出”。读完你会掌握 Orca 的 relay 架构与主机所有权模型理解为何升级客户端会“孤立”而非“终止”远端终端并学会一套可操作的证据检验清单来判断远端进程的真实状态。本指南的主体内容继承自仓库内参考文档 ssh-execution-boundary.md并逐一对应仓库源码给出实现依据。适用前提Orca 的“客户端 SSH 远端主机”直接执行模型与“客户端 配对运行时代理peer/headless runtime”模型是两套对立模型务必先分清你正在使用哪一种。为什么需要一份“执行边界”文档在此之前docs/目录没有任何文档正面回答过这些问题Orca 把工作拆在本地与 SSH 主机之间时边界到底画在哪断线之后远端 PTY 是否还活着unverifiable与exited究竟差在哪Agent 和人类只能从错误字符串去反推经常得出错误结论。ssh-execution-boundary.md 的写作目的就是把这三件事写成确定的契约执行归属、断线存活、状态词汇。它的读者不仅是人类运维者也包括驱动终端的编码 Agent——两者都需要用同一套词汇对远端状态下结论。第一性规则执行主机拥有一切接触执行的东西Orca 的模型用一句话概括执行主机execution host拥有所有触碰执行的东西——工具、凭据、身份、环境、进程与产物而客户端只拥有 UI、传输通道和 Orca 控制平面control-plane状态对执行状态没有最终裁定权。由此得出两条不可谈判的推论禁止静默替换no silent substitution针对远端repoPath的操作绝不能悄悄回退到客户端本地执行。缺少 SSH provider 不构成“在本地回答”的许可——本地执行可能回答了错误的仓库。禁止断言无法观测到的东西no asserting what you cannot observe与远端失去联系不是exited的证据。必须上报unverifiable永远不能上报exited。固定词汇表live / unverifiable / exited本模型的词汇是固定的三个状态取自现有实现UnstoppedPtyVerdict状态含义live拥有该进程的主机确认进程仍然在运行unverifiable无法向拥有该进程的主机求证联系中断、查询失败、证据不足exited拥有该进程的主机给出了进程确实不存在的正向证据规则不得引入同义词不得把unverifiable折叠进相邻两个状态中的任何一个。exited必须来自拥有进程的那台主机的正向缺席证据一次传输失败最多只能推导出unverifiable。推论一的源码落点路由以 executionHostId 为准绝不静默降级“禁止静默替换”并非口号而是贯穿在多个层级的强制约束。文档明确列出了这些落点默认分支解析repo-default-branch.ts工作树管理repo-worktrees.tsOrcaRuntimeService.probeWorktreeDriftworktree 漂移探测orca-runtime.ts渲染端连接上下文connection-context.ts在运行时命令层约束由两个“require”函数强制执行git 侧由requireRuntimeGitProvider执行位于 runtime-git-command-target.ts函数定义在第 82 行附近文件系统侧由requireRuntimeFileProvider执行位于 runtime-file-command-target.ts两者共同贯穿整个 orca-runtime-git.ts 运行时 git 实现。从源码实现看这两个函数有一个共同的路由原则它们基于目标解析出的executionHostId路由而不是基于仓库行的connectionId路由。由此产生三种严格区分的返回行为当某个 SSH 主机没有注册 provider 时——抛出 provider-unavailableprovider 不可用消息当目标是runtime:主机、但当前进程并不执行该主机时——抛出ExecutionHostNotDispatchableError不可派发错误只有目标是local时才返回null。文档还提醒这些函数的调用点会随代码演化而变化要 grep 上述名称获取当前调用点而不是相信某个过时的数量统计。推论二的参考实现unstopped-pty-verification.tsunverifiable不是口头约定它有正式的类型与逻辑实现。unstopped-pty-verification.ts 被文档称为推论二rule 2的参考实现第 17 行export type UnstoppedPtyVerdict PtyLivenessVerdict把三态判定正式化三态保持严格区分源码注释直言 “we could not ask” 本身就是一个独立答案——“没能问”不是 PTY 存活的证据调用方会因此措辞不同的错误verifyUnstoppedPtys第 30 行起会给验证阶段分配独立预算下限为WORKTREE_TEARDOWN_VERIFY_GRACE_MS 2_000ms在 provider 的进程清单上重新列进程决定一次失败的 stop RPC 究竟意味着什么。清单查询失败或超时返回{ status: unverifiable }只有明确列出仍存活的 PTY 才返回live否则返回exitedresolveUnstoppedPtyVerdict第 79 行起体现关键细节当 provider 并不观测拥有该进程的主机providerObservesOwningHost false时直接拒绝下exited结论——因为“存活的 provider 的清单”对“它够不着的主机”是沉默的沉默不是退出的证据此时会返回unverifiable或使用运行时给出的 unverifiable 判定。这段实现的注释还揭示了历史教训issue #11960验证曾跑在 sweep 自己的截止时间上而 sweep 通常刚刚耗尽预算导致一个已正常退出的 PTY 被误判为 unverifiable工作区永远无法被删除。因此验证获得了独立预算。若某次停止因失去与 PTY 宿主机的联系而中断Force Delete 仍是逃逸出口。什么在远端执行、什么在客户端执行文档给出了一张权威的职责分布表是所有后续分析的基础关注点执行位置说明PTY、Agent CLI远端它们是分离的 relay 守护进程的子进程不是 ssh 通道的子进程gitstatus、diff、log、fetch、push、commit、branch、worktree远端经由 git-handler.ts 完成文件系统、文件监听、搜索远端—仓库 setup 钩子--setup远端与本地采用完全一致的策略commit-message / PR 字段的 AI 生成远端使用远端 Agent CLI 及其自身认证gh/ GitHub API、glab/ GitLab客户端与“执行主机拥有执行”规则不一致PR 携带的是客户端的身份远端终端里的orcaCLI客户端 runtime仅控制平面——你的文件与进程仍留在远端见下文“控制平面”一节注意表中 gh/glab 一行的诚实标注这是与规则不一致的既有事实。当你在远端仓库里跑gh或glab时命令实际上在客户端执行因此 PR 携带的是客户端本机的身份而不是远端主机上配置的身份——这对“远端执行远端身份”的直觉是一个反例在文档中被明确记录。断线存活性断线“不会”做什么默认情况下远端工作会在你本机消失后继续存活。文档给出了一组实现事实relay 是一个分离的守护进程启动方式形如nohup … /dev/null relay.ts 中的 handler 会忽略SIGHUPPTY 是 relay 的子进程而不是 ssh 通道的子进程——通道断了PTY 不随之消亡退出 Orca 是detach分离不是 dispose销毁见 ssh-relay-session.ts 的 901-915 行附近睡眠Sleep模式额外把graceTimeSeconds推成0解除任何正在运行的宽限期窗口。远端工作真正会停的两种方式文档强调远端工作可能停止的路径只有两条且都具有明确语义其一有界的宽限期bounded grace period。出厂默认值是0 一直保持存活直到重置“keep terminals alive until reset” 默认勾选若取消勾选可配置范围是60s–7d表单默认值是24h倒计时从客户端断开时开始到期后 relay 会对每个 PTY 发SIGKILL注意其中的不对称性睡眠sleep能保护你但普通断开与应用退出不能且没有任何命令会报告某个目标当前生效的是哪个设置——所以在断开 N 小时后你无法区分“无限制存活”和“24h 且只剩 7h”此时应把远端当作unverifiable而不是exited。其二主机已确认的显式用户操作。包括 End Remote Terminals结束远端终端、Reset Relay重置 relay、移除目标removing the target、或关闭标签页。当主机无法确认这些请求时例如已断线关闭标签页或移除目标可能只清除了客户端状态远端判定仍然是unverifiable。重连重新挂到同一个活 PTY 上重连会重新挂接re-attach到同一个仍然存活的 PTY并回放一个有界缓冲区REPLAY_BUFFER_MAX即102,400 个 code-unit 的尾部内容。你离线期间超出该缓冲的输出对客户端来说已经丢失——尽管进程从未被打断。文档给出了一句精炼总结转录transcript被截断了但工作仍是live。丢失回放 ≠ 进程退出二者绝不可混为一谈。第三种结局升级 Orca 会“孤立”relay 终端宽限期与显式操作之外还有第三种结局既不属于上述任何一种又最容易在词汇上出错工作没有停止只是变得永久不可达。整个链条在源码中环环相扣relay 的安装目录——以及由此派生的 socket 路径——由 relay 包relay bundle的内容哈希命名计算函数为computeRemoteRelayDirssh-relay-versioned-install.ts消费方是resolveRemoteInstallStatessh-relay-deploy.tsrelay 守护进程拒绝任何 bundle 哈希不一致的客户端handleDaemonHandshakeFramerelay-handshake.ts会以退出码EXIT_CODE_VERSION_MISMATCH42拒绝握手。即便两个构建的 relay 协议字节级相同也会互相拒绝因此应用更新后的第一次重连会部署一个新 relay——它监听在一个旧 relay 从未监听过的新路径上新旧双方在原理上就无法通信旧 relay 拥有的每一个 PTY 因此都是unverifiable在运行、不可达、且绝不可能是exited客户端的租约lease尝试落在一个从未铸造过这些 id 的 relay 上因“找不到”而到期handlePtyReattachFailure见 ssh-relay-session.ts该窗格回退到 cold-restore冷恢复的 agent 恢复流程若当时没有捕获可恢复的 provider 会话则退化为一个裸 shell与此同时旧 relay 的目录因为 socket 确实存活而被固定住、免于被垃圾回收hasLiveRelaySocket见 remote-install-gc.ts。这是一个真实的已知问题文档记录其编号为 #13852。为什么 peer对等模型没有这个失败文档指出这正是“一台主机只能注册一种模型”的具体理由守护进程daemon的 endpoint 用语义化协议版本命名而不是用构建内容哈希命名daemon-vN.sock来源是getDaemonSocketPathdaemon-spawner.ts每一个更早的协议版本都保持可挂接PROTOCOL_VERSIONdaemon-protocol-version.ts持有活跃会话的守护进程在版本变更时被保留而不是被替换shouldPreserveDaemonWithLiveSessionsdaemon-replacement-preflight.ts。于是升级桌面应用不会让 daemon 后端及其 PTY 变成“孤儿”——这正是与 relay 内容哈希方案的直接对照。控制平面SSH 主机上的 orca 只是一个 shim在 SSH 主机上orca实际是一个 shim[~/.orca-relay/bin/orca]它通过 relay socket代理回客户端的 runtime。你的仓库、进程、文件都留在远端只有控制平面在客户端。对 SSH 目标来说这是正确设计但文档明确点出了一个必须说透的后果当客户端断开时在 SSH 主机上运行的每一条orca …命令都会失败并报No owning Orca client is connected to the relay。PTY 仍然是live的它的控制平面不是。编排状态Runs、Tasks、Dispatches、mailboxes出于同样的原因驻留在客户端。因此SSH 主机上的 Agent不应依赖orca来完成任何需要在客户端离线期间收尾的工作。文档给出的实战铁律是尽早 commit 并 push——远端机器上未推送的工作在客户端重连之前对客户端是不可用的。如何区分unverifiable与exited证据检验清单一个判定结论必须来自拥有该进程的主机。文档给出了一套按顺序执行的检验法逐条对照检验 1信号来自拥有主机还是来自客户端自己的记账bookkeeping客户端侧集合中的缺席、一次抛异常的查找、一个关闭的 socket、一条超时的命令——这些都没有观测到进程本身。无论字段叫什么名字它们按构造就是unverifiable。检验 2该目标上的所有远端 PTY 是否同时静默传输层中断会把它们一起带走。某台主机上的同时静默说明链路丢了而不是同时死亡。检验 3终止事件是否匹配当前身份主机投递的、针对“当前存活的 PTY 化身 当前 provider 代际”的退出事件且其兄弟 PTY 仍在报告才可建立exited。一条陈旧事件、一条针对已取代化身的旧事件、或一条没有任何主机证据的“单终端静默”都不足以判定退出。检验 4答案是否携带了证据还是只有相同措辞这一条是细节最多的地方。pty.attach对两种截然不同的情况都会拒绝并报PTY id not found一是 relay 探测过 pid 且确认进程已消失二是这个 id 根本从未出现在它的会话表中——由于 id 携带每次启动的铸造纪元per-start mint epochrelay 重启之前铸造的所有 id 都属于后者。区别在于只有经过探测的拒绝才携带PTY_ATTACH_PROVEN_EXITED_MARKER“已证实退出的缺席证据”并以SshPtyProvenExitedOnRelayError形式到达客户端——这是“可证实的已退出”未带标记的并集则以SshPtyAbsentFromRelayError到达——它只授权客户端清除自己到该 PTY 的路由仅此而已缺失标记永远不是证据——较旧的 relay 也会省略它。检验 5返回的状态真的是“成功”的声明吗报告失败的操作可能已经成功报告成功的操作可能根本没有运行。文档建议去检查它本应改变的持久状态durable state而不是相信返回值本身。任何达不到“主机正向证据”级别的结果都是unverifiable。文档给出了整个体系的警告语义把它误报成exited正是本文件要阻止的错误——它会孤立活工作并可能在同一棵工作树上冷启动一个重复的 Agent 实例。判定远端窗格空闲sweep 的证据单位必须与破坏动作对齐孤立-PTY 清扫orphan-PTY sweep是唯一把“一次观测”变成“一次 SIGKILL”的流程因此它的空闲证据必须与信号实际到达的对象对齐——而这个对象不是终端。forceKillPosixPtyProcessGroupsposix-pty-process-groups.ts会收集该窗格 tty 上的每一个进程组并逐个killpg因此杀伤半径是(tty 上的进程组) × (这些进程组的成员无论它们身处何处)——第二个因子完全不受终端边界约束。有两个事实让这个缺口真实可触达作业控制可以关闭在set m下后台作业不会获得自己的进程组——它保留 shell 的进程组。此时ps会显示 tty 上只有一个进程组、正在跑一个构建。任何“tty 形状”的谓词都看不见它。组成员可以离开终端只调用ioctl(TIOCNOTTY)而不调用setsid会丢弃控制终端但保留 pgid——于是进程报告tpgid -1永远不会出现在ps -t tty中却仍会被killpg(shellPgid)杀死。同理双重 fork 的孙进程保留 pgid 但被重新挂到 pid 1 名下所以从 PTY 根按ppid遍历也无法发现它。因此shellOwnsEveryTtyProcessGroupagent-foreground-process-batch.ts要求两个测量同时成立tty 上的每个进程组都是 shell 自己的且没有 stopped 成员并且shell 自己的进程组在主机进程表中任何地方都没有其他成员。这个函数名之所以“tty 形状”文档注明纯粹是为了线上兼容性wire compatibility考虑。即便这样仍有两个残余不可在本层消除捕获是快照ps与信号之间启动的工作是看不见的——由读取侧的RELAY_PTY_SWEEP_MAX_EVIDENCE_AGE_MS限制窗口而不是消除竞态主机自己的ps都无法枚举的进程另一个 PID 命名空间、hidepid2、被权限边界截断的进程表虽然对观测不可见但killpg依然能到达它。文档由此提炼出贯穿全文的一般原则证据必须在破坏性动作所作用的单位上进行测量。单位不匹配的证据无论看起来多精确都是unverifiable。用工件artifact取代进程状态作为证据工件比活性信号更强但它们回答的问题比表面看起来更窄。来自git ls-remote --heads origin branch的一个匹配 commit或一次 PR head 查询只能证明那个 commit 到达了远端——不能证明是当前这次运行推送的也不能证明最新工作被包含在内一个“缺席”的结果只证明没有找到不证明“没推送过”ref 可能被删除、PR 可能被关闭、查询本身可能失败一次列表结果只能作为“它实际覆盖到的主机”的证据。当结果没有标明作用范围时空答案不是“别处没有运行”的证据——一棵干净的本地worktree 对远端那棵工作树什么都说明不了。这条与上文的“单位一致性”原则相呼应选错证据的粒度就会对未覆盖的主机轻率地下结论。一台主机一种模型SSH 直连与配对 runtime 不可混用文档最后给出模型选择上的硬性建议一台物理主机只能选择一种注册方式。SSH 直连主机由客户端驱动的“哑执行主机”dumb execution host控制平面在客户端配对 runtimeorca environment拥有自己控制平面的对等方peer。把同一台机器同时用两种方式注册会带来三重恶果其 worktrees 被拆散到两个身份下、terminal list的输出会随是否传--environment而不同、并且会稳定地同时迷惑人类与 Agent。离线仍需继续的工作请用 peer/headless-runtime 模型对“必须在你离线时继续”的工作文档的建议是在远端主机上使用peer/headless-runtime 模型而不是 direct-SSH 模型。理由全部有源码与文档支撑其控制平面是主机本地的host-local其 daemon 支撑的 PTY 能在PID 作用域内的 runtime 重启后保持live使 runtime 可以重新挂接——因为 endpoint 按语义协议版本命名daemon-vN.sock、旧协议版本保持可挂接、持有活会话的 daemon 会被保留详见上文“peer 模型没有这个失败”一节只有当服务管理器收割 runtime 的 cgroup、或显式关闭 daemon时它们才会变成exited——这一语义详见 orcad-operations.md 中关于“进程作用域与 cgroup 级停止”的说明注意同一 cgroup 下的KillModemixed或KillModecontrol-group在停止超时后仍会 SIGKILL 整个 cgroup进程分离不等于服务隔离。不要通过两种模型注册同一台机器。另有一个补充Orca 之外的一个脱离detachedAgent 进程同样能在控制平面中断时存活但它没有 stdin——因此它的指令无法在运行中途被修正。这一点在评估“离线下继续”的方案时同样值得计入。小结把三件事钉成契约回到本文开头的问题Orca 的 SSH 执行边界可以浓缩为三条契约执行归属执行主机拥有工具、凭据、身份、环境、进程与产物客户端对执行状态没有最终裁定权。远端操作禁止静默落到本地。断线存活默认远端工作存活普通断开与应用退出是 detach 不是 dispose只有“有界宽限期到期”和“主机已确认的显式操作”才能终止它升级应用造成的是“不可达”而非“退出”。状态词汇只有live/unverifiable/exited三态。判定必须携带“拥有进程的主机”给出的证据联系不上永远是unverifiable——把unverifiable说成exited会孤立活工作、并可能在相同 worktree 上冷启动出重复 Agent。想深入阅读时建议从 unstopped-pty-verification.ts三态判定的参考实现、runtime-git-command-target.ts 与 runtime-file-command-target.ts按executionHostId强制路由以及 posix-pty-process-groups.tssweep 杀伤半径四个文件入手再对照本篇文章逐条验证。【免费下载链接】orcaOrca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and remote runtime.项目地址: https://gitcode.com/GitHub_Trending/orca48/orca创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考