Agent工坊

【Agent工坊】让AI写的代码自带"安检门":结构化反向压力实战指南

AI编码Agent最大的问题不是写不对,而是你无法确定它对不对。把规则从Prompt搬进类型系统,让编译器替你守住底线。

痛点:当AI写了100个handler,第37个忘了auth检查

2026年最讽刺的景象:AI代码生成能力突飞猛进,一次能产出上万行代码,但最无聊的安全漏洞——越权访问——依然是OWASP Top 10榜首。不是你Prompt写得不够细,不是CLAUDE.md里的规则不够多。根本问题是:规则住错了地方

你告诉Claude Code"每个handler都要检查用户是否属于该租户",它确实照做了——前36个handler都完美。第37个handler是凌晨3点生成的,Prompt已经滚出了上下文窗口,那个if !user.IsMemberOf(tenantID)就被遗漏了。然后你合并了PR,因为你也审不了100个handler里的每一个auth检查。

这就是"行为门"的局限:依赖Agent记住规则 → 识别适用场景 → 抵抗局部上下文的引力 → 人类reviewer跨整个代码库维护同一个不变量。四个环节,任意一个断裂 = 漏洞上线。

今天我们要介绍的方案来自一个简洁而有力的洞察:结构化反向压力(Structural Backpressure)比更聪明的Agent更重要。它的核心理念是——把你最关心的规则从Prompt搬进类型系统,让编译器在Agent写出违规代码时直接拒绝编译。

本文方法论参考自 Reuben Brooks 的 Shen-Backpressure 项目(2026年5月发布,HN 41分),结合 AI 创业者在生产环境中的实际可落地方案。

核心理念:行为门 vs 结构门

行为门 vs 结构门

▲ 行为门(软约束)vs 结构门(硬约束):前者依赖Agent记忆规则,后者让编译器拒绝违规代码

行为门(Behavioral Gate)

Prompt → Agent理解 → Agent记忆 → Agent执行 → Human Review

   ↓ ↓ ↓ ↓ ↓

 写了规则 可能理解 可能记住 可能执行 可能审到

行为门是"软约束"——你告诉Agent应该做什么,但无法验证它是否真的做了。Agent表现足够好让你信任它,又不够好让你放心的那根钢丝,就是行为门的致命缺陷。

结构门(Structural Gate)

Spec → Codegen → 编译器类型检查 → Gate失败 → 反馈给Agent

  ↓ ↓ ↓ ↓ ↓

 写一次 自动生成 拒绝违规代码 精确的错误 自动修正循环

结构门是"硬约束"——不依赖Agent记住规则。违规代码根本无法编译通过。失败信息本身就是精确的、确定性的反馈,驱动Agent自动修正。

关键区别:行为门问"Agent是否正确执行了规则?",结构门问"这段代码是否可能违反不变量?"——前者是主观信任,后者是客观事实。

实战方案1:智能构造器模式(TypeScript版)

智能构造器模式

▲ 从散落的auth检查(左)到统一的TenantAccess.create()闸门(右):智能构造器让类型携带验证证明

不用引入任何新工具,你可以立刻在现有TypeScript项目中实施这个模式。

问题:散落的auth检查

// ❌ 传统写法:auth检查散落在每个handler里

async function getResource(req: Request, res: Response) {

  const user = await getUser(req);

  if (!user.isMemberOf(req.params.tenantId)) { // 第37个handler忘了这行

    return res.status(403).json({ error: 'Forbidden' });

  }

  const resource = await db.resources.findById(req.params.resourceId);

  // ...

}

async function updateResource(req: Request, res: Response) {

  const user = await getUser(req);

  // 忘了检查tenant membership!但代码编译通过,测试通过(测试也没写这个case)

  const resource = await db.resources.update(req.params.resourceId, req.body);

  // ...

}

方案:让类型携带"已通过检查"的证明

// ✅ 智能构造器模式:类型本身是"安检通过"的凭证

// 第一步:定义不可外部构造的类型

class TenantAccess {

  private constructor(

    public readonly userId: string,

    public readonly tenantId: string,

    private readonly _proof: symbol // 类型级标记,防止伪造

  ) {}

  // 唯一构造入口——强制走检查逻辑

  static async create(userId: string, tenantId: string): Promise<TenantAccess | Error> {

    const isMember = await checkMembership(userId, tenantId);

    if (!isMember) {

      return new Error(`User ${userId} is not a member of tenant ${tenantId}`);

    }

    return new TenantAccess(userId, tenantId, Symbol('tenant-access-proof'));

  }

  // 解构方法——只能通过已验证的实例访问

  get userIdStr(): string { return this.userId; }

  get tenantIdStr(): string { return this.tenantId; }

}

// 第二步:handler只接受已验证的类型

async function getResource(access: TenantAccess, resourceId: string) {

  // 不需要检查auth——类型已经保证了

  const resource = await db.resources.findInTenant(access.tenantIdStr, resourceId);

  return resource;

}

// 第三步:路由层统一处理,只构造一次

app.get('/api/tenants/:tenantId/resources/:resourceId', async (req, res) => {

  const access = await TenantAccess.create(req.userId, req.params.tenantId);

  if (access instanceof Error) {

    return res.status(403).json({ error: access.message });

  }

  // access 的类型是 TenantAccess,编译器知道它已经过验证

  const resource = await getResource(access, req.params.resourceId);

  res.json(resource);

});

效果

  • getResource 函数签名从 (req, res) 变为 (access: TenantAccess, resourceId: string)——一眼看出它需要已验证的访问权限
  • 如果Agent尝试传入原始 userId 字符串,TypeScript编译器直接报错
  • private constructor + Symbol 双重保证:无法绕过 create() 方法
  • Auth检查集中在一个地方,不会被遗漏

进阶:多级证明链

// 认证 → 租户验证 → 资源所有权 → 操作授权,每一级都是独立类型

class AuthenticatedUser {

  private constructor(public readonly userId: string) {}

  static async create(token: string): Promise<AuthenticatedUser | Error> {

    const user = await verifyToken(token);

    return user ? new AuthenticatedUser(user.id) : new Error('Invalid token');

  }

}

class TenantAccess {

  private constructor(

    public readonly user: AuthenticatedUser,

    public readonly tenantId: string,

  ) {}

  static async create(user: AuthenticatedUser, tenantId: string): Promise<TenantAccess | Error> {

    const isMember = await checkMembership(user.userId, tenantId);

    return isMember ? new TenantAccess(user, tenantId) : new Error('Not a member');

  }

}

class ResourceAccess {

  private constructor(

    public readonly tenantAccess: TenantAccess,

    public readonly resourceId: string,

  ) {}

  static async create(access: TenantAccess, resourceId: string): Promise<ResourceAccess | Error> {

    const belongs = await resourceBelongsToTenant(resourceId, access.tenantId);

    return belongs ? new ResourceAccess(access, resourceId) : new Error('Resource not in tenant');

  }

}

// 类型驱动的API:看函数签名就知道需要什么级别的权限

async function readResource(access: ResourceAccess): Promise<Resource> { ... }

async function updateResource(access: ResourceAccess): Promise<void> { ... }

// readResource 不关心你是怎么拿到 ResourceAccess 的,它只关心你有

这整套模式在TypeScript中零依赖、零运行时开销(编译后类型擦除)、与现有框架完全兼容。

实战方案2:代码生成 + 门禁流水线(Go示例)

门禁流水线

▲ 四道门禁流水线:Spec验证→编译检查→审计→测试,失败信息精确反馈给AI Agent驱动自动修正

当你有20个微服务、每个都需要相同的租户隔离逻辑时,手写智能构造器会变成新的负担。这时应该引入代码生成。

从Spec生成Guard代码

Spec(声明式规则) → Codegen → Guard Types(编译时强制)

     写一次 自动 每次构建验证

Spec 示例(用 YAML 表达,团队都能看懂):

# specs/tenant-access.yaml

rule: TenantAccess

inputs:

  - principal: AuthenticatedUser # 必须已认证

  - tenant: TenantId # 必须是有效租户ID

constraint: principal.isMemberOf(tenant) == true

output: TenantAccess { principal, tenant }

rule: ResourceAccess

inputs:

  - access: TenantAccess # 必须已有租户访问权

  - resource: ResourceId # 必须是有效资源ID

constraint: resource.belongsTo(access.tenant) == true

output: ResourceAccess { access, resource }

生成的Go代码(关键部分):

// Code generated by guardgen; DO NOT EDIT.

package guard

type TenantAccess struct {

    principal AuthenticatedUser

    tenant TenantId

}

// NewTenantAccess 是唯一的构造入口

func NewTenantAccess(principal AuthenticatedUser, tenant TenantId, isMember bool) (TenantAccess, error) {

    if !isMember {

        return TenantAccess{}, fmt.Errorf("guard: principal %s is not a member of tenant %s", principal.ID(), tenant)

    }

    return TenantAccess{principal: principal, tenant: tenant}, nil

}

// 字段不可导出(小写),外部包无法直接构造

// 只能通过 NewTenantAccess 获取有效实例

门禁流水线(CI/CD集成)

# .github/workflows/guard-gates.yml

name: Guard Gates

on: [push, pull_request]

jobs:

  gates:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v4

      # Gate 1: 检查生成代码是否与Spec一致(防止手动修改)

      - name: Spec-Generated Consistency

        run: guardgen verify --spec specs/ --output pkg/guard/

      # Gate 2: 编译检查(类型级约束生效)

      - name: Build

        run: go build ./...

      # Gate 3: 审计——检测是否有人手动编辑过生成代码

      - name: Guard Code Audit

        run: guardgen audit --output pkg/guard/

      # Gate 4: 运行测试

      - name: Test

        run: go test ./...

当AI Agent提交的代码触发门禁失败时:

❌ Gate 1 FAILED: pkg/guard/tenant_access.go

   Line 15: manual edit detected — generated constructor body modified

   Fix: update specs/tenant-access.yaml and regenerate, or revert manual changes

❌ Gate 2 FAILED: handlers/resource.go:42

   cannot use tenantID (variable of type string) as guard.TenantAccess value

   Hint: wrap tenantID through guard.NewTenantAccess() first

Agent收到这些精确的错误信息后,自动修正代码。这就是"反向压力"——不给Agent长篇大论的Prompt,而是给编译器的拒绝信息。

接入AI编码Agent工作流

Claude Code 集成

.claude/commands/sb/ 目录下放置自定义命令:

# .claude/commands/sb/gates.sh

#!/bin/bash

# 运行所有门禁,失败时输出精确的错误信息供Agent修正

echo "=== Guard Gates ==="

guardgen verify --spec specs/ --output pkg/guard/ || exit 1

go build ./... || exit 1

guardgen audit --output pkg/guard/ || exit 1

go test ./... || exit 1

echo "✅ All gates passed"

CLAUDE.md 中添加:

## Code Invariant Rules (Enforced by Gates, NOT by Prompt)

- Tenant isolation: enforced by guard.TenantAccess constructor, NOT by manual checks

- Resource ownership: enforced by guard.ResourceAccess constructor

- If a handler needs tenant access, the function signature MUST accept guard.TenantAccess

- After every code change, run: /sb/gates

- If gates fail, fix the code until ALL gates pass before claiming completion

Hermes Agent 集成

Hermes Agent v0.14.0(2026年5月16日发布)支持 /goal 命令和 Kanban 多Agent板,天然适合门禁驱动的开发流程:

# 在 Hermes 中设置任务:

/goal "实现新API端点 GET /api/resources/:id,确保所有gate通过"

# Hermes 会自动循环:写代码 → 跑gates → 失败反馈 → 修正 → 再跑gates

# 直到所有gate通过才标记完成

更进一步的,可以在Hermes的skill中固化门禁规则:

# .hermes/skills/guard-gates.md

name: guard-gates

description: 代码门禁检查——在提交任何代码修改后自动运行

triggers:

  - 任何Go代码修改

  - 任何spec修改

执行:

  1. guardgen verify --spec specs/ --output pkg/guard/

  2. go build ./...

  3. guardgen audit --output pkg/guard/

  4. go test ./...

  任一步失败 → 将错误信息反馈给Agent → Agent修正代码 → 重新运行

  全部通过 → 允许继续

成本与局限:诚实地说

成本

成本项评估
初始投入编写Spec + 搭建Codegen + 配置CI ≈ 2-4小时(一次性)
维护成本每次新增不变量需要更新Spec并重新生成 ≈ 5-15分钟
学习曲线TypeScript智能构造器:15分钟理解;代码生成方案:1-2小时
运行时开销零(编译后类型擦除)

局限(必须知道)

  1. 这是"防意外"不是"防恶意":包内代码理论上可以绕过封装(如Go的反射、TypeScript的as any)。结构门的承诺是"让违规变得困难且有意图",不是"让违规变得不可能"。
  1. Spec本身可能写错:如果你的Spec写错了规则(比如把"isMember == true"写成了"isMember == false"),生成的Guard代码会忠实地执行错误规则。Spec是新的信任根。
  1. 代码生成链的维护:Codegen本身也是软件,需要维护和调试。当你的目标语言或框架升级时,生成器可能需要更新。
  1. 不适合所有场景
  1. AI Agent有时会绕过Guard:Agent可能写出// @ts-ignore或使用反射来绕过类型检查。这时需要审计门(Gate 3)来捕获。

何时该用、何时不该用

该用

  • 团队主要用AI Agent写生产代码(3人以上团队)
  • 系统有多租户、权限、敏感数据等安全硬约束
  • 同类型规则需要在20+ handler中重复执行

不该用

  • 个人项目的MVP阶段(初期速度比安全性重要)
  • 只有1-2个handler需要检查(不值得搭建基础设施)
  • 团队不熟悉TypeScript/Go的类型系统(学习成本会超过收益)

FAQ

Q: 这和普通的类型检查有什么区别?

A: 普通类型检查保证"字符串是字符串",结构门保证"这个字符串代表一个已验证的租户访问权限"。前者防止类型错误,后者防止逻辑错误。stringTenantAccess 都是类型,但后者携带了"已通过验证"的证明。

Q: 引入这个模式后,AI Agent写代码会变慢吗?

A: 初期会——Agent需要多迭代1-2轮来通过所有gates。但这是好事:它用编译器的2秒拒绝了潜在的线上事故。从整体看,发现和修复bug的时间从"线上发现→几小时排查"变成"编译时发现→30秒修正"。

Q: 和形式化验证(Formal Verification)有什么区别?

A: 形式化验证追求"数学上不可能违反",结构门追求"工程上很难意外违反"。前者需要TLA+/Coq等专业工具和专业人才,后者只需要TypeScript/Go的类型系统和少量代码生成。对99%的创业公司,后者的性价比远高于前者。

Q: 已有项目能渐进引入吗?

A: 完全可以。从最敏感的一个模块开始(比如租户隔离),只对该模块引入智能构造器。不需要全量改造。Go的internal/包和TypeScript的private constructor都支持渐进式引入。

总结

AI编码Agent已经好到可以写你90%的代码了。剩下10%的挑战不是"写得更对",而是"知道它写得对不对"。

结构化反向压力的核心洞察只有一句话:把不变量从Prompt搬进类型系统,让编译器替你拒绝违规代码。

三个立刻可以做的事:

  1. 今天:在项目里找一个"每个handler都需要检查"的规则,把它抽成智能构造器
  2. 本周:搭建gate流水线,让CI在每次PR时自动验证
  3. 本月:将验证过的模式写入Agent的CLAUDE.md/skill,形成团队规范

当你的代码库自己会对AI Agent说"不"的时候,你就不需要每个凌晨3点的PR都逐行审计了。


#AI创业 #Agent工坊 #AI编码 #类型系统 #安全实践

本文由AI辅助创作,经人工审核编辑发布