<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>AI - Category - SaiyuDoc</title><link>https://www.wangsaiyu.com/categories/ai/</link><description>AI - Category - SaiyuDoc</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>panicwang@outlook.com (王赛宇)</managingEditor><webMaster>panicwang@outlook.com (王赛宇)</webMaster><lastBuildDate>Tue, 13 Oct 2026 17:00:00 +0800</lastBuildDate><atom:link href="https://www.wangsaiyu.com/categories/ai/" rel="self" type="application/rss+xml"/><item><title>Agent 部署方式：中心服务与 Sandbox</title><link>https://www.wangsaiyu.com/posts/ai/agent-deployment-architecture/</link><pubDate>Tue, 13 Oct 2026 17:00:00 +0800</pubDate><author>王赛宇</author><guid>https://www.wangsaiyu.com/posts/ai/agent-deployment-architecture/</guid><description><![CDATA[<p>Agent 接收到任务后，会多次调用模型和工具，直到任务结束。组织这个过程的程序称为运行时，也就是 Runtime。</p>
<p>本文讨论两种部署方式：由中心服务运行 Agent，通过远程接口操作工程；或者把 Codex 放进 Sandbox，由沙箱内的引擎直接操作工程。下面分别介绍它们的组成、数据存储、执行过程，以及中断后的恢复方法。</p>
<h2 id="1-总体结构">1. 总体结构</h2>
<p>先说明本文中几个组件的含义：</p>
<ul>
<li><code>Runtime</code>：组织模型调用、工具执行和上下文更新的程序；</li>
<li><code>Sandbox</code>：隔离的执行环境，包含工程目录，以及读写文件、运行命令所需的程序；</li>
<li><code>模型 API</code>：接收上下文，返回文本或工具调用请求；</li>
<li><code>平台服务</code>：接收用户需求，管理项目、任务、权限、预算和交付结果。</li>
</ul>
<p>这几个组件可以有不同的部署关系，如下图所示。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/01-runtime-location.svg" title="中心 Runtime 与 Sandbox 内 Codex 的部署关系" data-thumbnail="/images/agent-deployment/01-runtime-location.svg" data-sub-html="<h2>图 1：两种部署方式</h2>">
        
    </a><figcaption class="image-caption">图 1：两种部署方式</figcaption>
    </figure>
<p>中心服务方案中，Runtime 调用模型，解析模型提出的工具请求，再通过远程接口让 Sandbox 执行。模型拿到工具结果后，继续决定下一步操作。Runtime 可以部署多个实例，分别执行不同任务。</p>
<p>沙箱内 Codex 方案中，平台把任务交给 Codex，由它完成这段循环。文件和命令就在 Codex 所在的环境中，可以直接访问。这里放进沙箱的是执行程序，模型推理仍然可以使用远端 API。</p>
<p>两种方案都需要平台，也都可以扩容。它们主要区别在于：模型调用与工具执行之间的循环，由中心进程维护，还是由沙箱内进程维护。</p>
<p>运行时是否自研，是另一个选择。中心服务可以接入现成引擎，沙箱里也可以运行自研 Agent。本文用我们现有的中心 Runtime 说明第一种方式，用沙箱内 Codex 说明第二种方式；后者按参考架构讨论。</p>
<h2 id="2-中心服务">2. 中心服务</h2>
<h3 id="21-接收任务与分配执行">2.1. 接收任务与分配执行</h3>
<p>假设用户提交一个需求：“给游戏增加暂停菜单”。接入服务需要先确定用户、项目和工作区，检查权限与预算，再创建运行记录。</p>
<p>我们将一次用户需求对应的运行称为 <code>Run</code>。Runtime 的工作实例称为 <code>Worker</code>。接入服务把 <code>run_id</code> 交给任务队列，空闲 Worker 领取任务后，从数据库读取运行信息，开始执行。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/02-central-runtime.svg" title="任务从接入服务经队列交给 Runtime，并访问数据库和工具" data-thumbnail="/images/agent-deployment/02-central-runtime.svg" data-sub-html="<h2>图 2：中心服务的组成与调用关系</h2>">
        
    </a><figcaption class="image-caption">图 2：中心服务的组成与调用关系</figcaption>
    </figure>
<p>数据库写入和队列派发之间，存在一个需要处理的间隙：任务已经保存，但进程在发送队列消息前退出，任务就可能一直无人执行。</p>
<p>一种实现方法是在同一事务中保存 <code>Run</code> 和待派发记录，再由后台程序将待派发记录送进队列，这就是 <code>Outbox</code>。发送失败时重新发送；Worker 收到重复通知时，通过数据库条件更新领取任务，避免多个实例同时取得执行权。</p>
<p>用户重复点击提交，也需要单独处理。接入请求可以携带稳定的请求编号，相同请求再次到达时返回原 <code>Run</code>。这个编号表示用户的一次提交，与 Worker 后续重试的编号不同。</p>
<h3 id="22-运行记录">2.2. 运行记录</h3>
<p>中心服务首先要区分三种对象：</p>
<ul>
<li><code>Thread</code>：会话，关联多轮对话及其工程；</li>
<li><code>Run</code>：一次运行，对应本轮用户需求；</li>
<li><code>Attempt</code>：一次执行尝试，记录这次由哪个 Worker 执行，以及如何结束。</li>
</ul>
<p>例如，同一个会话里“增加暂停菜单”和“修改菜单颜色”是两个 <code>Run</code>。第一个 Run 执行到一半，Worker 重启后由另一实例接管，则会产生新的 <code>Attempt</code>，但仍属于原 Run。</p>
<p>我们的中心运行时使用 <code>threads</code>、<code>agent_runs</code>、<code>agent_run_attempts</code> 和 <code>messages</code> 保存相应记录。运行配置和恢复所需变量也随 Run 保存，恢复时使用原任务的输入。</p>
<p>围绕一次运行，持久存储至少需要覆盖以下内容。这里按用途列出逻辑记录，实际实现可以合并或拆分表。</p>
<table>
  <thead>
      <tr>
          <th>记录</th>
          <th>保存内容与写入时机</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>会话</td>
          <td>创建时保存用户、项目、工作区和默认配置；配置变化时更新。</td>
      </tr>
      <tr>
          <td>运行</td>
          <td>接收需求时保存原始输入、本次配置、预算和状态；结束时补充结果与原因。</td>
      </tr>
      <tr>
          <td>执行尝试</td>
          <td>领取或接管任务时登记执行者，执行中更新心跳，结束时记录退出原因。</td>
      </tr>
      <tr>
          <td>消息</td>
          <td>按顺序追加完整的用户输入、模型输出、工具调用及工具结果。</td>
      </tr>
      <tr>
          <td>工具操作</td>
          <td>调用前保存参数和操作编号，返回后保存状态、远端任务或命令会话编号。</td>
      </tr>
      <tr>
          <td>检查点</td>
          <td>上下文压缩完成后，保存压缩结果及覆盖到哪条消息。</td>
      </tr>
      <tr>
          <td>执行事件</td>
          <td>保存需要补发给客户端的进度、事件顺序和所属执行尝试。</td>
      </tr>
      <tr>
          <td>交付记录</td>
          <td>保存代码或文件检查点、产物地址、运行版本和验证结果。</td>
      </tr>
  </tbody>
</table>
<p>这些记录可以使用关系数据库保存。编号、状态、归属和时间使用固定字段；模型消息、工具参数等可变内容使用 JSON 字段。按会话、Run 和消息顺序建立索引，便于查询和恢复。</p>
<p>工程文件放在独立的工作区，通过持久数据盘或文件快照保存。工作区包含未提交到 Git 的修改，所以仅保存 Git 提交号并不够。图片、截图、完整日志和构建产物一般进入对象存储，数据库保存引用和校验信息。</p>
<p>Worker 内存中仍然会缓存当前上下文和执行状态，但进程重启后，继续任务所需的数据应当能从这些存储中取回。</p>
<h3 id="23-消息与上下文">2.3. 消息与上下文</h3>
<p>消息历史和模型上下文有不同的用途。</p>
<p>消息历史记录已经发生的对话和操作，用于展示、排查与恢复；上下文是下一次实际发给模型的输入，除了消息，还包括本轮指令、工程规则和工具定义。</p>
<p>工具定义描述名称、用途和参数格式。例如命令工具需要命令与工作目录，文件工具需要路径。模型根据这些定义提出调用，Runtime 再交给对应的执行代码。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/03-context-storage.svg" title="消息字段、历史顺序、上下文检查点及模型输入" data-thumbnail="/images/agent-deployment/03-context-storage.svg" data-sub-html="<h2>图 3：消息与上下文的逻辑结构，字段为示意</h2>">
        
    </a><figcaption class="image-caption">图 3：消息与上下文的逻辑结构，字段为示意</figcaption>
    </figure>
<p>图中的消息包含以下信息：</p>
<ul>
<li><code>message_id</code>：消息编号；</li>
<li><code>run_id</code>：所属运行；</li>
<li><code>role</code>：用户、模型或工具等消息角色；</li>
<li><code>tool_call_id</code>：工具调用与结果的关联编号，普通消息可以没有；</li>
<li><code>content</code>：文本或结构化内容。</li>
</ul>
<p>上下文长度有限，不能无限追加全部历史。任务较长时，可以把较早的历史整理成摘要，保留继续执行所需的原文，再追加新的消息。压缩完成后保存的记录，称为上下文检查点。</p>
<p>图中较早的消息已整理进检查点，后续新消息仍单独保存。恢复时先加载检查点，再按顺序追加它尚未覆盖的消息。因而检查点既要保存整理后的历史，也要保存覆盖位置；只留下摘要，无法确定哪些消息已经包含在内。</p>
<p>工具调用和结果还需要符合所用模型的消息协议。压缩时保留必要的关联，尚未确认完成的操作也要保留真实状态，避免后续把“已提交”理解成“已完成”。</p>
<p>我们的实现中，<code>prompt_checkpoints</code> 保存压缩后的历史及覆盖边界，<code>runtime_context_snapshots</code> 按会话和 Agent 保存最新的模型可见上下文。前者用于接续，后者便于检查当前输入。</p>
<p>两者分开还有存储上的原因。假设每一步都向历史表追加一整份上下文，前面的内容就会反复写入。原始消息顺序追加、压缩时保存检查点、最新快照覆盖更新，可以减少这种重复。</p>
<h3 id="24-工具调用过程">2.4. 工具调用过程</h3>
<p>Runtime 收到模型输出后，需要区分普通文本和工具调用。以运行测试为例，一次工具调用的过程如下：</p>
<ol>
<li>接收完整的工具名称和参数；</li>
<li>检查工具是否可用、参数是否合法，以及任务是否有操作权限；</li>
<li>保存本次工具操作，分配可查询的调用编号；</li>
<li>调用 Sandbox 的执行接口；</li>
<li>保存执行结果，与原工具调用关联；</li>
<li>将结果加入上下文，发起下一次模型请求。</li>
</ol>
<p>工具参数可能通过流式响应逐段到达。执行层需要等完整参数形成后再调用，不能把半段命令提前交给沙箱。</p>
<p>工具也有两种常见去向。读文件、改代码、运行命令进入 Sandbox；素材生成、扣费、版本发布进入各自的业务服务。后者的操作状态由业务服务保存，Runtime 留下查询它们所需的编号。</p>
<h3 id="25-命令执行会话">2.5. 命令执行会话</h3>
<p>读取文件通常很快完成，命令则可能持续很久。测试运行几十秒，开发服务器还可能一直不退出。工具接口等待一段时间后先返回时，需要同时说明命令是否仍在执行。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/04-command-session.svg" title="命令执行会话的启动、继续读取及结束" data-thumbnail="/images/agent-deployment/04-command-session.svg" data-sub-html="<h2>图 4：通过同一执行会话访问长时间运行的命令</h2>">
        
    </a><figcaption class="image-caption">图 4：通过同一执行会话访问长时间运行的命令</figcaption>
    </figure>
<p>命令启动后，执行服务返回会话标识，通过它找到对应的沙箱和进程。后续请求使用同一标识，继续读取新增输出、发送输入，或者中断进程。进程结束时再返回退出码。</p>
<p>我们现有的 <code>exec_command</code> 由中心 Runtime 调用 SandboxProxy，再由沙箱中的执行服务启动命令。后续通过 <code>write_stdin</code> 等接口访问原会话。它们采用 Codex 风格的工具接口，这条链路中的 Agent 主循环仍在中心 Runtime。</p>
<p>输出量也需要控制。短输出可以直接进入工具结果；长日志分段返回，并注明是否截断，必要时附上完整日志引用。否则一次构建就可能把后续模型输入占满。</p>
<h3 id="26-中断与恢复">2.6. 中断与恢复</h3>
<p>Worker 重启时，Sandbox 中的命令可能还在运行。新 Worker 读取运行、消息和工具记录后，应当先查询原执行会话：</p>
<ul>
<li>进程仍在运行：继续读取输出，等待结果；</li>
<li>进程已经结束：取回退出码和剩余输出；</li>
<li>会话或沙箱已经丢失：根据操作性质决定是否重新执行；</li>
<li>状态暂时无法确认：保留未知状态，继续查询或安排核对。</li>
</ul>
<p>测试一般可以重跑，发布和付费操作则需要查询原操作、确认幂等条件，不能统一按失败重试。</p>
<p>接管还需要处理旧 Worker 恢复连接的情况。任务可以使用带有效期的租约表示执行权，由 Worker 定期更新心跳。接管时生成新的执行编号，后续数据库写入校验这个编号，拒绝旧执行者的更新。</p>
<p>但数据库的校验不会自动停止旧进程修改工程。同一个工作区需要单独保证写入者唯一，或者为不同执行尝试使用独立副本。交给新执行者之前，要确认旧进程已经停止或失去写入权限。</p>
<p>至此，中心运行时需要维护的主要部分就包括：运行记录、模型上下文、工具协议、远程进程，以及这些状态的恢复。</p>
<h2 id="3-sandbox-内运行-codex">3. Sandbox 内运行 Codex</h2>
<h3 id="31-接入结构">3.1. 接入结构</h3>
<p>把 Codex 放进 Sandbox 后，平台仍然接收用户需求、创建 Run，并保存项目、权限和预算。执行时由平台分配沙箱，再将任务交给其中的 Codex。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/05-sandbox-runtime.svg" title="平台调度多个包含 Runner 和 Codex 的 Sandbox" data-thumbnail="/images/agent-deployment/05-sandbox-runtime.svg" data-sub-html="<h2>图 5：平台与沙箱内执行程序的关系</h2>">
        
    </a><figcaption class="image-caption">图 5：平台与沙箱内执行程序的关系</figcaption>
    </figure>
<p>平台与引擎之间通常还需要一段接入程序。这里称为 <code>Runner</code>，主要负责：</p>
<ul>
<li>启动引擎，传入任务与运行配置；</li>
<li>继续原会话，传入用户的补充输入；</li>
<li>接收引擎事件，回传进度和结果；</li>
<li>处理中断，管理引擎及相关命令进程。</li>
</ul>
<p>Codex 提供 SDK 和命令行入口，可以启动、继续或恢复会话，也可以输出结构化执行事件。具体采用哪种方式，需要与选定版本一起验证。<a href="https://learn.chatgpt.com/docs/codex-sdk" target="_blank" rel="noopener noreffer ">Codex SDK</a>、<a href="https://learn.chatgpt.com/docs/developer-commands#codex-exec" target="_blank" rel="noopener noreffer ">codex exec</a>。</p>
<p>普通文件读写和命令执行在沙箱内部完成。平台原来围绕模型维护的上下文组织、工具调用循环，转由引擎处理；平台与执行侧的主要接口变为任务、补充输入、事件和控制指令。</p>
<h3 id="32-数据保存">3.2. 数据保存</h3>
<p>执行程序移入沙箱后，数据也会分布在不同位置。</p>
<table>
  <thead>
      <tr>
          <th>保存位置</th>
          <th>保存内容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>平台数据库</td>
          <td>Run、Attempt、用户与项目、预算、沙箱和会话映射、已确认事件、交付引用。</td>
      </tr>
      <tr>
          <td>Codex 状态目录</td>
          <td>固定版本恢复会话所需的文件和索引，以及对应的版本和运行配置。</td>
      </tr>
      <tr>
          <td>工程持久卷或快照</td>
          <td>源码、未提交修改、必要配置和文件检查点。</td>
      </tr>
      <tr>
          <td>业务服务</td>
          <td>已提交操作的编号、状态、费用和结果。</td>
      </tr>
      <tr>
          <td>对象存储</td>
          <td>需要留存的日志、截图、图片和构建产物。</td>
      </tr>
      <tr>
          <td>沙箱临时目录</td>
          <td>可重新安装的依赖、缓存和临时构建文件。</td>
      </tr>
  </tbody>
</table>
<p>平台需要把 <code>run_id</code>、<code>attempt_id</code>、<code>sandbox_id</code>、工作区编号和 Codex 会话编号关联起来。用户继续修改时，据此找到原工程与会话；沙箱替换时，据此确定恢复哪份状态。</p>
<p>Codex 状态目录需要按照固定版本的实际要求保存。聊天文本导出未必包含恢复所需的全部索引和执行状态。不同项目还应使用隔离的状态目录与工作区，避免引擎找到另一项目的文件或会话。</p>
<h3 id="33-任务执行与事件回传">3.3. 任务执行与事件回传</h3>
<p>一项任务进入执行阶段时，平台先创建或复用沙箱，挂载工作区与引擎状态，准备依赖和范围受限的访问权限。Runner 接收启动指令后，创建新会话或继续原会话。</p>
<p>这里需要分别处理启动指令和执行事件的重试。</p>
<p>平台发送启动指令后没有收到确认，Runner 可能已经启动了引擎。启动指令因此需要稳定编号，Runner 保存已接受的指令，平台重连后查询原执行。否则一次网络超时就可能启动两个 Codex 进程。</p>
<p>事件回传也可能断线。Runner 可以按 Run 和 Attempt 为事件编号，暂存尚未确认的事件；平台保存后返回确认，重连后从最后确认位置继续传递。平台按照事件编号去重，客户端也可以从已接收位置补读。</p>
<p>这里重传的是执行记录，接收方不会因此再执行一遍工具。沙箱本地暂存只能应对短暂断线；如果连磁盘一起丢失，未同步出去的事件仍然会丢失，关键恢复记录需要及时保存到外部存储。</p>
<h3 id="34-沙箱回收与恢复">3.4. 沙箱回收与恢复</h3>
<p>沙箱重新创建后，文件、会话和进程需要分别处理。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/06-sandbox-recovery.svg" title="利用文件快照、引擎状态和业务记录恢复沙箱任务" data-thumbnail="/images/agent-deployment/06-sandbox-recovery.svg" data-sub-html="<h2>图 6：沙箱重建后的恢复过程</h2>">
        
    </a><figcaption class="image-caption">图 6：沙箱重建后的恢复过程</figcaption>
    </figure>
<p>继续用暂停菜单的例子：Codex 修改了源码并启动测试，随后沙箱丢失。恢复时先准备新环境，取回源码和未提交修改，再加载原会话。原测试进程通常已经不存在，确认可以安全重跑后，重新启动测试。</p>
<p>文件与会话的保存位置不同，保存时间也可能不同。会话已记录“修改完成”，文件快照却来自修改前，后续执行就会基于错误的工程状态。可以在相关工具动作结束后，为工程快照、会话状态和事件确认位置保存关联检查点；无法保证一致时，恢复流程需要检查实际差异。</p>
<p>开发服务器、终端和网络连接属于运行中的进程状态。即使会话与文件都恢复了，也需要重新检查这些进程是否存在，并按需启动。</p>
<p>我们的沙箱代码中，有一套可选的本地文件读写方案：依赖与构建尽量使用本地磁盘，源码保存在持久工作区，最终产物再写回。该方案默认关闭，需要配置启用。这里区分的正是可重建数据与需要保留的任务成果。</p>
<p>沙箱分配方式也影响恢复成本。按项目保留环境，可以复用依赖和预览进程，同时需要处理空闲资源、残留进程和并发写入；按任务新建环境，清理比较直接，但每次要准备依赖并恢复工作区。</p>
<p>引擎与镜像版本同样要固定并记录。升级前验证原会话能否继续、工具行为是否变化。重新分配执行者时，仍需处理旧租约、旧进程及工作区写入权限。</p>
<h2 id="4-业务工具">4. 业务工具</h2>
<h3 id="41-请求已执行但响应丢失">4.1. 请求已执行，但响应丢失</h3>
<p>两种部署方式都会调用素材、费用和发布等业务服务。这类调用存在一种情况：服务已经接受请求，Agent 却没有收到响应。</p>
<p>例如素材服务创建了背景图任务并登记费用，网络在返回结果时断开。Agent 恢复后若新建一次生成请求，就可能产生第二份任务和费用。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/07-lost-response.svg" title="通过稳定操作编号查询或重试同一次素材任务" data-thumbnail="/images/agent-deployment/07-lost-response.svg" data-sub-html="<h2>图 7：业务操作的查询与幂等重试</h2>">
        
    </a><figcaption class="image-caption">图 7：业务操作的查询与幂等重试</figcaption>
    </figure>
<p>Agent 在第一次调用前保存业务操作标识，素材服务接单时，将这个标识与创建的素材任务关联。这样，即使回复丢失，双方仍然能够找到同一次操作。</p>
<p>同一操作编号和相同参数再次提交时，服务返回原任务，不再执行第二次业务动作，这就是幂等。相同编号携带不同参数则应拒绝或报冲突。编号的去重范围还要结合用户、项目等归属确定。</p>
<p>恢复时按原操作标识查询，可以确认原任务仍在执行、已经结束，还是没有成功提交。不支持查询、也不支持幂等的接口，在断线后可能暂时无法确定结果，应保留未知状态，再按操作性质处理。</p>
<h3 id="42-取消权限与预算">4.2. 取消、权限与预算</h3>
<p>用户取消任务时，中心 Runtime 或 Runner 可以停止后续模型调用，并中断当前命令。已经交给素材或发布服务的工作，需要另外发送取消请求或确认最终结果。因此，“收到取消指令”和“执行已经停止”需要分别记录。</p>
<p>权限与预算由平台和业务服务校验。平台向执行环境提供有有效期、范围受限的访问权限，下游服务校验项目、操作和额度。恢复任务可以使用原运行配置，但已经撤销的权限仍以当前业务校验为准。</p>
<p>沙箱内程序可访问的文件、网络与凭证，由运行环境限制。长期、跨项目的高权限凭证进入沙箱后，生成的代码也可能读取到。Codex 自身的命令限制和审批策略需要与外层沙箱共同配置。<a href="https://learn.chatgpt.com/docs/agent-approvals-security" target="_blank" rel="noopener noreffer ">Codex 安全与审批说明</a>。</p>
<h2 id="5-方案比较">5. 方案比较</h2>
<table>
  <thead>
      <tr>
          <th>中心 Runtime</th>
          <th>Sandbox 内 Codex</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>中心运行时维护模型循环与上下文，也可以接入现成引擎。</td>
          <td>沙箱中的 Codex 维护模型循环与上下文。</td>
      </tr>
      <tr>
          <td>文件和命令经过远程工具协议执行，需要维护执行会话。</td>
          <td>基础工程操作在本地执行，平台维护引擎托管接口。</td>
      </tr>
      <tr>
          <td>恢复中心记录后，查询远端工具和业务操作。</td>
          <td>恢复平台映射、引擎状态与工作区，再核对外部操作。</td>
      </tr>
      <tr>
          <td>管理 Worker，以及实际执行代码的沙箱。</td>
          <td>管理沙箱内的引擎版本、会话、依赖和进程。</td>
      </tr>
      <tr>
          <td>项目、权限、费用和交付由业务系统管理。</td>
          <td>项目、权限、费用和交付仍由业务系统管理。</td>
      </tr>
  </tbody>
</table>
<p>任务以调用业务 API 为主、流程比较明确时，中心服务便于集中组织。任务需要长时间在工程中修改、构建和调试时，沙箱内 Codex 可以复用已有的编码执行能力，减少基础工具适配。</p>
<p>这只是按工作形态做出的判断。比较具体方案时，还需要固定任务、模型和工具条件，检查正常完成率，以及 Worker 重启、沙箱丢失、响应超时和用户取消时的恢复情况。等待时间、重复操作与有效任务成本，应当一起记录。</p>
<p>任务结束时，还需要明确交付了什么。下面是暂停菜单任务的一份简化结果结构。</p>
<figure><a class="lightgallery" href="/images/agent-deployment/08-platform-responsibility.svg" title="运行结果关联源码、构建、预览和功能验证记录" data-thumbnail="/images/agent-deployment/08-platform-responsibility.svg" data-sub-html="<h2>图 8：运行结果与交付记录的逻辑关系</h2>">
        
    </a><figcaption class="image-caption">图 8：运行结果与交付记录的逻辑关系</figcaption>
    </figure>
<p>源码记录说明改了哪些文件；构建记录说明构建是否通过、使用哪份产物；预览记录说明哪个版本正在运行；验证记录说明暂停与继续功能是否符合要求。</p>
<p>我们的一次游戏实验中，部署和健康检查通过以后，多人玩法仍然需要用多个客户端验证。执行器的结束、部署的完成和需求的实现，有各自对应的记录。无论采用哪种部署方式，平台都需要把这些结果关联到原任务。</p>]]></description></item><item><title>游戏 Agent 演进之道</title><link>https://www.wangsaiyu.com/posts/ai/game-agent-evolution/</link><pubDate>Mon, 21 Sep 2026 07:56:40 +0000</pubDate><author>王赛宇</author><guid>https://www.wangsaiyu.com/posts/ai/game-agent-evolution/</guid><description><![CDATA[<p>游戏 Agent 的目标是把用户需求转换成一个可以运行的游戏。这个过程包含需求分析、素材生成、代码修改、测试和发布，需要模型与业务系统共同完成。</p>
<p>本文记录我们在实现这条链路时做过的几次调整，依次介绍 Prompt、多 Agent 分工、Skills、工具接口和任务恢复。每一部分说明原来的实现、遇到的问题，以及后续采用的处理方式。原生引擎的接入状态以本文最初整理时为准。</p>
<h2 id="1-通过-prompt-组织开发流程">1. 通过 Prompt 组织开发流程</h2>
<h3 id="11-初始实现">1.1. 初始实现</h3>
<p>第一版使用 Python 实现。我们在一份较长的 Prompt 中描述游戏开发过程，包括理解需求、整理策划、准备素材、制作地图、编写玩法、测试和部署。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/01-overview.svg" title="初始游戏 Agent 按 Prompt 组织开发过程" data-thumbnail="/images/game-agent-evolution/01-overview.svg" data-sub-html="<h2>图 1：初始游戏开发流程</h2>">
        
    </a><figcaption class="image-caption">图 1：初始游戏开发流程</figcaption>
    </figure>
<p>Prompt 同时提供工程约定：使用什么技术栈，地图采用什么格式，素材怎样加载，最后需要交付什么。模型根据这些信息调用工具，把每一步的结果交给后续步骤。</p>
<p>这套实现的主要作用，是把业务知识和开发步骤放到一个明确的位置，使整条链路能够开始运行。后续增加的规则，则需要区分它究竟在解决哪一类问题。</p>
<h3 id="12-规则对应的问题">1.2. 规则对应的问题</h3>
<p>例如，模型不知道地图文件的格式，可以补充格式说明；模型启动命令后拿不到剩余输出，需要补充命令会话接口；素材已经生成，恢复任务时却找不到，需要保存素材任务与结果的关联。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/13-problem-types.svg" title="知识、执行接口和业务记录分别对应不同的处理方式" data-thumbnail="/images/game-agent-evolution/13-problem-types.svg" data-sub-html="<h2>图 2：不同问题对应的修改位置</h2>">
        
    </a><figcaption class="image-caption">图 2：不同问题对应的修改位置</figcaption>
    </figure>
<p>这三类问题都可能表现为“游戏没有完成”，但修改位置不同。只有第一类可以直接通过补充知识解决。工具或业务状态没有接好时，增加文字提醒仍然缺少实际的执行依据。</p>
<p>因此，Prompt 中的内容需要能够追溯到具体问题。项目约定和素材能力属于持续需要的业务信息；为了绕开某个模型弱点而加入的固定步骤，则需要在模型更新后重新验证。</p>
<h2 id="2-多-agent-分工">2. 多 Agent 分工</h2>
<h3 id="21-角色划分">2.1. 角色划分</h3>
<p>游戏任务变复杂后，我们开始按照能力拆分 Agent：主 Agent 组织任务，开发 Agent 修改代码，配置 Agent 处理地图和音频，素材相关 Agent 生成资源。角色、动画、封面等工作也有各自的入口。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/04-specialist-agents.svg" title="主 Agent 与开发、配置和素材 Agent 的调用关系" data-thumbnail="/images/game-agent-evolution/04-specialist-agents.svg" data-sub-html="<h2>图 3：按专业能力划分 Agent</h2>">
        
    </a><figcaption class="image-caption">图 3：按专业能力划分 Agent</figcaption>
    </figure>
<p>每个 Agent 有自己的 Prompt 和工具集合，能够单独调整。适合独立完成的素材任务，也可以并行执行。</p>
<p>但子 Agent 拥有自己的执行过程，主 Agent 已经知道的信息，需要通过输入显式传过去。任务完成后，结果还要传回主 Agent，才能继续后续工作。</p>
<h3 id="22-上下文交接">2.2. 上下文交接</h3>
<p>以“给角色增加二段跳”为例，开发 Agent 需要读取角色移动代码、跳跃状态、动画和音效的使用方式。如果用户同时要求“保持原来的移动手感”，这条限制也要进入子任务输入。</p>
<p>一次交接至少涉及：</p>
<ul>
<li>本次需要完成的修改；</li>
<li>必须保留的行为和用户限制；</li>
<li>使用哪个工程与工作区；</li>
<li>已经完成的相关工作；</li>
<li>返回什么结果，后续由谁接着处理。</li>
</ul>
<figure><a class="lightgallery" href="/images/game-agent-evolution/05-agent-handoff.svg" title="主 Agent 通过开发工具包装器启动子 Runner，并接收执行结果" data-thumbnail="/images/game-agent-evolution/05-agent-handoff.svg" data-sub-html="<h2>图 4：开发子任务的执行与结果返回</h2>">
        
    </a><figcaption class="image-caption">图 4：开发子任务的执行与结果返回</figcaption>
    </figure>
<p>我们后来还去掉过主 Agent 的 Bash 入口，把编码进一步交给开发 Agent。这样工具权限更加集中，但主 Agent 需要通过子任务才能检查工程或完成一次小修改。</p>
<p>代码、地图配置和素材接入虽然属于不同专业，却经常共同完成同一个需求。拆开以后，各个 Agent 都需要重新理解相关部分，主 Agent 再整合局部结果。交接遗漏约束、结果不足以判断是否完成，都是需要处理的问题。</p>
<p>因此，角色名称只能说明知识分工，还不足以确定执行边界。是否能够并行、是否需要隔离权限、结果是否容易交接，都需要结合实际任务判断。</p>
<h2 id="3-主-agent-与-skills">3. 主 Agent 与 Skills</h2>
<p>第二版将编码能力放回主 Agent，同时通过 Skills 按需提供专业方法。</p>
<p>这里的 Skill 是完成某类工作需要的方法、说明和辅助资源。加载 Skill 后，相关知识进入当前工作上下文；启动子 Agent 则会建立一段独立执行。两者有不同的用途。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/06-skills.svg" title="主 Agent 在同一工程中执行，按需读取专业知识与工具说明" data-thumbnail="/images/game-agent-evolution/06-skills.svg" data-sub-html="<h2>图 5：通过 Skills 提供专业知识</h2>">
        
    </a><figcaption class="image-caption">图 5：通过 Skills 提供专业知识</figcaption>
    </figure>
<p>处理地图时加载地图格式与操作方法，接入素材时加载对应的资源使用说明。仍然由主 Agent 理解需求、读取工程、修改文件，再检查修改结果。</p>
<p>这样，配置修改和代码修改可以在同一上下文里连续完成。用户刚确认的限制也保留在原会话中，减少了重复组织子任务输入的工作。</p>
<p>独立素材生产、封面制作等任务仍然保留自己的执行入口。这类工作有相对明确的输入和输出，外部执行结束后，可以通过任务编号与产物引用交回主流程。</p>
<p>调整后的分工可以归纳为：专业知识通过 Skill 提供，需要独立权限、生命周期或并行执行的工作，再使用独立任务。这一划分仍然要通过任务完成情况验证。</p>
<h2 id="4-工具接口与模型上下文">4. 工具接口与模型上下文</h2>
<h3 id="41-工具集的适配">4.1. 工具集的适配</h3>
<p>模型接入后，需要同时提供它能够使用的工具。工具的名称、参数、执行行为和结果格式，共同组成调用协议。</p>
<p>我们逐步对齐了 Codex 和 Claude Code 两类基础工具，并按所选模型装配相应工具集。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/07-toolsets.svg" title="自研 Runtime 为不同模型装配相应的文件和命令工具" data-thumbnail="/images/game-agent-evolution/07-toolsets.svg" data-sub-html="<h2>图 6：自研 Runtime 中的工具适配</h2>">
        
    </a><figcaption class="image-caption">图 6：自研 Runtime 中的工具适配</figcaption>
    </figure>
<p>这里仍由自研 Runtime 组织模型循环。采用 Codex 风格的工具接口，并不表示已经由原生 Codex 执行任务。</p>
<p>以命令工具为例，启动测试以后可能出现三种结果：</p>
<ol>
<li>命令已结束，返回输出与退出码；</li>
<li>命令仍在运行，返回执行会话编号，之后继续读取输出；</li>
<li>执行出错，返回能够定位原因的信息。</li>
</ol>
<p>后续还需要通过原会话发送输入、中断命令，以及在 Runtime 重启后重新查询。若命令仍在运行，工具却只返回一段文本，模型就无法判断是等待、重试还是继续修改。</p>
<p>文件工具也一样。读取范围、补丁格式和修改失败的反馈，需要与实际工作方式对应。对齐工具名称之后，还需要逐项检查这些行为。</p>
<h3 id="42-上下文组织">4.2. 上下文组织</h3>
<p>每次模型请求包含本轮指令、工具定义、历史消息，以及新的工具结果。运行时间增长后，需要压缩较早的历史，并保留后续执行所需的信息。</p>
<p>模型切换时，还要重新核对推理参数、工具调用与结果的对应关系，以及缓存条件。例如，把已有历史作为稳定前缀继续追加，和每轮重新排列同一批内容，可能有不同的缓存效果；实际命中情况仍以服务端返回的使用信息为准。</p>
<p>这些内容通常归入 Harness，也就是模型外层组织工具、上下文和执行环境的部分。模型能力变化后，Prompt、工具协议和上下文组织需要一起检查，不能只修改模型名称。</p>
<h2 id="5-验证与修复流程">5. 验证与修复流程</h2>
<h3 id="51-固定检查流程">5.1. 固定检查流程</h3>
<p>我们曾增加一套较完整的质量流程，将用户意图、素材、运行证据、检查和发布串起来。对于“玩法保持不变，场景改成雪地”这类需求，流程需要一直保留修改目标与不变条件。</p>
<p>其中还包含固定检查多个视口，以及开始、胜利、失败、重试等场景。检查得到问题后，再触发修复和重新部署。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/10-quality.svg" title="固定检查与修复循环，以及按需求组织的验证过程" data-thumbnail="/images/game-agent-evolution/10-quality.svg" data-sub-html="<h2>图 7：验证与修复流程的调整</h2>">
        
    </a><figcaption class="image-caption">图 7：验证与修复流程的调整</figcaption>
    </figure>
<p>问题出在检查范围与当前需求不一定一致。用户只要求手机竖屏时，其他方向的检查可能触发额外修改；每次修改又需要重新运行和部署。实际案例中出现过消耗和等待增加，以及围绕检查结果反复修补的情况。这里是个案观察，不据此给出整体效率结论。</p>
<h3 id="52-按任务确定验证范围">5.2. 按任务确定验证范围</h3>
<p>调整后，我们把素材使用、工程接入和预算安排提前到部署前处理。验证仍然保留，但检查范围根据当前需求确定，修复也结合问题影响与剩余预算决定。</p>
<p>以手机竖屏游戏为例，本轮优先检查竖屏的核心游玩路径。其他视口的问题可以记录，但是否必须在本轮修复，需要根据交付范围判断。</p>
<p>这样调整之后，需要保留的记录包括：本轮验收条件、执行过的检查、检查结果，以及决定继续修改或结束的依据。减少固定步骤之后，仍然能够说明最终交付经过了哪些验证。</p>
<h2 id="6-异步任务与恢复">6. 异步任务与恢复</h2>
<h3 id="61-几种不同的执行状态">6.1. 几种不同的执行状态</h3>
<p>游戏能力接入 Go 后端以后，会话、执行记录和费用由运行时及业务服务管理。一次游戏生成又包含多个异步过程，不能用单一状态概括。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/08-progress.svg" title="模型运行、素材生成、版本发布与客户端接收具有独立进度" data-thumbnail="/images/game-agent-evolution/08-progress.svg" data-sub-html="<h2>图 8：一次游戏生成中的不同进度，横向长度不表示实际耗时</h2>">
        
    </a><figcaption class="image-caption">图 8：一次游戏生成中的不同进度，横向长度不表示实际耗时</figcaption>
    </figure>
<p>模型结束一轮输出时，素材可能仍在生成。素材完成以后，需要接入工程；游戏发布完成以后，客户端还需要收到版本信息。系统应当保存这些状态及其所属任务，再组合成用户看到的进度。</p>
<p>否则，模型的一次结束可能被误用为整个任务成功，外部任务已经完成也可能因为消息丢失而一直显示等待。</p>
<h3 id="62-恢复记录">6.2. 恢复记录</h3>
<p>恢复一项工作时，需要读取它所对应的持久记录。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/09-recovery.svg" title="上下文、异步任务、事件与用户轮次的恢复记录" data-thumbnail="/images/game-agent-evolution/09-recovery.svg" data-sub-html="<h2>图 9：任务恢复所使用的记录</h2>">
        
    </a><figcaption class="image-caption">图 9：任务恢复所使用的记录</figcaption>
    </figure>
<p>上下文检查点保存整理过的历史和覆盖位置，用于继续模型执行。素材任务记录保存外部任务编号与产物引用，用于查询原操作。事件 Outbox 保存待发送事件，用于失败后补发。用户轮次与运行记录关联，使恢复的结果仍然属于原来的请求。</p>
<p>素材是否成功、费用是否发生、版本是否创建，由对应业务记录确认。模型摘要可以帮助继续理解任务，但不能作为这些操作已经发生的唯一依据。</p>
<p>事件补发也不应重新执行一次素材生成或发布。接收方按事件编号去重，业务操作则使用自己的任务编号或幂等编号，两者分别处理。</p>
<h2 id="7-原生执行引擎的接入">7. 原生执行引擎的接入</h2>
<p>自研 Runtime 要持续维护模型循环、工具协议和上下文组织。另一种选择是复用原生编码引擎，把业务工具接到已有的执行环境中。</p>
<p>Codex 提供程序化接入方式，Claude Agent SDK 也提供相应的执行能力。<a href="https://learn.chatgpt.com/docs/codex-sdk" target="_blank" rel="noopener noreffer ">Codex SDK</a>、<a href="https://code.claude.com/docs/en/agent-sdk/overview" target="_blank" rel="noopener noreffer ">Claude Agent SDK 文档</a>。</p>
<p>我们在这一阶段开始尝试把原生 Codex 接入新的 Runtime，通过适配层连接现有业务服务。</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/11-runtime-boundary.svg" title="自研执行链与原生引擎方案共用业务服务" data-thumbnail="/images/game-agent-evolution/11-runtime-boundary.svg" data-sub-html="<h2>图 10：自研运行时与原生引擎的职责划分</h2>">
        
    </a><figcaption class="image-caption">图 10：自研运行时与原生引擎的职责划分</figcaption>
    </figure>
<p>引擎负责读取工程、调用基础工具、修改代码和执行验证。平台继续保存项目、会话和运行关系；素材、费用、版本与交付结果仍由业务服务负责。</p>
<p>适配层需要处理事件转换、取消、继续会话，以及产物接入。即使引擎已经在文件系统中生成了一张图片或构建了一份游戏，平台仍需要登记产物、关联项目和版本，才能进入产品的交付流程。</p>
<p>本文最初整理时，原生引擎接入仍在开发验证阶段。部分原生执行和素材任务完成了限定范围的开发环境验收，项目交互、完整交付和恢复仍在推进。这些结果只能说明已验证的范围，不能作为整套迁移完成的依据。</p>
<h2 id="8-模型与流程的对照验证">8. 模型与流程的对照验证</h2>
<p>上面的调整同时涉及模型、Prompt、工具和编排。若把它们一起修改，只能观察到最终结果变化，很难确定是哪一部分起了作用。</p>
<p>后续可以固定一组游戏需求和验收条件，分步骤比较：</p>
<figure><a class="lightgallery" href="/images/game-agent-evolution/12-follow-model.svg" title="固定任务后分别比较模型、执行环境和流程变化" data-thumbnail="/images/game-agent-evolution/12-follow-model.svg" data-sub-html="<h2>图 11：对照验证的组织方法，尚不表示实验已全部完成</h2>">
        
    </a><figcaption class="image-caption">图 11：对照验证的组织方法，尚不表示实验已全部完成</figcaption>
    </figure>
<p>先保存现有模型与流程的结果，再接入兼容的新模型；之后调整工具和上下文；最后每次简化一部分编排。比较旧环境时，应先满足新模型的接口与工具兼容要求。</p>
<p>各组使用相同的记录项：需求完成情况、核心玩法验证、交付版本、总费用、等待时间，以及失败后的恢复结果。把重复素材任务、无效部署和额外修复计入成本，才能判断一次有效交付实际消耗多少资源。</p>
<p>多 Agent、固定检查和独立评估都可以放进这套比较中。某一步删除后，若结果在约定范围内保持稳定，同时费用与等待下降，才有依据继续简化；出现质量下降，则需要检查该步骤在当前任务中承担了什么作用。</p>]]></description></item></channel></rss>