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 结构门(硬约束):前者依赖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小时 |
| 运行时开销 | 零(编译后类型擦除) |
局限(必须知道)
- 这是"防意外"不是"防恶意":包内代码理论上可以绕过封装(如Go的反射、TypeScript的
as any)。结构门的承诺是"让违规变得困难且有意图",不是"让违规变得不可能"。
- Spec本身可能写错:如果你的Spec写错了规则(比如把"isMember == true"写成了"isMember == false"),生成的Guard代码会忠实地执行错误规则。Spec是新的信任根。
- 代码生成链的维护:Codegen本身也是软件,需要维护和调试。当你的目标语言或框架升级时,生成器可能需要更新。
- 不适合所有场景:
- AI Agent有时会绕过Guard:Agent可能写出
// @ts-ignore或使用反射来绕过类型检查。这时需要审计门(Gate 3)来捕获。
何时该用、何时不该用
该用:
- 团队主要用AI Agent写生产代码(3人以上团队)
- 系统有多租户、权限、敏感数据等安全硬约束
- 同类型规则需要在20+ handler中重复执行
不该用:
- 个人项目的MVP阶段(初期速度比安全性重要)
- 只有1-2个handler需要检查(不值得搭建基础设施)
- 团队不熟悉TypeScript/Go的类型系统(学习成本会超过收益)
FAQ
Q: 这和普通的类型检查有什么区别?
A: 普通类型检查保证"字符串是字符串",结构门保证"这个字符串代表一个已验证的租户访问权限"。前者防止类型错误,后者防止逻辑错误。string 和 TenantAccess 都是类型,但后者携带了"已通过验证"的证明。
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搬进类型系统,让编译器替你拒绝违规代码。
三个立刻可以做的事:
- 今天:在项目里找一个"每个handler都需要检查"的规则,把它抽成智能构造器
- 本周:搭建gate流水线,让CI在每次PR时自动验证
- 本月:将验证过的模式写入Agent的CLAUDE.md/skill,形成团队规范
当你的代码库自己会对AI Agent说"不"的时候,你就不需要每个凌晨3点的PR都逐行审计了。
#AI创业 #Agent工坊 #AI编码 #类型系统 #安全实践
本文由AI辅助创作,经人工审核编辑发布