【AI全职下属】把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥

把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥

系列导读 :本系列用一个可运行的 Spring Boot 秒杀 Demo,拆解"当 AI 成为我的全职下属"之后,研发工作流应该如何重建。五期内容从一次超卖事故开始,依次讨论并发正确性、上下文裁剪、防作弊 CI、Agent 友好任务单元和 Human-in-the-loop 上线门禁。主线不是证明 AI 能不能写代码,而是回答一个更实际的问题:当 Agent 已经能高吞吐地产出代码时,工程师如何用边界、验证和审批机制,把它变成可靠的执行者。

配套代码demo/AutoEnterprise-Seckill/src/main/java/com/xiaoz/seckill/service

文章目录

  • [把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥](#把代码拆成 Agent 可安全执行的任务单元:别再让 AI 在大 Service 里自由发挥)
    • [1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统](#1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统)
    • [2. 原因:大 Service 会放大 Agent 的误改面](#2. 原因:大 Service 会放大 Agent 的误改面)
    • [3. 目标:把需求变成一个可交给 Agent 的任务单元](#3. 目标:把需求变成一个可交给 Agent 的任务单元)
    • [4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元](#4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元)
    • [5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收](#5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收)
    • [6. Redisson 例子:锁边界也要写成 Agent 可执行规则](#6. Redisson 例子:锁边界也要写成 Agent 可执行规则)
      • [6.1 启用并验证 Redisson 模式](#6.1 启用并验证 Redisson 模式)
    • [7. SOLID 在这里的作用:不是原则展示,而是任务隔离](#7. SOLID 在这里的作用:不是原则展示,而是任务隔离)
      • [7.1 单一职责:让 Agent 少读少改](#7.1 单一职责:让 Agent 少读少改)
      • [7.2 开闭原则:新增策略,不覆盖旧案例](#7.2 开闭原则:新增策略,不覆盖旧案例)
      • [7.3 依赖倒置:让测试替换基础设施](#7.3 依赖倒置:让测试替换基础设施)
      • [7.4 接口隔离:减少 Agent 可误用能力](#7.4 接口隔离:减少 Agent 可误用能力)
    • [8. 不要为了"Agent 友好"制造过度抽象](#8. 不要为了“Agent 友好”制造过度抽象)
    • [9. 验收一次 Agent 修改:看文件、看命令、看不变量](#9. 验收一次 Agent 修改:看文件、看命令、看不变量)
    • 实验环境
    • 小结

1. 问题现场:你让 Agent 改库存策略,它顺手改了半个系统

你给 Agent 的任务是:"给秒杀接口加一个库存扣减策略,保持不超卖。"

结果它不仅改了库存逻辑,还顺手调整了订单创建、Redis 锁、Controller 返回值和测试夹具。PR 看起来很努力,但 Review 时很难回答三个问题:

  • 这次修改到底影响了哪些路径?
  • 哪些文件是完成任务必须改的,哪些是越界改动?
  • 现有并发验证还能不能证明库存、订单和响应一致?

这不是单纯的"模型不够聪明"。更常见的原因是:我们把一个边界不清的大 Service 交给 Agent,让它在过大的操作面里自由搜索。它看到的上下文越混杂,越容易把"可修改"误判成"应该修改"。

本文不把重点放在"SOLID 原则有多优雅",而是讨论一个更实用的目标:如何把 Java 代码拆成 Agent 可安全执行、可审计、可验收的任务单元

2. 原因:大 Service 会放大 Agent 的误改面

典型的"万能秒杀 Service"可能同时负责:

  • 参数校验和用户身份解析。
  • 查询商品、扣库存、创建订单。
  • 获取 Redis 锁和处理重试。
  • 记录日志、发送消息、降级补偿。
  • 组装接口响应和异常文案。

人类可以凭经验跳读这些分支,Agent 却会把它们都纳入候选修改范围。对 Agent 来说,大 Service 至少带来四类额外风险:

风险 表现 后果
上下文噪声 无关 Controller、DTO、日志和配置混入任务包 Agent 选错参考代码
权限模糊 没说哪些文件不能改 Agent 顺手改测试或流水线
副作用隐藏 订单、库存、锁释放耦在一起 Review 难以判断影响范围
验收不聚焦 只说"测试通过" Agent 可能绕开真实业务不变量

所以第 4 期要解决的不是"怎样写一个更漂亮的架构",而是:怎样让 Agent 接到任务时,只能看见必要上下文,只能修改允许路径,只能通过真实验收。

3. 目标:把需求变成一个可交给 Agent 的任务单元

一个适合交给 Agent 的任务单元,至少要包含五部分:

text 复制代码
agent-task/
├── issue.md            # 问题、目标、成功标准
├── allowed-files.txt   # 允许修改的文件
├── context.json        # 进入上下文的文件、原因和规模
├── constraints.md      # 禁止修改的路径、依赖和行为
└── verify.ps1          # 必须执行的验收命令

如果任务是"修改库存扣减策略",不要把整个仓库扔给 Agent。更合适的任务边界是:

text 复制代码
允许阅读:
- AtomicSeckillService.java
- OrderCreator.java
- SeckillProductMapper.java
- SeckillResult.java
- 相关并发测试

允许修改:
- AtomicSeckillService.java
- SeckillProductMapper.java
- 必要时新增同目录下的小策略类

禁止修改:
- pipeline/
- ai_firm/cheat_detector.py
- Controller 返回协议
- 已有并发测试的断言语义
- pom.xml 中未说明的新依赖

这样做的价值是把"请帮我修一下"改成"你只能在这个边界内完成这个验收目标"。Agent 的自由度减少了,但成功率和可审计性会上升。

4. Demo:把秒杀逻辑拆成 Agent 能理解的小单元

当前 Demo 把核心逻辑拆成四个角色:

组件 给 Agent 的含义
AtomicSeckillService 任务入口:编排库存扣减和订单创建
SeckillProductMapper 库存不变量:stock > 0 条件更新
OrderCreator 订单副作用:只负责创建订单
SeckillResult 响应契约:成功、售罄、模式和消息

AtomicSeckillService 的业务逻辑很短:

java 复制代码
@Transactional
public SeckillResult execute(Long userId, Long productId) {
    if (productMapper.deductStockIfAvailable(productId) != 1) {
        return SeckillResult.soldOut("atomic");
    }
    SeckillOrder order = orderCreator.create(userId, productId, "atomic");
    return SeckillResult.success(order.getId(), "atomic");
}

这段代码对 Agent 友好,不是因为它符合某个抽象口号,而是因为它让任务边界变得清楚:

  • 要改库存条件,优先看 SeckillProductMapper
  • 要改订单字段,优先看 OrderCreator
  • 要改成功/失败响应,优先看 SeckillResult
  • 要调整编排顺序,才需要改 AtomicSeckillService

上下文裁剪器在这个结构下只需带回目标 Service、Mapper、订单创建器和结果对象。它不是为了"少给 Token"而少给,而是为了让 Agent 明确知道:本次任务与 Web 层、管理接口、H2 Console、CI 脚本无关。

5. 怎么给 Agent 下任务:不要只写需求,要写边界和验收

下面是一个可以直接给 Agent 的任务描述模板:

text 复制代码
任务:调整 atomic 秒杀路径的库存扣减策略,保持不超卖。

允许修改:
- src/main/java/com/xiaoz/seckill/service/AtomicSeckillService.java
- src/main/java/com/xiaoz/seckill/mapper/SeckillProductMapper.java

禁止修改:
- pipeline/
- ai_firm/
- src/test/ 中已有断言语义
- Controller 响应结构
- pom.xml 中未说明的新依赖

验收标准:
- mvn.cmd test 通过
- python pipeline\verify_integrity.py 通过
- unsafe 模式允许复现超卖
- atomic 模式 database_orders <= stock
- atomic 模式 remaining_stock >= 0
- oversold 必须为 false

这个模板的重点不是格式,而是把 Agent 的目标从"让项目绿"改成"在指定文件内恢复业务不变量"。如果它修改了 pipeline/ 或跳过并发测试,即使最终 CI 是绿的,也应该判为失败。

6. Redisson 例子:锁边界也要写成 Agent 可执行规则

Redisson 模式是一个很好的边界案例。它只负责获取锁、调用原子业务、在 finally 中释放当前线程持有的锁:

java 复制代码
RLock lock = redisson.getLock("seckill:product:" + productId);
boolean acquired = false;
try {
    acquired = lock.tryLock(500, TimeUnit.MILLISECONDS);
    if (!acquired) {
        return new SeckillResult(false, null, "redisson", "系统繁忙,请重试");
    }
    SeckillResult result = atomicSeckillService.execute(userId, productId);
    return new SeckillResult(result.success(), result.orderId(), "redisson", result.message());
} finally {
    if (acquired && lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

给 Agent 修改这段代码时,任务约束应写得更具体:

text 复制代码
必须保留:
- finally 中释放锁
- lock.isHeldByCurrentThread() 判断
- Redis 不可用时失败,而不是降级到 unsafe
- 数据库 stock > 0 条件更新作为最终防线

不得引入:
- catch Throwable 后吞异常
- 未说明的无限等待锁
- Redis 失败后自动调用 unsafe 模式

这里仍保留数据库 stock > 0 条件更新。原因是锁与数据库约束解决的问题不同:

  • 锁降低同一资源的并发冲突。
  • 数据库条件更新守住最终库存不变量。

只依赖 Redis 锁,一旦锁配置、网络或调用路径出现问题,数据库就失去最后防线。对 Agent 来说,这条规则必须写进 constraints.md,不能只靠它"理解架构意图"。

6.1 启用并验证 Redisson 模式

先将可访问的 Redis URI 与应用基础地址写入环境变量,再执行:

powershell 复制代码
$env:REDIS_ADDRESS='<Redis URI>'
$env:SECKILL_BASE_URL='<应用基础地址>'
mvn.cmd spring-boot:run "-Dspring-boot.run.profiles=redis"
python pipeline\run_stress_test.py --base-url $env:SECKILL_BASE_URL --mode redisson --concurrency 100 --requests 500 --stock 100

预期 database_orders 不超过 100、remaining_stock 不小于 0、oversoldfalse。如果 Redis 不可用,接口应返回 HTTP 503,而不是静默降级为无锁执行。

本文当前环境没有运行 Redis,所以 Redisson 网络行为仍标注为待读者在 Redis 环境中复验;这不是缺陷隐藏,而是明确区分"已编译"和"已压测"。

7. SOLID 在这里的作用:不是原则展示,而是任务隔离

SOLID 可以保留,但它在本文里的角色应该是工具,不是主角。

7.1 单一职责:让 Agent 少读少改

一个类只处理一个变化原因,Agent 修改时就不必同时推理订单、库存、消息和锁。单一职责的直接收益是:上下文包更小,Review 面更窄。

7.2 开闭原则:新增策略,不覆盖旧案例

Demo 保留 unsafeatomicredisson 三条路径。这样读者可以对比行为,Agent 也不需要为了实现新策略删除旧案例。旧路径能继续作为回归参照。

7.3 依赖倒置:让测试替换基础设施

业务编排依赖 Mapper 和小组件,而不是在方法中自行创建连接、客户端和线程池。测试可以注入替代实现,生产配置也能独立变化。

7.4 接口隔离:减少 Agent 可误用能力

如果一个工具只需要扣库存,就不应同时暴露删除商品、批量改价等接口。对 Agent 而言,接口越大,可误用的能力越多。

8. 不要为了"Agent 友好"制造过度抽象

Agent 友好不等于每三行代码创建一个接口。判断是否需要拆分,可以问三个问题:

  1. 这个职责是否有独立变化原因?
  2. 是否存在第二种实现、测试替身或独立验收标准?
  3. 拆分后是否明显缩小 Agent 的阅读范围或修改范围?

如果三个答案都是"否",保留直接代码通常更清晰。过度抽象会制造新的坏处:文件更多、跳转更远、上下文裁剪更难解释。Agent 不是越多层越安全,而是边界越明确越安全。

9. 验收一次 Agent 修改:看文件、看命令、看不变量

一次 Agent 提交能否合并,不应只看"有没有通过测试"。建议至少检查这几项:

检查项 通过标准
修改范围 只改了任务允许的文件
路径边界 没有修改 pipeline/、审计脚本和无关 Controller
依赖变化 没有新增未说明依赖
测试诚信 没有 @DisabledassertTrue(true)、空 catch
业务不变量 订单数、库存、成功响应三者一致
模式隔离 unsafeatomicredisson 行为仍可区分

可执行命令可以写成固定清单:

powershell 复制代码
mvn.cmd test
python pipeline\verify_integrity.py
python pipeline\run_stress_test.py --mode unsafe --concurrency 100 --requests 500 --stock 100
python pipeline\run_stress_test.py --mode atomic --concurrency 100 --requests 500 --stock 100

如果要运行 Redisson 模式,先启动 Redis 并设置 REDIS_ADDRESS,再补跑:

powershell 复制代码
python pipeline\run_stress_test.py --base-url $env:SECKILL_BASE_URL --mode redisson --concurrency 100 --requests 500 --stock 100

这些命令把"我觉得它没乱改"变成可复核证据。Agent 可以写代码,但验收标准不能交给它自己临时解释。

实验环境

项目 版本或参数
JDK 17.0.15
Spring Boot 3.5.15
MyBatis-Plus 3.5.15
Redisson 4.5.0 API 编译通过
验证范围 Maven 测试通过;本文运行环境未安装 Redis,分布式锁网络行为需读者启动 Redis 后验证

小结

第 4 期的重点不是证明 SOLID 仍然正确,而是把代码结构变成 Agent 可安全执行的工作边界。

当一个任务单元具备明确输入、允许修改范围、禁止路径和验收命令时,Agent 的能力才会从"自由发挥"变成"受控执行"。模块越清晰,Agent 需要的上下文越少;边界越稳定,自动生成代码的风险越可控。

最后一期将把前四期组合为一套 Human-in-the-loop 工作流:哪些步骤交给 Agent,哪些门禁必须由人类和自动化系统掌握。

适用边界与回退

  • 单机应用优先考虑数据库原子更新,不要为了"分布式"强行引入 Redis。
  • Redis 锁不可用时应失败或进入经过设计的降级路径,不能自动切换到 unsafe
  • Redisson Watchdog、等待时间和连接池参数需要结合业务耗时压测,Demo 参数不能直接复制到生产。
  • 如果团队还没有稳定的自动化测试,先补验收脚本,再把修改任务大规模交给 Agent。

测试 Demo 仓库

配套 Demo 仓库地址:https://github.com/quan020406/xiaozhan-blog-column-demos


上一篇AI Agent 防作弊 CI 实战

下一篇:小z疯狂码字ing...

感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!