<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: EdgeStorage</title>
    <description>The latest articles on DEV Community by EdgeStorage (@edgestorage).</description>
    <link>https://dev.to/edgestorage</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4124950%2Ff3cc2b5b-1b2c-49dd-a192-cf96ba2c923f.jpg</url>
      <title>DEV Community: EdgeStorage</title>
      <link>https://dev.to/edgestorage</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edgestorage"/>
    <language>en</language>
    <item>
      <title>在多台远程服务器上并行跑 Codex：worktree 应该怎么分</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:37:21 +0000</pubDate>
      <link>https://dev.to/edgestorage/zai-duo-tai-yuan-cheng-fu-wu-qi-shang-bing-xing-pao-codexworktree-ying-gai-zen-yao-fen-74g</link>
      <guid>https://dev.to/edgestorage/zai-duo-tai-yuan-cheng-fu-wu-qi-shang-bing-xing-pao-codexworktree-ying-gai-zen-yao-fen-74g</guid>
      <description>&lt;p&gt;并行跑 AI 任务，常见的阻碍往往不是模型能力，而是目录。&lt;/p&gt;

&lt;p&gt;两个会话在同一个工作目录里改代码，A 刚写完的文件可能被 B 覆盖；你打开文件准备保存，界面提示版本已经过期；测试失败时，也很难判断是哪一个任务改坏的。机器一多，问题还会多一层：任务到底跑在哪台服务器、哪个目录、哪个分支？&lt;/p&gt;

&lt;p&gt;Git worktree 能解决其中一部分问题，但它不是「把同一个目录挂到多台机器」的魔法。这篇讲清楚 TaskHandoff 里的分工方式：一台节点、一份仓库 clone、一个任务、一个 worktree，以及多节点时哪些事情不能做。&lt;/p&gt;

&lt;h2&gt;
  
  
  先说结论：多节点并行不是共享一个 worktree
&lt;/h2&gt;

&lt;p&gt;Git worktree 是&lt;strong&gt;同一个仓库的独立工作目录和分支&lt;/strong&gt;。它让多个会话各自拥有一个目录，减少并行编辑冲突；但 worktree 仍然属于某一台机器上的某一个仓库 clone。&lt;/p&gt;

&lt;p&gt;所以多台远程服务器并行跑任务时，正确模型是：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;同一远端仓库
   ├── 节点 A 的 clone → worktree A-1（分支 feature/a）
   └── 节点 B 的 clone → worktree B-1（分支 feature/b）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不是：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;节点 A 的 worktree ← 同时挂给节点 B 的会话
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;前者是多个独立 checkout，后者会制造路径、锁和文件一致性问题。TaskHandoff 的多节点模型按节点管理实例和会话，不把 worktree 当成跨节点共享目录；它做的是把多个节点、多个实例和多个会话放进同一个控制面里查看和操作。&lt;/p&gt;

&lt;h2&gt;
  
  
  为什么不用同一个目录硬跑
&lt;/h2&gt;

&lt;p&gt;在同一台机器上，两个 Codex 或 OpenCode 会话共用一个工作目录，至少会遇到三类问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;文件互相覆盖&lt;/strong&gt;：两个任务同时改同一个文件，后写入的版本不一定是你想要的。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;状态难以归因&lt;/strong&gt;：测试失败、依赖变化、未跟踪文件，分不清来自哪个任务。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;保存冲突&lt;/strong&gt;：文件被 AI、终端或另一个编辑器改过后，保存会因版本过期停止，需要先比较再决定。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;worktree 把「任务」和「目录」绑定起来：一个任务一个目录，一个目录一个分支。这样每个任务的变更、测试和提交都能独立检查。&lt;/p&gt;

&lt;h2&gt;
  
  
  做法：一台节点，一个任务，一个 worktree
&lt;/h2&gt;

&lt;p&gt;下面是一条可复用的路径。它不是通用答案，但边界比较清楚。&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 把远程服务器接成节点
&lt;/h3&gt;

&lt;p&gt;节点是运行 Node Agent、并实际管理实例的计算机。打开「设置 → 节点 → 添加节点」，根据网络条件选择连接方式。远程节点通常用反向 WSS：Node Agent 主动连 Control Plane，不需要给节点开放公网入站端口。&lt;/p&gt;

&lt;p&gt;一次性加入 Token 只用于首次建立信任，配对完成后不要把它写进脚本仓库、工单或日志。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 在每台节点上创建 Git 实例
&lt;/h3&gt;

&lt;p&gt;每台要参与并行的服务器，各自创建自己的 Docker 实例，并把同一个远端仓库 clone 进来。多节点不是共享工作区，而是每台机器保持自己的 clone。&lt;/p&gt;

&lt;p&gt;如果你需要控制 Git 权限，凭据按 remote scope 匹配；HTTPS token 只在 credential-helper 请求期间短暂提供，不写进 remote URL、命令行或环境变量。SSH 私钥留在 Node Agent，实例只拿签名能力。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 从已有 Git 会话创建托管 worktree
&lt;/h3&gt;

&lt;p&gt;打开已有 Git 会话的「环境 → 工作树」，选择「新建托管工作树 AI 会话」，填写新分支名称和起始引用，再选择 Codex 或 OpenCode。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb46t8yx05zmx3v2wlrk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb46t8yx05zmx3v2wlrk.png" alt="新建 AI 会话时选择 Agent、模型和工作目录" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;创建完成后，TaskHandoff 会生成托管 worktree 和新会话；原会话仍停留在原来的工作目录。会话创建后工作目录不会自动改变，所以换 worktree 的正确做法是「在目标 worktree 上新开会话」，不是把当前会话挪过去。&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 在另一台节点重复，而不是复制目录
&lt;/h3&gt;

&lt;p&gt;第二台服务器也按同样方式创建自己的实例、clone 和 worktree。两个 worktree 可以在同一个远端仓库上使用不同分支，但它们在文件系统上彼此独立。&lt;/p&gt;

&lt;p&gt;建议把分支名和任务目标绑定，例如 &lt;code&gt;feat/api-timeout&lt;/code&gt;、&lt;code&gt;fix/windows-paths&lt;/code&gt;。不要让两个会话在同一个 worktree 同时修改相同文件；如果一个任务需要改同一批文件，先拆任务，或者让它们串行。&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 分别在每个 worktree 里审阅、测试、提交
&lt;/h3&gt;

&lt;p&gt;每个 worktree 都有自己的变更集。分别检查测试和 diff，确认后再提交和推送。TaskHandoff 的远端操作是保守的：Pull 仅允许 fast-forward，不自动 merge、rebase 或 stash；分支分叉时阻止自动同步；永远不 force push。&lt;/p&gt;

&lt;p&gt;复杂冲突、已分叉分支、大型或二进制 diff，建议到对应会话的终端里处理，然后刷新仓库页面。&lt;/p&gt;

&lt;h2&gt;
  
  
  多节点时，节点选择看什么
&lt;/h2&gt;

&lt;p&gt;节点不是越多越好。比较实际的分配依据是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;运行时和依赖&lt;/strong&gt;：哪台机器有项目需要的 Docker 镜像、系统工具或 GPU。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;隔离边界&lt;/strong&gt;：不同客户、不同仓库、不同权限的凭据是否应该落在不同节点。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;网络位置&lt;/strong&gt;：节点在公司内网、家用服务器还是云主机，决定了它能不能访问目标仓库和依赖。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;任务时长&lt;/strong&gt;：长任务放在常开的服务器上，临时任务放在随手可用的节点上。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不要为了「看起来并行」把同一个 worktree 复制到多台机器。复制目录会带来身份、缓存和未跟踪文件的不一致；正确做法是每台节点从远端仓库创建自己的 checkout。&lt;/p&gt;

&lt;h2&gt;
  
  
  交付和清理：分支保留，目录才删除
&lt;/h2&gt;

&lt;p&gt;托管 worktree 由 TaskHandoff 创建，可以安全地用于新 AI 会话。外部 worktree 只有位于实例授权目录且仍可访问时才能使用，系统不会接管或删除它。&lt;/p&gt;

&lt;p&gt;删除托管 worktree 前，它必须是非主工作树、无修改、未锁定，并且没有活跃的 AI 或应用会话。删除的是工作树目录，关联的 Git 分支会保留；系统使用非 force 删除，不会顺手删掉你的分支。&lt;/p&gt;

&lt;p&gt;主工作树、外部 worktree、有未提交修改或仍被活跃会话占用的 worktree，不能从界面删除。这条限制看起来麻烦，但它防止的是「清理目录时把正在跑的任务或未提交改动一起弄丢」。&lt;/p&gt;

&lt;h2&gt;
  
  
  踩坑：四个容易犯的错
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;把 worktree 当成跨机器共享目录。&lt;/strong&gt; 它不是。多节点并行是多个 clone、多个 worktree，不是一个目录挂给多台机器。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;两个会话共用一个 worktree。&lt;/strong&gt; 文档明确建议并行任务分配不同 worktree；同一 worktree 里的两个会话会看到彼此写入磁盘的修改。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在主工作树上直接开长任务。&lt;/strong&gt; 主工作树通常承载你正在做的改动。更稳妥的方式是从它创建托管 worktree，把实验性任务放到独立目录和分支。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;以为移除 worktree 会删分支。&lt;/strong&gt; 移除的是目录，Git 分支保留；但目录里有未提交修改或仍有活跃会话时，系统会阻止删除。&lt;/p&gt;

&lt;h2&gt;
  
  
  结果：行为上发生了什么变化
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;两台服务器上的任务出现在同一个控制面里，可以按节点、项目、状态查看。&lt;/li&gt;
&lt;li&gt;每个任务有自己的目录和分支，文件冲突不再靠人肉记忆避免。&lt;/li&gt;
&lt;li&gt;每个 worktree 单独测试、审阅、提交，失败时更容易归因。&lt;/li&gt;
&lt;li&gt;清理 worktree 时分支保留，复杂合并回到终端显式处理。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  可复用的五条原则
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;一个任务一个 worktree。&lt;/strong&gt; 目录是并行任务的隔离边界，不是共享缓存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;worktree 属于节点，不属于控制面。&lt;/strong&gt; 多节点并行靠多个 clone，不靠共享目录。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;分支名写清楚任务目标。&lt;/strong&gt; 让 worktree、分支和任务能一一对应。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;每个 worktree 单独验收。&lt;/strong&gt; 测试、diff、提交和推送都按 worktree 走。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;清理是流程的一部分。&lt;/strong&gt; 先关会话、保持干净、再移除目录；分支是否保留由 Git 决定。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  相关文档
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TaskHandoff 官网（产品定位与客户端下载）：&lt;a href="https://www.thandoff.com/" rel="noopener noreferrer"&gt;https://www.thandoff.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;开源仓库：&lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;https://github.com/edgestorage/task-handoff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;用 worktree 并行运行 AI 的完整教程：&lt;a href="https://docs.thandoff.com/tutorials/worktree-session" rel="noopener noreferrer"&gt;https://docs.thandoff.com/tutorials/worktree-session&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;仓库协作、变更、分支与远端同步：&lt;a href="https://docs.thandoff.com/workspace/repository" rel="noopener noreferrer"&gt;https://docs.thandoff.com/workspace/repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI 会话与固定工作目录：&lt;a href="https://docs.thandoff.com/sessions/" rel="noopener noreferrer"&gt;https://docs.thandoff.com/sessions/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;节点管理：&lt;a href="https://docs.thandoff.com/admin/nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/admin/nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;连接远程节点：&lt;a href="https://docs.thandoff.com/integrations/remote-nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/integrations/remote-nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;安装与部署：&lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;https://docs.thandoff.com/guide/install&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>git</category>
      <category>devops</category>
    </item>
    <item>
      <title>管理多台远程服务器，不一定要开公网端口：多节点接入与免费中转</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:07:20 +0000</pubDate>
      <link>https://dev.to/edgestorage/guan-li-duo-tai-yuan-cheng-fu-wu-qi-bu-ding-yao-kai-gong-wang-duan-kou-duo-jie-dian-jie-ru-yu-mian-fei-zhong-zhuan-5bo8</link>
      <guid>https://dev.to/edgestorage/guan-li-duo-tai-yuan-cheng-fu-wu-qi-bu-ding-yao-kai-gong-wang-duan-kou-duo-jie-dian-jie-ru-yu-mian-fei-zhong-zhuan-5bo8</guid>
      <description>&lt;p&gt;远程服务器管理和远程开发常被当成一个「能不能连上」的问题。真做起来，其实是两条链路：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;你&lt;/strong&gt;（浏览器 / 桌面端 / 手机）怎么访问 Control Plane；&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Plane&lt;/strong&gt; 怎么管理散在不同网络里的节点。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这两条链路的入口、认证和信任边界是分开的。把它们混成一条，最常见的做法就是给每台机器开公网端口、配端口映射，然后把安全寄托在「这个 URL 别人猜不到」上。&lt;/p&gt;

&lt;p&gt;TaskHandoff 是免费、开源、可自托管的 AI 远程工作平台。它的做法是把这两条链路拆开：节点用反向连接主动接入；控制面可以不暴露到公网，通过 Thandoff 账户的免费后台中转供已授权客户端访问。&lt;/p&gt;

&lt;p&gt;这篇文章讨论的不是「怎么在服务器上执行一条命令」，而是怎么把服务器纳入一个长期可用的远程工作台：节点、Docker 工作区、AI 会话和终端都在同一个控制面里。&lt;/p&gt;

&lt;h2&gt;
  
  
  问题：一台机器一个终端窗口，机器一多就崩
&lt;/h2&gt;

&lt;p&gt;手上有三台机器的人，日常大概是这样的：笔记本、一台常开的家用服务器、一台公司内网主机。你在它们上面分别跑 Codex 或 OpenCode，然后开始当人肉调度器：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;记得哪台机器上有哪个仓库、哪个分支、哪个 worktree；&lt;/li&gt;
&lt;li&gt;反复切 SSH 窗口看进度，因为任务只活在那个终端里；&lt;/li&gt;
&lt;li&gt;关掉笔记本之后，不知道服务器上的任务到底还在不在跑。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;常见的三种连通方案各有代价：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;公网 IP + 端口映射 / 内网穿透&lt;/strong&gt;：每台机器一套 NAT、TLS、认证；暴露面变大，节点换网络后还要重新确认。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPN&lt;/strong&gt;：连通性解决了，但「哪台机器上有哪个任务」的可见性没有解决，也没有任务级的权限边界。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;一台机器一个远程桌面&lt;/strong&gt;：能用，但把整个桌面暴露出去，和「只想看一个任务的进度」不是一回事。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  做法一：把节点接进来，而不是把端口开出去
&lt;/h2&gt;

&lt;p&gt;在 TaskHandoff 里，&lt;strong&gt;节点&lt;/strong&gt;是运行 Node Agent、并实际管理实例的计算机。所有节点出现在同一个控制面板里，可以按节点、项目、状态筛选。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1h0kqae80pw0miudtl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1h0kqae80pw0miudtl9.png" alt="设置 → 节点：本地节点与远程节点在同一个列表里" width="800" height="581"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;图 1：设置 → 节点。左侧是本机和远程节点，右侧是节点的运行环境、实例与诊断信息。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;添加节点的向导会先检查 Control Plane 的公网可达性，再根据目标机器的网络条件推荐连接方式。核心原则只有一句：&lt;strong&gt;让能够主动出站的一方发起连接，尽量减少公网入站端口。&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;谁发起连接&lt;/th&gt;
&lt;th&gt;适合的场景&lt;/th&gt;
&lt;th&gt;公网要求&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;反向 WSS&lt;/td&gt;
&lt;td&gt;Node Agent&lt;/td&gt;
&lt;td&gt;远程节点能访问 Control Plane&lt;/td&gt;
&lt;td&gt;只公开 Control Plane 的 HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS 直连&lt;/td&gt;
&lt;td&gt;Control Plane&lt;/td&gt;
&lt;td&gt;Control Plane 能访问 Node Agent&lt;/td&gt;
&lt;td&gt;公开 Node Agent 的 HTTPS API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可信控制面代理&lt;/td&gt;
&lt;td&gt;当前 Control Plane&lt;/td&gt;
&lt;td&gt;双方都不能接受入站连接&lt;/td&gt;
&lt;td&gt;两端都能访问代理面板&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;反向 WSS 通常是首选。&lt;/strong&gt; 它不要求给 Node Agent 配公网监听、NAT 和 TLS；Node Agent 会持久化连接，服务重启后自动重连。目标机也不需要预装 Node.js 或 npm——受支持的安装器会准备运行环境。&lt;/p&gt;

&lt;p&gt;配对用的加入 Token 是&lt;strong&gt;一次性&lt;/strong&gt;的，有有效期，只用于首次建立信任。配对完成后的反向连接使用单独凭据，不依赖 Token 保持在线。这一点值得单独强调：不要把长期凭据塞进安装命令，也不要把 Token 写进脚本仓库、工单或日志。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTPS 直连&lt;/strong&gt;只在控制面无法被目标节点访问、但能访问 Node Agent 时使用。它要求公网 Node Agent 必须使用 HTTPS。把 listener 从 &lt;code&gt;127.0.0.1&lt;/code&gt; 改成 &lt;code&gt;0.0.0.0&lt;/code&gt; 只会扩大监听范围，不会自动配置 TLS、防火墙或认证——这是最容易出事的一步。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;可信控制面代理&lt;/strong&gt;用于双方都不能接受入站连接的场景。需要注意它的语义：代理面板可以读取、修改并代表你执行全部被代理的节点操作，而且这条路径&lt;strong&gt;不是端到端加密&lt;/strong&gt;的，只能在同一可信管理边界内使用。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3pq935ev2c1s2xkox2p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3pq935ev2c1s2xkox2p.png" alt="添加远程节点：直接端点 / 控制面代理" width="800" height="581"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;图 2：「添加远程节点」里的两种方式。选择控制面代理时，需要显式勾选「我信任此控制面板代理 node-agent 操作」。&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  做法二：控制面不暴露到公网，也能从手机或浏览器访问
&lt;/h2&gt;

&lt;p&gt;第二条链路是「人 → Control Plane」。传统做法是域名 + HTTPS 反向代理，把控制面挂到公网。这在小团队里完全可行，但要求你维护 DNS、证书、端口和认证。&lt;/p&gt;

&lt;p&gt;如果你不想把控制面暴露到公网，可以用 &lt;strong&gt;Thandoff 账户中转（免费）&lt;/strong&gt;：把一台自管 Control Plane 发布到账户目录，移动端等已授权客户端通过后台中转访问它。官网当前把这项能力描述为端到端加密中继，节点覆盖中国与全球。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Control Plane &lt;strong&gt;主动建立出站连接&lt;/strong&gt;，所以不用为这条路径开放 Control Plane 或 Node Agent 的公网入站端口。&lt;/li&gt;
&lt;li&gt;中转的是「客户端 → Control Plane」的 API、响应流和 WebSocket，&lt;strong&gt;不是节点之间的账号型中继&lt;/strong&gt;。这个边界很重要，下面会单独说。&lt;/li&gt;
&lt;li&gt;中转由 Control Plane 的后台进程持有：关闭设置页、退出本地 Web UI、授权用的浏览器会话过期，都不会停掉中转。&lt;/li&gt;
&lt;li&gt;停止 Control Plane 服务、关闭远程访问、撤销绑定或发生身份冲突，会终止中转；服务恢复后，只要绑定仍有效且远程访问已开启，后台进程会用专用凭据重新连接。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;两个操作的名字很像，语义完全不同：&lt;strong&gt;「关闭远程访问」&lt;/strong&gt;保留账户绑定，随时可以再开；&lt;strong&gt;「断开 Thandoff 账户」&lt;/strong&gt;会撤销绑定，下次要重新走一遍授权。授权码大约 5 分钟内有效，消费后立即失效；客户端访问时还会校验 Control Plane ID、账户、绑定 ID 和 revision，旧的或已撤销的授权不能继续建立新连接。&lt;/p&gt;

&lt;h2&gt;
  
  
  踩坑与边界：不能吹的部分
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;第一，账号型 Node Agent 中继目前还不支持。&lt;/strong&gt; 文档写得很明确：Thandoff 账户中转的是客户端到 Control Plane 的访问，不是节点中继。如果两台机器都不能接受入站连接，正确路径是可信控制面代理，而不是指望账户中转替你连节点。把这条边界写清楚，比含糊带过更有用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二，复制数据目录会造成身份克隆。&lt;/strong&gt; Control Plane 的 ID 和签名身份保存在数据目录里；把整个目录复制一份、同时运行原安装和副本，两台机器会以同一个身份上线。账户服务检测到之后会把状态置为「身份冲突」，删除后台中转凭据并隔离所有远程数据连接。不要靠反复点「开启远程访问」绕过，先停掉其中一个安装，确定哪台机器保留原身份。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三，反向代理不能只转发 &lt;code&gt;/api/events&lt;/code&gt;。&lt;/strong&gt; 实例终端、浏览器等功能还有别的 WebSocket 路由。稳妥的做法是让整个 Control Plane Origin 正确转发升级头，同时把请求体大小和读取超时放宽；否则会出现「页面能打开，但状态不更新」「终端一开就断」。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第四，移动端 Origin 必须是规范形式。&lt;/strong&gt; &lt;code&gt;https://主机[:端口]&lt;/code&gt;，不能带路径、query、内嵌用户名密码，也不能依赖重定向。配置完成后不要随意更换 Origin——已有移动端配置会把 Control Plane ID 和签名密钥指纹与这条连接一起校验。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第五，关掉控制面不等于关掉任务。&lt;/strong&gt; 关闭或重启 Control Plane 不会停止远程 Node Agent 管理的实例；真正会中断任务的是停止或删除实例、关闭会话、停止 Node Agent。Local Runtime 的实例会随 Node Agent 退出而停止，Docker Runtime 的实例会继续运行，并在下次启动后被重新发现和接管。&lt;/p&gt;

&lt;h2&gt;
  
  
  结果：行为上发生了什么变化
&lt;/h2&gt;

&lt;p&gt;不列数字，只列可验证的差异：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;加一台机器&lt;/strong&gt;：在目标机上跑一条带一次性 Token 的安装命令，不用为它开放公网入站端口。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;节点掉线&lt;/strong&gt;：状态显示重连中，网络恢复后自动连回来，不需要重新配对——节点记录和配对关系都不应该靠删除来「测试连通性」。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;在外面&lt;/strong&gt;：手机或桌面客户端登录同一个 Thandoff 账户，从 Control Plane 目录里添加面板，不需要记公网 IP，也不需要把控制面挂到公网。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;长任务&lt;/strong&gt;：关浏览器、退出本地 Web UI，任务在节点上继续；回到电脑上能看到权威状态。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;多机视图&lt;/strong&gt;：所有远程服务器和实例在同一个看板里，按节点、项目、状态筛选，不用逐个 SSH 上去看。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco961zvgw0tok9miq1ai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco961zvgw0tok9miq1ai.png" alt="Story 列表按所属节点筛选" width="800" height="581"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;图 3：多节点之后，「按所属节点筛选」会变成最常用的视图之一。&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  可复用的五条原则
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;两条链路分开规划。&lt;/strong&gt; 人怎么访问控制面、控制面怎么访问节点，入口和信任边界不是一回事。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;让能出站的一方发起连接。&lt;/strong&gt; 反向连接优先于端口映射；少开一个入站端口，就少一类暴露面。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;一次性 Token 只用于建立信任。&lt;/strong&gt; 长期连接用独立凭据，Token 不进仓库、工单和日志。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;把「关闭」和「断开」做成两个语义不同的操作。&lt;/strong&gt; 一个可恢复，一个要重新授权，界面上必须说清楚。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;能力边界写进文档。&lt;/strong&gt; 不支持的中继方式要明说，并给出替代路径——这比让用户自己试错便宜得多。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  相关文档
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TaskHandoff 官网（产品定位与客户端下载）：&lt;a href="https://www.thandoff.com/" rel="noopener noreferrer"&gt;https://www.thandoff.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;开源仓库：&lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;https://github.com/edgestorage/task-handoff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;远程访问总览（两条链路的入口、认证与信任边界）：&lt;a href="https://docs.thandoff.com/zh/integrations/remote-access" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/integrations/remote-access&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;连接远程节点（反向 WSS / HTTPS 直连 / 可信控制面代理）：&lt;a href="https://docs.thandoff.com/zh/integrations/remote-nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/integrations/remote-nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;通过 Thandoff 账户访问（绑定、免费后台中转、撤销与身份冲突）：&lt;a href="https://docs.thandoff.com/zh/integrations/thandoff-account" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/integrations/thandoff-account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;让 Control Plane 可远程访问（域名、TLS、反向代理与 WebSocket 要求）：&lt;a href="https://docs.thandoff.com/zh/integrations/public-control-plane" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/integrations/public-control-plane&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;节点管理（状态、连接方式、本地目录与模型中转）：&lt;a href="https://docs.thandoff.com/zh/admin/nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/admin/nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;移动端（从手机查看会话与实例）：&lt;a href="https://docs.thandoff.com/zh/clients/mobile" rel="noopener noreferrer"&gt;https://docs.thandoff.com/zh/clients/mobile&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Codex 在 Windows + PowerShell 上的问题，我们决定把执行面换到 Linux</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:49:34 +0000</pubDate>
      <link>https://dev.to/edgestorage/codex-zai-windows-powershell-shang-de-wen-ti-wo-men-jue-ding-huan-ge-si-lu-rao-kai-leb</link>
      <guid>https://dev.to/edgestorage/codex-zai-windows-powershell-shang-de-wen-ti-wo-men-jue-ding-huan-ge-si-lu-rao-kai-leb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;标题备选（数字钩子，需带上查询语句与日期）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;「4,269 条 issue 标题里带 Windows：Codex 在 PowerShell 下的问题是结构性的」&lt;/li&gt;
&lt;li&gt;「Codex CLI 有 203 条标题含 PowerShell 的 issue，我们不再指望修好它」&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;先说结论：Codex 在 Windows 上不是不能用，而是&lt;strong&gt;你把任务规模化之后，被 PowerShell 打断的次数会变成一种固定成本&lt;/strong&gt;。我们试过的方向里，性价比最高的不是写更长的提示词教模型写 PowerShell，而是把执行面换到 Linux 用户态。&lt;/p&gt;

&lt;p&gt;TaskHandoff 是免费、开源、可自托管的 AI 远程工作平台。这篇先把网上能查到的真实问题摊开（都带链接，可以自己核），再讲我们在 TaskHandoff 里选的那条路，以及这条路的代价。&lt;/p&gt;

&lt;h2&gt;
  
  
  一、先看真实问题：四类，且不是同一类
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A 类：PowerShell 不是 Bash，模型的命令会持续撞墙
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Codex CLI 在 PowerShell 下会&lt;strong&gt;反复生成&lt;/strong&gt; &lt;code&gt;rg 'pattern' *.md&lt;/code&gt; 这种写法，直接报 &lt;code&gt;os error 123&lt;/code&gt;；同一环境里 &lt;code&gt;rg -g '*.md' 'pattern' .&lt;/code&gt; 是正常的。报告者的原话是：这不是一次失败，而是在正常使用中不断生成不被支持的命令形式，产生可避免的失败调用和恢复工作，浪费时间和 token（&lt;a href="https://github.com/openai/codex/issues/45685" rel="noopener noreferrer"&gt;#45685&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;过大的 PowerShell 命令会在启动前就失败，报 &lt;code&gt;os error 206&lt;/code&gt;（&lt;a href="https://github.com/openai/codex/issues/45788" rel="noopener noreferrer"&gt;#45788&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;引号、反斜杠、JSON 参数被 PowerShell 改写；从 Bash 教程抄来的命令直接失败；&lt;code&gt;ExecutionPolicy&lt;/code&gt; 拦截脚本；路径带空格或中文就出问题（&lt;a href="https://knightli.com/2026/07/29/codex-windows-native-wsl-sandbox-path-troubleshooting/" rel="noopener noreferrer"&gt;knightli.com 的排查手册&lt;/a&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  B 类：Windows 特有的策略与沙箱层
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exec_command&lt;/code&gt; 在 PowerShell 还没启动前就被拒绝：&lt;code&gt;CreateProcess rejected: blocked by policy&lt;/code&gt;，没有子进程 PID、没有退出码。值得注意的是，报告者当时已经配置 &lt;code&gt;approval_policy=Never&lt;/code&gt; + &lt;code&gt;sandbox_policy=DangerFullAccess&lt;/code&gt;，依然被拦，而且错误信息&lt;strong&gt;不告诉你是哪一层策略拦的&lt;/strong&gt;（&lt;a href="https://github.com/openai/codex/issues/44839" rel="noopener noreferrer"&gt;#44839&lt;/a&gt;）。同类报告还有只读 &lt;code&gt;Get-Content&lt;/code&gt; 被拒（&lt;a href="https://github.com/openai/codex/issues/43881" rel="noopener noreferrer"&gt;#43881&lt;/a&gt;）、只读健康检查被拒（&lt;a href="https://github.com/openai/codex/issues/43633" rel="noopener noreferrer"&gt;#43633&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;Windows 沙箱阻塞 Node 子进程：&lt;code&gt;spawnSync EPERM&lt;/code&gt;、&lt;code&gt;errno -4048&lt;/code&gt;；想改成提权模式，反而在 UAC 提示出现前就失败：&lt;code&gt;helper_sandbox_lock_failed&lt;/code&gt; / &lt;code&gt;SetNamedSecurityInfoW ... failed: 5&lt;/code&gt;（&lt;a href="https://github.com/openai/codex/issues/45697" rel="noopener noreferrer"&gt;#45697&lt;/a&gt;）。同类还有子进程管道建立失败、PID 为 0（&lt;a href="https://github.com/openai/codex/issues/44838" rel="noopener noreferrer"&gt;#44838&lt;/a&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  C 类：跨文件系统与路径映射
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;切到 WSL2 之后，项目路径会变成 &lt;code&gt;/mnt/c/Program Files/WindowsApps/...&lt;/code&gt; 这种形态；合法符号链接被拒；粘贴的图片 image tool 读不到（而沙箱里的 Bash 明明能读）（&lt;a href="https://github.com/openai/codex/issues/44182" rel="noopener noreferrer"&gt;#44182&lt;/a&gt;）。Windows + WSL2 无法创建本地项目也有人在报（&lt;a href="https://github.com/openai/codex/issues/45887" rel="noopener noreferrer"&gt;#45887&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;CRLF 会让 Git 显示「所有文件都被修改」，权限位全部变化；同一个仓库可能出现 Windows 侧能拉取、WSL 侧不能拉取（knightli.com）。&lt;/li&gt;
&lt;li&gt;官方说明里有一条容易被忽略：&lt;strong&gt;集成终端 shell 和 agent environment 是两个独立设置&lt;/strong&gt;。你把集成终端改成 Git Bash，agent 依然用 PowerShell 跑命令（&lt;a href="https://nenufm.com/posts/optimize-codex-shell-on-windows" rel="noopener noreferrer"&gt;nenufm.com&lt;/a&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  D 类：安全软件与企业环境
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;卡巴斯基把 &lt;code&gt;%USERPROFILE%\.codex\.sandbox-bin\codex-command-runner-0.154.0.exe&lt;/code&gt; 判为 &lt;code&gt;PDM:Exploit.Win32.Generic.nblk&lt;/code&gt; 并拦截；而该文件与官方 npm 包内的文件 SHA-256 完全一致，Kaspersky 自己的动态分析结果又是 Clean（&lt;a href="https://github.com/openai/codex/issues/45810" rel="noopener noreferrer"&gt;#45810&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;与 FSLogix 不兼容（&lt;a href="https://github.com/openai/codex/issues/45897" rel="noopener noreferrer"&gt;#45897&lt;/a&gt;）；应用商店更新后启动失败报 &lt;code&gt;rename_staging EPERM&lt;/code&gt;（&lt;a href="https://github.com/openai/codex/issues/45858" rel="noopener noreferrer"&gt;#45858&lt;/a&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;规模参考&lt;/strong&gt;（2026-09-16 用 GitHub 搜索查询，计数为近似值）：&lt;code&gt;repo:openai/codex type:issue&lt;/code&gt; 共 28,341 条，其中 &lt;code&gt;label:"windows-os"&lt;/code&gt; 7,291 条，标题含 Windows 的 issue 4,269 条，标题含 PowerShell 的 203 条。&lt;/p&gt;

&lt;h2&gt;
  
  
  二、为什么「修好 PowerShell」这条路收益有限
&lt;/h2&gt;

&lt;p&gt;把上面四类分开看就清楚了：A 类是 shell 语义差异，B 类是 Windows 安全模型与沙箱实现，C 类是跨文件系统映射，D 类是第三方安全软件。&lt;strong&gt;它们不是一个补丁能一起解决的问题。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;社区里有人做过一轮很扎实的尝试（&lt;a href="https://nenufm.com/posts/optimize-codex-shell-on-windows" rel="noopener noreferrer"&gt;nenufm.com&lt;/a&gt;，2026-07-21 发布、2026-09-06 更新）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;装了 PowerShell 7，对体验有帮助，保留；&lt;/li&gt;
&lt;li&gt;把一大段 PowerShell 工具提示词放进全局 &lt;code&gt;AGENTS.md&lt;/code&gt;——&lt;strong&gt;没解决问题&lt;/strong&gt;，后来删掉了；&lt;/li&gt;
&lt;li&gt;装了 Coreutils 想让命令更接近 Unix，结果 &lt;code&gt;PSConsoleHostReadLine&lt;/code&gt; 会改写交互输入，&lt;code&gt;Get-Command rm&lt;/code&gt; 显示的别名和实际执行入口不一致，辨认成本上升，最后卸载；&lt;/li&gt;
&lt;li&gt;还发现失败未必跟 shell 语法有关：有一次是 Codex 调用了旧版本目录里的 &lt;code&gt;codex.exe&lt;/code&gt;，换到当前安装路径就成功了——这种错误升级 PowerShell 或换 Bash 都救不了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;他最后的做法是把约束写简单：精确搜索用 &lt;code&gt;rg -n -F&lt;/code&gt;、多条件用多个 &lt;code&gt;-e&lt;/code&gt;、不要额外嵌套 &lt;code&gt;pwsh -Command&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这条经验值得记住：&lt;strong&gt;提示词能降低概率，但降不到零；而每次失败的代价是一次工具调用、一次恢复、以及被消耗掉的上下文。&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  三、我们选的路：Windows 只当入口，任务跑在 Linux 侧
&lt;/h2&gt;

&lt;p&gt;TaskHandoff 在 Windows 桌面端上有一个明确的约束：&lt;strong&gt;Windows 上创建本机实例走的是 Docker Runtime，内置 Local Runtime 只在受支持的非 Windows 节点提供&lt;/strong&gt;（见 &lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;安装与登录&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;翻译成使用方式就是：Windows 负责界面，任务实际跑在 Linux 容器里。A 类的 PowerShell 语义问题、B 类的 Windows 沙箱策略问题，在这一层就不存在了——agent 执行的是容器内 Linux shell 的命令。&lt;/p&gt;

&lt;p&gt;另外两条路径也在同一套模型里：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;把远程 Linux 主机加为节点。&lt;/strong&gt; 一台 Debian/Ubuntu 机器加入后，和本机实例出现在同一个控制面里，可以按节点、项目、状态筛选。装法和步骤见&lt;a href="https://docs.thandoff.com/guide/first-setup" rel="noopener noreferrer"&gt;首次配置&lt;/a&gt;和&lt;a href="https://docs.thandoff.com/integrations/remote-nodes" rel="noopener noreferrer"&gt;远程节点&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;只让 Windows 承担操作入口。&lt;/strong&gt; 桌面端、浏览器、移动端、聊天桥接共用同一个控制面，跟进任务不需要守在一个 PowerShell 窗口前。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;顺带解决掉几个 C 类和运维层面的老问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;路径与工作目录&lt;/strong&gt;：一次 AI 会话绑定固定工作目录，创建后不会自动改变；要换 worktree，就在目标 worktree 上新建会话（见&lt;a href="https://docs.thandoff.com/sessions/" rel="noopener noreferrer"&gt;使用 AI 会话&lt;/a&gt;）。少一层「Windows 路径 → WSL 路径 → 容器路径」的心智转换。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;交付&lt;/strong&gt;：改动通过文件/变更视图审阅，走分支或 worktree 交付；远端操作是保守的——Pull 仅允许 fast-forward，&lt;strong&gt;永远不 force push&lt;/strong&gt;，分支分叉就阻止自动同步（见&lt;a href="https://docs.thandoff.com/workspace/repository" rel="noopener noreferrer"&gt;仓库协作&lt;/a&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;凭据&lt;/strong&gt;：Git 凭据按「协议 + 主机 + 端口 + 大小写敏感的路径前缀」限定范围，不支持通配符；HTTPS token 只在 credential-helper 请求期间短暂提供，不写进 remote URL、命令行和环境变量（见&lt;a href="https://docs.thandoff.com/security/git-credentials" rel="noopener noreferrer"&gt;Git 凭据&lt;/a&gt;）。不用在 Windows 上给每个项目单独配一遍。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;长任务不靠窗口活着&lt;/strong&gt;：关闭桌面主窗口只是隐藏到托盘，Docker Runtime 实例保持运行，下次启动后节点代理会重新发现并接管（见&lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;安装与登录&lt;/a&gt;）。配合移动端和聊天桥接，离开电脑也能看进度。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  四、代价，说清楚
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Windows 上要装 Docker。&lt;/strong&gt; 安装桌面应用本身不等于安装 Docker；创建 Docker 实例前，节点必须已安装并启动 Docker daemon，且当前用户有权限访问它。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Runtime 在 Windows 上不可用&lt;/strong&gt;，想要「直接使用宿主机目录和工具」的用法得放到非 Windows 节点上。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;换成 Linux 用户态意味着接受 Linux 工具链&lt;/strong&gt;。如果你的项目强依赖 Windows 专有工具，这套方式不适用。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;多了一层容器&lt;/strong&gt;：镜像拉取、卷管理和环境模板这些概念需要理解一次。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  五、可复用的五条原则
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;区分「操作环境」和「执行环境」。&lt;/strong&gt; 你人在 Windows 上工作，不代表 agent 必须在 PowerShell 里执行命令。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不要在提示词层面解决平台语义问题。&lt;/strong&gt; 提示词能降低概率，不能替代把环境换对。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;同一类问题要分层归因&lt;/strong&gt;：shell 语义、沙箱策略、文件系统映射、安全软件——它们的解法完全不同。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;让失败可解释。&lt;/strong&gt; 「blocked by policy」这种没有规则归属的错误，会让 agent 和人都无法自行恢复；选工具时要把它算作一个真实成本。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;长任务的存活不要依赖终端窗口。&lt;/strong&gt; 判定方法：关掉窗口、重启界面服务，任务还在不在跑。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  相关文档
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TaskHandoff 官网（产品定位与客户端下载）：&lt;a href="https://www.thandoff.com/" rel="noopener noreferrer"&gt;https://www.thandoff.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;开源仓库：&lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;https://github.com/edgestorage/task-handoff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;安装与平台支持（含 Windows 使用 Docker Runtime 的说明）：&lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;https://docs.thandoff.com/guide/install&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;首次配置：节点、环境、模型 Provider：&lt;a href="https://docs.thandoff.com/guide/first-setup" rel="noopener noreferrer"&gt;https://docs.thandoff.com/guide/first-setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;远程节点接入：&lt;a href="https://docs.thandoff.com/integrations/remote-nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/integrations/remote-nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;会话与工作目录：&lt;a href="https://docs.thandoff.com/sessions/" rel="noopener noreferrer"&gt;https://docs.thandoff.com/sessions/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;仓库协作与交付：&lt;a href="https://docs.thandoff.com/workspace/repository" rel="noopener noreferrer"&gt;https://docs.thandoff.com/workspace/repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Git 凭据范围管理：&lt;a href="https://docs.thandoff.com/security/git-credentials" rel="noopener noreferrer"&gt;https://docs.thandoff.com/security/git-credentials&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>windows</category>
      <category>selfhosted</category>
      <category>devtools</category>
    </item>
    <item>
      <title>停掉控制面之后，服务器上的 AI 任务还在跑：我们为什么把它拆成三层</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:49:27 +0000</pubDate>
      <link>https://dev.to/edgestorage/ting-diao-kong-zhi-mian-zhi-hou-ai-ren-wu-huan-zai-pao-wo-men-wei-shi-yao-ba-ta-chai-cheng-san-ceng-4hdp</link>
      <guid>https://dev.to/edgestorage/ting-diao-kong-zhi-mian-zhi-hou-ai-ren-wu-huan-zai-pao-wo-men-wei-shi-yao-ba-ta-chai-cheng-san-ceng-4hdp</guid>
      <description>&lt;p&gt;用一个界面对着某台机器上的某个终端窗口，是大多数人开始用 AI 写代码的方式。任务变多之后，尤其是任务分散在本地电脑和远程服务器上时，会出现三个具体问题：任务分散在多台机器上、每个任务需要独立且可丢弃的环境、长任务不能因为浏览器关了或者界面断了就死掉。&lt;/p&gt;

&lt;p&gt;TaskHandoff 是免费、开源、可自托管的 AI 远程工作平台。这篇讲它在这三个问题上做的架构选择：为什么控制面和执行面要拆开，状态到底归谁管，以及由此付出的代价。&lt;/p&gt;

&lt;h2&gt;
  
  
  问题：任务一多，你就开始手动当调度器
&lt;/h2&gt;

&lt;p&gt;假设你手上有三台机器：一台笔记本、一台常开的家用服务器、一台公司内网主机。你在它们上面分别跑 Codex 或 OpenCode。于是你每天在做这些事：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;记得哪台机器上有哪个仓库、哪个分支、哪个 worktree；&lt;/li&gt;
&lt;li&gt;手动把环境装好，或者忍受容器里从零开始装工具；&lt;/li&gt;
&lt;li&gt;反复切 SSH 窗口看任务进度，因为任务只在那个终端的上下文里存在；&lt;/li&gt;
&lt;li&gt;关掉笔记本之后，不知道家用服务器上的任务到底还在不在跑。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些动作里没有一个是真正的开发工作，但它们都会消耗注意力，而且出错时很难回溯。&lt;/p&gt;

&lt;h2&gt;
  
  
  我们的做法：三层，各管一件事
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;浏览器 / 桌面端 / 移动端 / 聊天平台
              │
              ▼
        Control Plane（UI、API、聊天网关）
              │
              ▼
         Node Agent（节点资源与实例生命周期）
              │
              ▼
     Controlled Instance（工作区、应用、AI 会话）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;关键不在分层本身，而在&lt;strong&gt;每层各自拥有什么状态&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control Plane&lt;/strong&gt; 拥有节点清单、实例看板、跨实例的 AI 会话视图、聊天凭据与消息路由。它是你操作的地方，也是多端共用的入口。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node Agent&lt;/strong&gt; 拥有节点本地配置、运行时资源、目录清单和实例生命周期。实例的启停由它执行。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlled Instance&lt;/strong&gt; 承载工作区、应用、AI 会话和触发器，可以独立运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为实例生命周期归 Node Agent，&lt;strong&gt;停止或重启 Control Plane 不会顺带停掉节点上由 Node Agent 管理的 Docker 实例&lt;/strong&gt;；控制面板恢复后会重新同步状态。&lt;/p&gt;

&lt;p&gt;同理，AI 会话的对话状态以 AI Session 本身为准，控制面负责的是凭据、绑定、命令解析和路由。所以关掉网页不会中断任务——这条被写进了文档 FAQ，因为它是最常被问的问题。&lt;/p&gt;

&lt;p&gt;一篇会话绑定固定工作目录，创建后不会自动改变（这条也在文档里）。要换个 worktree 干活，是「在目标 worktree 上新建会话」，而不是「把当前会话挪过去」。这是同一套状态所有权思路的延续：会话不追着环境跑。&lt;/p&gt;

&lt;h2&gt;
  
  
  停掉控制面之后会发生什么
&lt;/h2&gt;

&lt;p&gt;这条不用实测也能从状态所有权推出来，文档里也已经把它写成了 FAQ——因为问的人最多：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;关掉浏览器页面&lt;/strong&gt;：任务继续跑，重新打开后恢复权威状态。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;停止或重启 Control Plane&lt;/strong&gt;：由 Node Agent 管理的 Docker 实例继续跑，控制面板恢复后重新同步。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;真正会中断任务的操作&lt;/strong&gt;：停止或删除实例、关闭会话、停止 Node Agent。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里有个最容易误判的坑，我们也把它写进了文档：&lt;strong&gt;页面显示「正在重连」不等于后台任务停了。&lt;/strong&gt; 如果界面一直卡在重连，问题通常在网络路径上——最常见的原因是反向代理没有转发 &lt;code&gt;/api/events&lt;/code&gt; 的 WebSocket，而不是 Agent 挂了。把「显示状态」和「权威状态」分开看，能省掉不少误判。&lt;/p&gt;

&lt;h2&gt;
  
  
  取舍与被否决的方案
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;为什么不把实例生命周期直接放在控制面？&lt;/strong&gt; 那样控制面重启就会波及正在跑的长任务，而且单点变成了「UI 挂掉 = 所有任务挂掉」。把生命周期的所有权下移到节点，代价是控制面离线期间你只能在节点上继续跑已经存在的实例——新建实例、改配置这类操作都要等控制面回来。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;断线重连的语义要写清楚。&lt;/strong&gt; 界面断线重连时会重新获取权威会话快照。最容易造成误解的是那个「正在重连」提示——它不代表后台任务已经停止。我们把这条明确写进文档，而不是靠用户自己猜：会话状态在 Agent 那边，界面的连接状态只是显示层的问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;运行时不锁死在 Docker。&lt;/strong&gt; Docker Runtime 是当前主要的隔离运行时，一个节点可以跑多个实例；Local Runtime 直接用宿主机环境，同一宿主用户只能有一个本地实例，也不支持 Windows。两者共享同一套实例模型，运行时通过 adapter 扩展，所以将来接 Kubernetes 不需要另做一套 UI 流程。&lt;/p&gt;

&lt;h2&gt;
  
  
  结果：行为上发生了什么变化
&lt;/h2&gt;

&lt;p&gt;不写数字，只列可验证的行为差异：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关掉网页、甚至停掉控制面，节点上正在跑的任务不受影响；重新打开后能拿回权威状态。&lt;/li&gt;
&lt;li&gt;本地和远程服务器上的实例出现在同一个看板里，可以按节点、项目、状态筛选，不用逐个 SSH 上去看。&lt;/li&gt;
&lt;li&gt;长任务可以在移动端或聊天里跟进；Telegram、微信、钉钉、飞书的桥接把消息和审批路由到指定实例的会话。&lt;/li&gt;
&lt;li&gt;实例可以独立运行，节点负责它的生命周期，控制面挂了也不会连带停掉它。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  可复用的四条原则
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;把生命周期所有权放在离资源最近的那一层。&lt;/strong&gt; 编排层可以崩、可以升级，但它不该拥有「谁在运行」这个事实。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;区分「显示状态」和「权威状态」。&lt;/strong&gt; 界面上的连接状态、重连提示，都不等于后台任务的状态；接错这两个概念会带来大量虚假告警和误判。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;让长任务的存活不依赖交互层。&lt;/strong&gt; 判定标准很简单：关掉浏览器、重启 UI 服务，任务还在不在跑。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;状态所有权写进文档，而不是留在实现里。&lt;/strong&gt; 用户遇到「正在重连」时，需要一句话告诉他这意味着什么。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  想自己跑一遍
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TaskHandoff 官网（产品定位与客户端下载）：&lt;a href="https://www.thandoff.com/" rel="noopener noreferrer"&gt;https://www.thandoff.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;开源仓库：&lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;https://github.com/edgestorage/task-handoff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;三层结构和每个概念的职责：&lt;a href="https://docs.thandoff.com/guide/" rel="noopener noreferrer"&gt;https://docs.thandoff.com/guide/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;部署到自己的机器（Docker Compose 或 Debian/Ubuntu systemd 一键安装）：&lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;https://docs.thandoff.com/guide/install&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;接入远程节点：&lt;a href="https://docs.thandoff.com/integrations/remote-nodes" rel="noopener noreferrer"&gt;https://docs.thandoff.com/integrations/remote-nodes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;「关闭网页会不会停任务」「停 Control Plane 会不会停实例」：&lt;a href="https://docs.thandoff.com/faq" rel="noopener noreferrer"&gt;https://docs.thandoff.com/faq&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>devtools</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A review checklist for AI coding agent output (7 things I check before trusting a change)</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Tue, 15 Sep 2026 02:08:05 +0000</pubDate>
      <link>https://dev.to/edgestorage/a-review-checklist-for-ai-coding-agent-output-7-things-i-check-before-trusting-a-change-276c</link>
      <guid>https://dev.to/edgestorage/a-review-checklist-for-ai-coding-agent-output-7-things-i-check-before-trusting-a-change-276c</guid>
      <description>&lt;p&gt;Every team running an AI coding agent eventually hits the same wall: the agent produces a lot of output, and reviewing that output is now the bottleneck. A terminal transcript is not a review artifact. Here is the checklist I use before I trust an agent's change, and the tooling each line implies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Can I see the change as a diff, not as prose?&lt;/strong&gt;&lt;br&gt;
If the only thing you get is a summary, you are reviewing a claim, not code. Ask for the diff first. Everything after this line only matters if this one is true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Do I know which directory the agent was in?&lt;/strong&gt;&lt;br&gt;
Ambiguous working directories are the root cause of a surprising number of "it edited the wrong file" incidents. Pin the path explicitly and make it part of the job record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is the change isolated to a ref I can check out?&lt;/strong&gt;&lt;br&gt;
A branch or a worktree means you can test the change yourself, and revert is one command. If the agent mutated your working tree in place, you have already lost the ability to compare. There is a full walkthrough of the worktree approach in the &lt;a href="https://docs.thandoff.com/tutorials/worktree-session" rel="noopener noreferrer"&gt;worktree session tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Which commands did it run, and did any of them touch the network?&lt;/strong&gt;&lt;br&gt;
Install scripts, dependency bumps and &lt;code&gt;curl | sh&lt;/code&gt; are where the sharp edges live. Log tool activity, not just the final text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Where did the credentials come from?&lt;/strong&gt;&lt;br&gt;
If the agent needed a private remote, the token should be scoped to that host and short-lived where possible. Broad credentials handed to an autonomous process is how a small mistake becomes an incident — see &lt;a href="https://docs.thandoff.com/security/git-credentials" rel="noopener noreferrer"&gt;git credentials&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Can a second person reproduce the run?&lt;/strong&gt;&lt;br&gt;
Same image, same runtime, same starting commit. If reproducing it takes a verbal explanation, it is not a process yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What stopped it?&lt;/strong&gt;&lt;br&gt;
Time limit, token limit, an approval gate, or completion — you should be able to tell from the session state alone. Getting the states named unambiguously is worth doing early; a shared &lt;a href="https://docs.thandoff.com/reference/status-glossary" rel="noopener noreferrer"&gt;status glossary&lt;/a&gt; removes a lot of ambiguity from handoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this implies about your setup
&lt;/h2&gt;

&lt;p&gt;Once you write the checklist down, the requirements fall out almost mechanically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sessions need an explicit working directory and state,&lt;/li&gt;
&lt;li&gt;workspaces need to be disposable and isolated, so a bad run cannot poison the next one (&lt;a href="https://docs.thandoff.com/instances/" rel="noopener noreferrer"&gt;instances&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;Git review needs to be native, not bolted on (&lt;a href="https://docs.thandoff.com/workspace/repository" rel="noopener noreferrer"&gt;repository workspace&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;automations that repeat a routine need their own audit trail (&lt;a href="https://docs.thandoff.com/automation/" rel="noopener noreferrer"&gt;automation&lt;/a&gt;, &lt;a href="https://docs.thandoff.com/automation/actions" rel="noopener noreferrer"&gt;actions&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;and the whole thing has to run where the code runs, or the credential question answers itself badly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;Most of this checklist exists because I did the opposite first. I let an agent work directly in a checkout I cared about, reviewed the summary, and merged. It worked — for about three weeks. The failure was not a dramatic bug; it was a dependency change nobody noticed until a deploy.&lt;/p&gt;

&lt;p&gt;Since then I run every agent task in a disposable workspace, with the diff as the deliverable. That workflow is what I packaged as &lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;TaskHandoff&lt;/a&gt;: a self-hosted control plane where each task gets its own Local or Docker workspace, sessions are reviewable, and Git changes arrive as a branch or worktree you can test. Apache-2.0, and the docs start at &lt;a href="https://docs.thandoff.com/" rel="noopener noreferrer"&gt;docs.thandoff.com&lt;/a&gt; (&lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;installation&lt;/a&gt;, &lt;a href="https://docs.thandoff.com/faq" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;What is on your checklist? I am particularly curious whether people gate on cost, on network access, or on the diff size.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>codequality</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Self-hosting an AI coding agent workbench: running Codex on my own machines</title>
      <dc:creator>EdgeStorage</dc:creator>
      <pubDate>Mon, 14 Sep 2026 17:34:57 +0000</pubDate>
      <link>https://dev.to/edgestorage/self-hosting-an-ai-coding-agent-workbench-running-codex-on-my-own-machines-58fc</link>
      <guid>https://dev.to/edgestorage/self-hosting-an-ai-coding-agent-workbench-running-codex-on-my-own-machines-58fc</guid>
      <description>&lt;p&gt;Most AI coding assistants assume you are fine sending your repository somewhere else. For side projects that is a reasonable trade. For anything with a client contract, an internal service, or a repository you cannot move, it is not.&lt;/p&gt;

&lt;p&gt;The interesting shift of the last year is that the &lt;em&gt;agent&lt;/em&gt; part has become a commodity — Codex, OpenCode, Claude Code and friends all expose a CLI you can run yourself. What is still missing for most teams is everything &lt;em&gt;around&lt;/em&gt; the agent: where does it run, which filesystem does it see, who reviews the diff, and how do you run the same routine twice without re-explaining it from scratch.&lt;/p&gt;

&lt;p&gt;I have been building that layer for a while. Here is the model that ended up working for me, and the concrete pieces you need if you want to reproduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Separate the control plane from the machine that runs the agent
&lt;/h2&gt;

&lt;p&gt;The mistake I made first was treating "the agent" as the single thing to manage. In practice there are three distinct things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;control plane&lt;/strong&gt; that owns identity, sessions and routing,&lt;/li&gt;
&lt;li&gt;one or more &lt;strong&gt;nodes&lt;/strong&gt; — your laptop, a home server, a cheap VPS — that can actually execute work,&lt;/li&gt;
&lt;li&gt;a set of &lt;strong&gt;workspaces&lt;/strong&gt; that give each task an isolated filesystem and process tree.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those are three separate concepts, everything else gets easier. A session is not "a chat window", it is a job with an owner, a working directory, a runtime and a status. You can list it, resume it, hand it to a colleague, or kill it.&lt;/p&gt;

&lt;p&gt;If you want the shape of that in practice, the &lt;a href="https://docs.thandoff.com/guide/first-setup" rel="noopener noreferrer"&gt;first setup guide&lt;/a&gt; walks the control-plane/node split end to end, and &lt;a href="https://docs.thandoff.com/integrations/remote-nodes" rel="noopener noreferrer"&gt;remote nodes&lt;/a&gt; covers what it takes to attach a second machine — the interesting part is that node enrollment is a token, not a VPN, so a box behind NAT is reachable without exposing your whole network.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One workspace per task, not per conversation
&lt;/h2&gt;

&lt;p&gt;The second change was moving from "one container per project" to "one workspace per task". A task workspace is disposable: it gets a working directory, a defined runtime, and a bounded life. Two agents working on the same repository in parallel stop stepping on each other's files, and cleaning up is a delete rather than an archaeology exercise.&lt;/p&gt;

&lt;p&gt;That works with either a local runtime (run directly on the host, reuse the tools you already have installed) or a Docker runtime (each workspace in its own container). I default to Docker for anything that installs dependencies, and local for repositories where I want the host toolchain.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://docs.thandoff.com/instances/" rel="noopener noreferrer"&gt;instances&lt;/a&gt; for how workspaces are created and restored, and &lt;a href="https://docs.thandoff.com/workspace/repository" rel="noopener noreferrer"&gt;workspace: repository&lt;/a&gt; if you want the Git side of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make the agent's output reviewable, not just readable
&lt;/h2&gt;

&lt;p&gt;A terminal scrollback is a terrible review artifact. What I want after an agent finishes is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the diff, in a form I can read like a pull request,&lt;/li&gt;
&lt;li&gt;the commands it actually ran,&lt;/li&gt;
&lt;li&gt;a branch or worktree I can check out and test myself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The practical trick is to have the agent deliver onto a separate ref instead of mutating your working tree. Worktrees are the cheapest version of that: the agent works on its own checkout, &lt;code&gt;git diff&lt;/code&gt; shows you exactly what happened, and merging is a normal Git operation. There is a walkthrough of that workflow in the &lt;a href="https://docs.thandoff.com/tutorials/worktree-session" rel="noopener noreferrer"&gt;worktree session tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the agent needs to pull a private dependency, the credentials question comes up immediately. Scope the token to the specific remote, use it for provisioning, and keep it out of the agent's environment where possible — &lt;a href="https://docs.thandoff.com/security/git-credentials" rel="noopener noreferrer"&gt;git credentials&lt;/a&gt; goes into that in more detail than is comfortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Automate the routines you repeat
&lt;/h2&gt;

&lt;p&gt;After a few weeks you notice you are running the same sequence over and over: pull, update a lockfile, run the test suite, fix the lint, open a branch. That is where a small automation layer pays for itself. I ended up with four primitives that compose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;documents&lt;/strong&gt; — the instructions, versioned,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;triggers&lt;/strong&gt; — what starts it (schedule, webhook, manual),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;actions&lt;/strong&gt; — what it does (run a session, run a command, notify),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;schedules&lt;/strong&gt; — when.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those exist you stop pasting the same prompt and start reviewing output instead. The &lt;a href="https://docs.thandoff.com/automation/" rel="noopener noreferrer"&gt;automation docs&lt;/a&gt; cover the model, and &lt;a href="https://docs.thandoff.com/automation/actions" rel="noopener noreferrer"&gt;actions&lt;/a&gt; is the part with the sharp edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run the agent where the code lives. Latency is the least of your problems; data residency and filesystem access are the real ones.&lt;/li&gt;
&lt;li&gt;Give every task a disposable workspace. It removes an entire class of "which file did it overwrite" bugs.&lt;/li&gt;
&lt;li&gt;Make the agent deliver a diff, not a summary. Reviewing summaries is how you end up trusting a black box.&lt;/li&gt;
&lt;li&gt;Automate only after you have run the routine manually three times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to try this shape without assembling it yourself, this is exactly what I have been packaging as &lt;a href="https://github.com/edgestorage/task-handoff" rel="noopener noreferrer"&gt;TaskHandoff&lt;/a&gt; — a self-hosted control plane for AI coding agents, Apache-2.0, with local and remote nodes, per-task Local or Docker workspaces, Codex and OpenCode sessions, Git review, and scheduled automation. Start with &lt;a href="https://docs.thandoff.com/guide/install" rel="noopener noreferrer"&gt;installation&lt;/a&gt;, or skim the &lt;a href="https://docs.thandoff.com/faq" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; if you are wondering whether it needs Docker or a public IP.&lt;/p&gt;

&lt;p&gt;The docs are at &lt;strong&gt;&lt;a href="https://docs.thandoff.com/" rel="noopener noreferrer"&gt;docs.thandoff.com&lt;/a&gt;&lt;/strong&gt; (also in &lt;a href="https://docs.thandoff.com/" rel="noopener noreferrer"&gt;Chinese&lt;/a&gt; — the site is bilingual, add &lt;code&gt;/en/&lt;/code&gt; for the English tree).&lt;/p&gt;

&lt;p&gt;What is your setup? Are you running agents on your laptop, a home lab box, or somewhere properly boring like a VPS? I am curious which of the three layers above people are missing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>selfhosted</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
