<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>AI Native - Tag - SaiyuDoc</title><link>https://www.wangsaiyu.com/tags/ai-native/</link><description>AI Native - Tag - SaiyuDoc</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>panicwang@outlook.com (王赛宇)</managingEditor><webMaster>panicwang@outlook.com (王赛宇)</webMaster><lastBuildDate>Wed, 14 Oct 2026 14:49:37 +0800</lastBuildDate><atom:link href="https://www.wangsaiyu.com/tags/ai-native/" rel="self" type="application/rss+xml"/><item><title>AI Native 开发环境：让 Agent 完成开发、发布与排错</title><link>https://www.wangsaiyu.com/posts/engineering/ai-native-development-environment/</link><pubDate>Wed, 14 Oct 2026 14:49:37 +0800</pubDate><author>王赛宇</author><guid>https://www.wangsaiyu.com/posts/engineering/ai-native-development-environment/</guid><description><![CDATA[<p>让 Agent 修改代码，通常很快就能得到结果。但让它把一个需求做到上线，过程会长得多：服务在哪里创建，接口怎样接入，代码怎样发布，测试请求会打到哪个环境，失败以后又该去哪里找原因。</p>
<p>这些步骤过去常常依靠开发者对团队系统的熟悉程度。Agent 接手以后，原来藏在操作习惯里的知识，需要变成它能够找到的说明、能够调用的工具，以及能够判断的执行结果。本文以我们的后端开发环境为例，介绍这条链路如何组织。</p>
<h2 id="1-从一次服务开发开始">1. 从一次服务开发开始</h2>
<p>假设需要增加一项异步处理能力：客户端提交任务，服务返回任务标识；任务在后台执行，客户端随后查询状态和结果。这里用简化的任务接口贯穿全文，便于说明开发、测试和排错之间的关系。</p>
<p>如果只看业务代码，这个需求可以拆成提交、执行和查询几个部分。但要交付它，Agent 还需要回答一串问题：已有服务能否承接，是否需要新的存储，如何部署一个可调试版本，以及怎样确认后台任务确实完成。</p>
<p>这些问题对应着不同的系统。代码仓库保存实现和接口定义，流水线生成可部署的制品，研发平台维护服务与发布记录，运行环境提供实际实例，日志和数据库则保存执行过程与业务状态。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/01-delivery-loop.svg" title="Agent 从代码仓库进入流水线和运行环境，再通过接口、日志和业务状态判断结果" data-thumbnail="/images/ai-native-development-environment/01-delivery-loop.svg" data-sub-html="<h2>图 1：一次开发任务涉及的系统与反馈路径</h2>">
        
    </a><figcaption class="image-caption">图 1：一次开发任务涉及的系统与反馈路径</figcaption>
    </figure>
<p>图中的关键是反馈路径。Agent 发起测试后，应当拿到实际响应；请求失败后，应当能沿着这次请求找到日志与状态。否则它只能不断修改代码，却无法判断改动是否解决了问题。</p>
<p>这里讨论的 AI Native 开发环境，就是围绕这条工作链路组织开发设施。下面从服务创建开始，逐步补齐其中的环节。</p>
<h2 id="2-新服务怎样进入开发环境">2. 新服务怎样进入开发环境</h2>
<h3 id="21-先找到业务归属">2.1. 先找到业务归属</h3>
<p>Agent 接到需求后，直接生成一个新服务很容易。更早需要确定的是：这项能力是否已经有负责的服务。</p>
<p>例如，任务状态已经由任务服务维护，查询接口也应当从这里读取。如果为了增加一个接口，又在调用方建立一份任务状态，两个服务就都能修改同一件事情。后续遇到重试、取消和状态同步时，复杂度会迅速增加。</p>
<p>我们在仓库中维护能力归属的说明，把业务职责、接口定义和客户端入口关联起来。Agent 先据此找到候选服务，再读取实际接口和实现。同步调用使用已有的生成客户端，异步协作沿用已有的事件协议。只有在状态归属、故障隔离或独立扩展上确实出现新边界时，才创建服务。</p>
<p>这份说明更接近索引。它帮助 Agent 找到代码，具体接口仍以当前定义为准。业务演进以后，只更新一篇概括性的文档而不核对实现，也会把 Agent 引向错误的位置。</p>
<h3 id="22-模板需要包含哪些内容">2.2. 模板需要包含哪些内容</h3>
<p>确定要创建服务后，下一个问题是让它符合整个仓库的运行约定。</p>
<p>我们使用统一的 Kitex 服务模板。Kitex 是内部 RPC 框架，服务通过接口描述文件定义请求、响应和方法，再生成服务端与客户端代码。模板最初只包含健康检查，业务逻辑按需增加。</p>
<p>模板同时放入配置加载、服务发现、请求上下文传递、日志、指标和优雅退出等基础能力。这样，后续代码调用其他服务时，环境标记和追踪信息能够继续向下传；实例停止时，也能按统一方式结束正在处理的请求。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/02-service-registration.svg" title="新服务分别完成代码侧接入与平台登记，发布时由服务名和构建绑定关联" data-thumbnail="/images/ai-native-development-environment/02-service-registration.svg" data-sub-html="<h2>图 2：代码生成、平台登记和首次发布之间的关系</h2>">
        
    </a><figcaption class="image-caption">图 2：代码生成、平台登记和首次发布之间的关系</figcaption>
    </figure>
<p>服务目录生成以后，还需要接入仓库的构建工作区、代码生成、监控清单和模块流水线。这些内容不都在服务自己的目录里，创建指引必须把它们列出来。否则很容易得到一个本地可以编译、流水线却找不到的服务。</p>
<h3 id="23-代码存在以后还要登记运行目标">2.3. 代码存在以后，还要登记运行目标</h3>
<p>代码模板解决了服务怎样运行，研发平台还需要知道它是谁、允许部署到哪里。</p>
<p>平台登记服务名称、用途、构建绑定和环境声明。构建绑定把逻辑服务与对应的代码位置、流水线和制品仓库关联起来；环境声明则描述这个服务有哪些可发布目标。二者分别回答“构建什么”和“发布到哪里”。</p>
<p>因此，创建代码、登记服务、创建运行实例是三个不同的动作。服务登记完成后可以出现在目录中，此时仍可能没有任何实例。流水线完成构建并提交首次发布后，平台才按受信任的运行模板创建相应资源。</p>
<p>数据库、缓存和公网入口也需要各自的申请过程。创建一个空服务，不应当顺带开出它暂时用不到的资源。对 Agent 来说，这个区分还会直接影响后续判断：目录中能够查到服务，只证明登记存在；能否调用，需要等实例与访问路径都准备好。</p>
<h2 id="3-缩短每一轮构建与发布">3. 缩短每一轮构建与发布</h2>
<h3 id="31-先把流水线组织成明确的依赖关系">3.1. 先把流水线组织成明确的依赖关系</h3>
<p>服务写好后，Agent 会进入反复修改和验证的过程。每一轮都要等待构建与部署，等待时间会累积成整个需求的交付时间。</p>
<p>我们的流水线先根据变更确定受影响的模块，再生成相应的构建和部署任务。业务目录映射到它所属的服务；共享库、代码生成与公共契约的变化，通过依赖声明触发使用它们的模块。这里需要维护真实的依赖关系，只看文件所在目录会漏掉下游影响。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/03-pipeline-dependencies.svg" title="变更分析生成受影响模块的检查与构建任务，通过后登记制品并请求平台部署" data-thumbnail="/images/ai-native-development-environment/03-pipeline-dependencies.svg" data-sub-html="<h2>图 3：按受影响模块生成任务，按依赖关系推进发布；图中两类服务仅为示意</h2>">
        
    </a><figcaption class="image-caption">图 3：按受影响模块生成任务，按依赖关系推进发布；图中两类服务仅为示意</figcaption>
    </figure>
<p>各模块可以独立构建。一个服务的部署等待它所需的检查和制品；如果多个服务必须配套发布，则明确声明部署顺序。GitLab 的 <code>needs</code> 可以表达任务依赖，使已经满足条件的任务提前开始，避免等待没有关系的任务。<a href="https://docs.gitlab.com/ci/yaml/needs/" target="_blank" rel="noopener noreffer ">GitLab 文档</a>介绍了这种组织方式。</p>
<p>我们还把动态流水线展开为直接执行的任务，减少只负责继续创建子流水线的中间层。它不会缩短编译本身，但可以减少调度和等待，也更容易从结果中找到真正失败的服务。</p>
<h3 id="32-缓存究竟复用了什么">3.2. 缓存究竟复用了什么</h3>
<p>只减少构建范围还不够。如果每次构建都重新下载依赖、重新编译没有变化的包，仍然会浪费大量时间。</p>
<p>这里需要区分三类东西。第一类是项目源代码和本次输出，必须对应本次构建输入；第二类是依赖下载与编译缓存，可以按内容复用；第三类是已经生成的不可变制品，可以在满足发布条件后进入其他环境。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/04-build-cache.svg" title="不同构建复用同一 Runner 上的 BuildKit 缓存，远端保存可导出的构建层，部署复用不可变制品" data-thumbnail="/images/ai-native-development-environment/04-build-cache.svg" data-sub-html="<h2>图 4：构建缓存与制品复用发生在不同位置</h2>">
        
    </a><figcaption class="image-caption">图 4：构建缓存与制品复用发生在不同位置</figcaption>
    </figure>
<p>我们的 Runner，也就是执行流水线任务的机器，会复用持久的 BuildKit 构建器。Go 模块、编译缓存和 Node 依赖下载缓存挂载在其中，下一次任务能够继续使用。Dockerfile 中依赖声明与经常变化的业务源码分开复制，可以减少缓存失效范围。<a href="https://docs.docker.com/build/cache/optimize/" target="_blank" rel="noopener noreffer ">Docker 的缓存说明</a>解释了缓存层与缓存挂载的区别。</p>
<p>本机缓存和远端缓存也有边界。我们将可导出的构建层保存到镜像仓库，便于更换 Runner 后恢复；缓存挂载中的包下载和编译数据，则仍依赖对应的构建器。换一台机器以后，不能因为远端存在构建缓存，就假定所有本机缓存都已恢复。</p>
<p>工作区同样按用途拆开。构建任务需要业务源码，部署与汇总任务往往只需要少量脚本和声明，可以使用稀疏检出。旧源码、旧输出和本次制品必须正确清理；不能为了保留缓存，顺带把上一次构建结果当成本次输入。</p>
<h3 id="33-用一次实际构建定位最长路径">3.3. 用一次实际构建定位最长路径</h3>
<p>做过这些优化以后，流水线仍然可能慢。2026 年 9 月的一次发布，从流水线创建到最后一个任务完成，约为 6 分 50 秒。沿着实际完成路径拆开，时间分布如下。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/05-pipeline-timing.svg" title="一次发布约 410 秒，其中构建 317.1 秒、部署 57.7 秒；构建内检查路径为 257.4 秒" data-thumbnail="/images/ai-native-development-environment/05-pipeline-timing.svg" data-sub-html="<h2>图 5：一次流水线的实际耗时；上方时间条按比例绘制，下方为构建内的并行关系</h2>">
        
    </a><figcaption class="image-caption">图 5：一次流水线的实际耗时；上方时间条按比例绘制，下方为构建内的并行关系</figcaption>
    </figure>
<table>
  <thead>
      <tr>
          <th>环节</th>
          <th style="text-align: right">耗时</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>创建至开始构建</td>
          <td style="text-align: right">22.8 秒</td>
      </tr>
      <tr>
          <td>镜像构建任务</td>
          <td style="text-align: right">317.1 秒</td>
      </tr>
      <tr>
          <td>构建后的排队</td>
          <td style="text-align: right">1.0 秒</td>
      </tr>
      <tr>
          <td>部署任务</td>
          <td style="text-align: right">57.7 秒</td>
      </tr>
      <tr>
          <td>汇总与收尾</td>
          <td style="text-align: right">11.2 秒</td>
      </tr>
  </tbody>
</table>
<p>其中，构建任务约占总时间的 77%。进一步查看构建日志，最慢的是 257.4 秒的检查路径：代码规范检查约 30 秒，独立类型检查约 51 秒，数据库测试准备与测试执行约 176 秒。</p>
<p>这次依赖安装已经命中缓存，源码准备也只有约 3 秒。继续压缩依赖下载，无法解决主要等待。测试配置为了避免共享数据库中的任务被其他测试消费，关闭了文件间并行；这个设置同时影响到了不访问数据库的纯逻辑测试。测试框架的阶段统计又显示，模块导入占用了大量被追踪的执行时间。</p>
<p>由此可以确定下一步的优化方向：把纯逻辑测试与共享数据库测试分开，核对模块加载开销；需要并行运行数据库测试时，先解决数据和后台 worker 的隔离。这些是这次诊断提出的后续工作，不能把预期节省的时间写成已经取得的结果。<a href="https://vitest.dev/guide/improving-performance" target="_blank" rel="noopener noreffer ">Vitest 性能文档</a>也建议先分析各阶段耗时，再选择优化方式。</p>
<p>还有一个容易算错的地方：当时的编译与进程验收已经和检查并行执行，且更早完成。将所有任务的耗时相加，会重复计算并行部分。决定等待时间的是依赖链中最后完成的路径，优化一个提前完成的任务，不一定能让整条流水线更早结束。</p>
<h3 id="34-后续环境复用已经验证的制品">3.4. 后续环境复用已经验证的制品</h3>
<p>测试环境验证完成后，同一份不可变制品继续进入预发布和生产环境。这里的制品，是已经构建并登记的镜像等部署产物；摘要用来确认内容没有变化。</p>
<p>在上述案例中，后续两个环境的构建任务分别用了 5.9 秒和 6.6 秒，日志确认它们复用了已有镜像。这两个数字只包含对应的构建任务，部署、验证和观察仍然需要时间。</p>
<p>复用带来的另一个作用是版本一致：已经验证的内容与随后发布的内容可以对应起来。如果每到一个环境都重新构建，还需要解释新的产物与上一环境验证的产物是否相同。配置也是部署输入，我们的标准服务将配置随镜像交付，修改配置同样需要重新构建并发布。</p>
<p>至此，Agent 能够较快地获得一个可运行版本。接下来还要解决一个问题：它发出的测试请求，是否真的在验证这个版本。</p>
<h2 id="4-给并行开发提供可确定的测试环境">4. 给并行开发提供可确定的测试环境</h2>
<p>多个 Agent 共用一个开发环境时，实例更新会相互覆盖。一个 Agent 刚刚发布，另一个 Agent 又替换了下游服务；前者随后看到的响应，已经对应另一组代码。</p>
<p>我们为开发中的变更提供个人环境。这里的“个人”指独立的运行目标和环境身份，同一开发者的不同任务也需要按实际环境区分。测试请求携带环境信息，入口、RPC 客户端和异步消息继续传递这个信息，使后续调用进入相应的服务实例。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/06-test-environment.svg" title="测试请求经共享入口进入个人任务服务和个人 worker，环境信息沿 RPC 和消息传递；数据仍需单独隔离" data-thumbnail="/images/ai-native-development-environment/06-test-environment.svg" data-sub-html="<h2>图 6：运行路由的隔离与测试数据的隔离</h2>">
        
    </a><figcaption class="image-caption">图 6：运行路由的隔离与测试数据的隔离</figcaption>
    </figure>
<p>对异步任务来说，入口请求返回以后工作还没有完成。环境信息需要进入消息，并在消费时恢复；消费者和队列的组织也要与路由约定一致。否则请求虽然进入了个人服务，任务仍可能被共享环境的 worker 执行。</p>
<p>并非每个依赖都需要复制一套。允许复用的依赖可以按明确规则访问共享环境，但本次正在验证的服务应当确认有对应实例，不能在目标缺失时无声地切到另一份实现。</p>
<p>运行环境隔离也不自动带来数据隔离。两个实例如果连接同一个数据库，使用同一批任务记录，依然可能相互影响。测试需要专用账号或数据范围；后台扫描与消费任务时，也要限制在相应范围内。涉及全表扫描、共享队列等场景，可能需要独立的库、队列或消费者配置。</p>
<p>因此，一次测试至少要明确两件事：请求经过哪些版本，以及它会读写哪一组数据。只在地址中换一个环境参数，还不足以回答这两个问题。</p>
<h2 id="5-每轮上线要验证到哪一步">5. 每轮上线要验证到哪一步</h2>
<h3 id="51-从接口定义生成调用入口">5.1. 从接口定义生成调用入口</h3>
<p>环境准备好后，Agent 需要构造请求。如果每次都临时拼路径、参数和鉴权头，测试本身也容易出错。</p>
<p>我们把接口定义作为调用代码的来源。网关接口生成 TypeScript 客户端，内部 RPC 使用对应的调用工具和测试用例。Agent 可以从修改过的方法找到已有测试，沿用其参数、身份和环境选择方式。</p>
<p>这解决了“怎样正确地调用”，业务是否正确仍要由测试说明。生成的契约探针能够检查接口定义和请求结构，一次预期的参数拒绝也能验证边界行为；它们各自证明的范围都有限。</p>
<h3 id="52-为改动选择有业务含义的断言">5.2. 为改动选择有业务含义的断言</h3>
<p>回到任务示例。提交接口返回成功，可能仅表示任务已受理；如果要验证整个功能，还需要查询任务状态，并检查最终结果。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/07-business-test.svg" title="测试使用专用身份提交任务，按返回标识查询，在限定时间内确认完成并检查结果，最后清理测试数据" data-thumbnail="/images/ai-native-development-environment/07-business-test.svg" data-sub-html="<h2>图 7：异步任务的业务测试；接口与状态为简化示意</h2>">
        
    </a><figcaption class="image-caption">图 7：异步任务的业务测试；接口与状态为简化示意</figcaption>
    </figure>
<p>一个有意义的用例需要把预期写清楚：提交结果属于当前测试账号，查询返回同一个任务，任务在限定时间内进入预期终态，输出满足业务条件。失败和取消是不同结果，等待超时也需要单独报告，不能一直轮询下去。</p>
<p>如果这次修改的是任务权限，就补充另一个账号不能读取该任务的验证；如果修改的是重试逻辑，就验证相同请求不会产生重复任务。测试范围随着实际变化扩大。普通处理逻辑修复，可以先运行受影响的用例；只有接口定义变化时，才需要重新生成对应客户端和契约检查。</p>
<p>我们不会要求每次改几行业务代码，都把全部服务、所有环境和所有测试重新跑一遍。发布前的必要检查必须保留，额外验证则应当针对本次可能破坏的行为。这样既能较快获得反馈，也能知道每项检查为什么存在。</p>
<p>测试数据本身也是测试设计的一部分。创建的数据需要能够识别并清理，清理失败要留有记录。涉及扣费、发送通知或删除业务对象的接口，应当有专门的测试条件与授权，不能直接把生产用户当作测试样本。</p>
<h3 id="53-部署完成与线上验证分开记录">5.3. 部署完成与线上验证分开记录</h3>
<p>本地测试和开发环境测试通过以后，还需要确认生产环境运行的是预期内容。</p>
<p>平台发布检查会核对发布目标、声明的镜像和实例就绪情况。我们的 <code>release verify</code> 还会检查这条发布是否仍是当前发布，避免拿已经被替换的旧记录证明当前状态。它的覆盖范围不包括业务接口、日志、监控，也不等于逐个容器的实际镜像摘要核验。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/08-release-verification.svg" title="发布受理、平台执行、部署核对和业务验证各自产生不同的结果，客户端超时后查询原操作" data-thumbnail="/images/ai-native-development-environment/08-release-verification.svg" data-sub-html="<h2>图 8：发布状态与业务验收分别推进，超时后先确认原操作</h2>">
        
    </a><figcaption class="image-caption">图 8：发布状态与业务验收分别推进，超时后先确认原操作</figcaption>
    </figure>
<p>因此，一轮上线至少要把部署结果和一次针对改动的真实请求接起来。请求需要使用正确的入口、身份和环境，检查业务字段与结果；发现异常时，再扩大到日志、监控或实际容器版本的核对。对异步任务，线上验证同样要继续观察任务终态，提交成功还不够。</p>
<p>结果中可以分别记录“部署已核对”“提交接口通过”“任务执行失败”。这些状态放在一起，Agent 才知道下一步是检查发布，还是进入业务排错。缺失的检查保留为未验证，不应当被一个总的成功状态覆盖。</p>
<p>还有一种情况是客户端等待超时。发布可能仍在平台执行，重复创建新的发布会使判断更复杂。我们保留原操作标识，先查询原发布或按同一意图重试；平台根据幂等记录恢复结果。命令停止等待，也不意味着服务端任务已经取消。</p>
<h2 id="6-验证失败后怎样找到原因">6. 验证失败后怎样找到原因</h2>
<h3 id="61-先保留这次失败的上下文">6.1. 先保留这次失败的上下文</h3>
<p>假设提交接口正常，但任务迟迟没有完成。Agent 如果只得到“测试失败”，很难确定应该从哪里开始。</p>
<p>测试输出需要保留环境、请求时间、接口、任务标识、预期结果和实际结果。能够取得请求追踪标识时，也应当一并保留。这些字段把一次失败限制到具体调用与时间范围，后续查询才能有明确目标。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/09-diagnosis.svg" title="失败请求提供时间、环境和任务信息，日志解释执行过程，数据库确认业务状态，按发现的问题继续查队列或外部结果" data-thumbnail="/images/ai-native-development-environment/09-diagnosis.svg" data-sub-html="<h2>图 9：从一次失败请求逐步缩小排查范围</h2>">
        
    </a><figcaption class="image-caption">图 9：从一次失败请求逐步缩小排查范围</figcaption>
    </figure>
<p>同步接口报错时，先沿请求查入口和被调用服务的日志。任务停留在某个状态时，先读取负责该状态的服务数据库，再根据记录中的执行阶段查相应日志、队列或租约。错误率上升、延迟变高这类整体问题，则更适合先看指标趋势，再选取具体请求分析。</p>
<p>这些入口不是每次都必须查一遍。它们分别回答不同的问题，下一次查询由上一份结果决定。</p>
<h3 id="62-日志数据库和指标各自说明什么">6.2. 日志、数据库和指标各自说明什么</h3>
<p>日志记录服务在某个时刻做了什么。通过任务标识，可以看到入口是否接收、消息是否发送、worker 是否开始执行，以及下游调用在哪里失败。跨服务使用一致的追踪字段，比单纯增加日志数量更有用。</p>
<p>数据库保存业务状态。例如任务记录已经存在，但仍处于等待状态，排查就应当继续进入分发与消费；任务已经标记完成，客户端却一直得到旧状态，则需要核对查询条件、账号范围、缓存或读取路径。</p>
<p>查库应当使用状态所属服务的数据库和明确条件。下面是说明查询范围的示意 SQL，表名与字段并非项目实际结构：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">status</span><span class="p">,</span><span class="w"> </span><span class="n">updated_at</span><span class="p">,</span><span class="w"> </span><span class="n">error_code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">task_records</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">task_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">:</span><span class="n">task_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">AND</span><span class="w"> </span><span class="n">owner_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">:</span><span class="n">test_user_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>参数来自本次测试结果。只读取排查需要的列，限制结果数量，并设置查询超时。我们的生产查询入口会设置只读事务与超时，但包装脚本的限制和数据库账号本身的权限仍是两件事；如果底层账号拥有写权限，只读规则还需要在凭据与服务端授权层继续收紧。</p>
<p>指标更适合判断影响范围：错误是否集中在某个接口，延迟是否从上线后开始上升，任务积压是否持续增加。没有采样、采集失败和数值为零要分别展示，Agent 才不会把“没有读到数据”解释成“没有问题”。</p>
<h3 id="63-修复以后回到原来的失败用例">6.3. 修复以后，回到原来的失败用例</h3>
<p>假设日志和状态共同表明，任务停在了消费者读取之后、业务执行之前。Agent 修改相应代码后，应当首先重跑原来的任务用例，确认同样的输入能够进入预期终态，再补充与修复直接相关的边界验证。</p>
<p>保留原用例的输入条件、环境和断言，可以让前后结果形成可比较的记录。若每次排错都换一个账号、另一套环境和另一种请求，即使后来成功，也很难说明原问题已经解决。</p>
<h2 id="7-权限怎样贯穿整个过程">7. 权限怎样贯穿整个过程</h2>
<h3 id="71-把员工身份带到操作入口">7.1. 把员工身份带到操作入口</h3>
<p>前面的步骤要连起来，Agent 必须能够读取必要信息、调用测试接口和发起已经授权的发布。但如果每个开发环境都直接持有集群、云资源和生产数据库的管理凭据，权限也会随工具一起扩散。</p>
<p>我们的做法是让员工通过统一身份登录，Agent 使用相应的员工工具入口。发布命令提交服务、环境和已经登记的制品，由研发平台解析实际运行目标并执行。CI 负责检查、构建、登记与请求发布，运行资源的选择和修改集中在平台。</p>
<figure><a class="lightgallery" href="/images/ai-native-development-environment/10-permission-boundaries.svg" title="Agent 通过员工身份调用平台，CI 使用受限构建身份；平台校验操作并访问运行资源，数据库查询走单独受控入口" data-thumbnail="/images/ai-native-development-environment/10-permission-boundaries.svg" data-sub-html="<h2>图 10：身份、操作授权与基础设施凭据的分工</h2>">
        
    </a><figcaption class="image-caption">图 10：身份、操作授权与基础设施凭据的分工</figcaption>
    </figure>
<p>这样，Agent 不需要根据集群名称猜测部署目标，也不需要为了发布一个服务取得任意集群操作权限。平台保存服务与目标的对应关系，并在服务端检查调用者、制品来源、环境门禁和操作状态。</p>
<p>真实操作人也要随请求进入审计记录。流水线机器人可以执行操作，但记录应当能够回答是谁发起、发布了什么、进入哪个环境，以及最后得到什么结果。代码作者、最近一次修改者和本次发布发起者不能混为一谈。</p>
<h3 id="72-不同操作采用不同边界">7.2. 不同操作采用不同边界</h3>
<p>权限如果只分成“普通用户”和“管理员”，往往会遇到两个问题：日常工作频繁等待管理员，或者为了让工作继续，把过多权限一次性开放。</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>
  </tbody>
</table>
<p>付费资源仍然经过申请、审批和开通。报价成功只得到成本信息，申请成功只代表申请已经受理，后续步骤各自留下状态。Agent 可以推进它有权完成的部分，并明确指出下一步需要什么条件。</p>
<p>服务端必须执行这些检查。页面隐藏按钮、Skill 写一句“不要越权”，都不能替代接口授权。数据库也一样：把查询工具描述为只读，并不能自动降低其底层账号权限。</p>
<p>平台能力和当前任务授权还需要分开。员工身份具备某项操作权限，只代表系统允许这个身份发起操作；Agent 在当前任务中是否应当执行，还取决于用户授权的服务、环境与动作。只读诊断任务中发现了解决办法，也不能据此直接修改生产数据。</p>
<h2 id="8-让-agent-找得到接得上这些能力">8. 让 Agent 找得到、接得上这些能力</h2>
<p>当工具逐渐齐全，新的问题是 Agent 怎样知道什么时候使用哪一个。把全部说明塞进每次对话，会让上下文很快被大量细节占满；只提供一组工具名称，又不足以理解它们的前置条件和结果含义。</p>
<p>我们把稳定的仓库约束放在 <code>AGENTS.md</code>，把具体工作拆成服务创建、接口测试、发布和线上排查等 Skill。每份 Skill 提供入口和判断顺序，再指向更详细的说明与现有脚本。例如，服务创建指引先要求确认业务归属；接口测试指引根据网关或内部 RPC 选择测试入口；排查指引根据错误表现选择日志、状态或指标。</p>
<p>实际调用由 CLI、测试工具和受控查询入口完成。Agent 可以在终端运行平台 CLI，由 CLI 通过 HTTP 接口访问研发平台。页面与 CLI 使用同一套服务端权限和发布记录，人打开平台页面时，也能继续查看 Agent 发起的那次操作。</p>
<p>例如，服务概览返回当前环境、可用制品与运行信息；发布命令返回发布记录；日志查询接收服务、环境和时间范围，返回限定数量的日志。工具用结构化结果区分成功、权限不足、前置条件不满足、数据不可用与结果未知。长任务返回可以继续查询的操作记录，临时断开后仍能确认进展。</p>
<p>服务、环境、制品、发布记录和本次测试任务之间的对应关系，也需要随开发任务保存。恢复工作时，Agent 先查询这些对象的当前状态，再决定继续等待、执行测试还是进入排错。这样，一次任务可以跨越多轮对话，而不必重新猜测上一次究竟发布到了哪里。</p>
<p>在这层设计里，说明文件负责告诉 Agent 怎样工作，工具负责执行动作，平台负责校验并保存执行状态。仅仅增加一段 Prompt，无法补出缺失的发布接口或数据库访问入口；仅仅增加一个接口，也不能让 Agent 自动理解什么结果才算完成。</p>
<p>这套环境把一次开发拆成了可以持续推进的步骤：创建符合约定的服务，用可复用的构建过程生成制品，在确定的环境中运行测试，发布后核对真实请求，失败时沿请求和状态继续排查。</p>
<p>接下来仍然需要完善的是业务验收。发布工具能够统一检查版本和实例，不同业务的正确结果却各不相同：任务需要执行完成，文件需要能够读取，通知需要实际送达。把这些条件写成可重复运行、能够管理测试数据的用例，Agent 才能逐步减少对人工补充判断的依赖。</p>
<p>与本文相关的存储和运行环境问题，另见<a href="/posts/engineering/sandbox-build-storage/" rel="">Sandbox 存储性能优化</a>与<a href="/posts/engineering/sandbox-environment-persistence/" rel="">Sandbox 环境持久化</a>；服务拆分和环境选路的变化，见<a href="/posts/engineering/rd-platform-evolution/" rel="">后端架构演进</a>。</p>]]></description></item></channel></rss>