第四章:架构约束 —— 让 Agent 不走偏
如果说上下文工程是让 Agent "看得见",那么架构约束就是让 Agent "不走偏"。
核心命题:
限制解决方案空间,才能增加输出的信任度和可靠性。 [2]
为什么约束是前提而非奢侈品
传统观念 vs AI 时代
| 观念 | 传统理解 | AI 时代翻转 |
|---|---|---|
| 架构约束 | 大团队才需要的奢侈品 | 第一天就需要的先决条件 |
| 代码规范 | 建议性的,可以灵活变通 | 必须机械化强制执行 |
| 技术选型 | 选"最好"的技术 | 选 Agent 最能理解的技术 |
| 代码审查 | 人类把关就够了 | Agent 产出太快,审查跟不上 |
确定性规则 > 自然语言描述
核心原则
能用代码强制执行的约束,就不要用自然语言描述。
# ❌ 自然语言描述 — Agent 会形式遵守,实质忽略
# docs/CONVENTIONS.md
# "请确保所有服务之间的依赖方向是单向的,从 UI 到 Types。"
# ✅ 确定性规则 — Agent 无法违反
# custom_linter/dependency_direction.py
def check_dependency_direction(source_module, target_module):
"""检查依赖方向是否符合分层架构"""
layers = {"Types": 0, "Config": 1, "Repo": 2, "Service": 3, "Runtime": 4, "UI": 5}
source_layer = get_layer(source_module)
target_layer = get_layer(target_module)
if layers[source_layer] < layers[target_layer]:
# 在错误信息中嵌入修复指令!
raise DependencyError(
f"违反单向依赖: {source_module}({source_layer}) → {target_module}({target_layer})\n"
f"修复: {target_layer} 层不应依赖 {source_layer} 层。\n"
f"请将共享逻辑提取到 {source_layer} 层中。"
)
修复指令注入
OpenAI 团队的一个巧妙设计:在 lint 错误信息中直接嵌入 Agent 可理解的修复指导。
传统 lint 错误:
Error: Dependency violation at line 42
修复指令注入:
Error: Dependency violation at line 42
Fix: UI modules should not import Types directly.
Use the Service layer as intermediary.
See docs/ARCHITECTURE.md#dependency-rules
这让 Agent 遇到违规时可以立即自我纠正,而不是困惑地猜测该怎么办。
分层架构实践
依赖方向控制
OpenAI 团队的分层域架构模型:
依赖方向(严格单向):
Types → Config → Repo → Service → Runtime → UI
↓
Providers(横切关注点入口)
↓
认证、连接器、遥测、功能标志
# 分层依赖检查器实现
class LayerDependencyChecker:
"""分层架构依赖检查器"""
LAYERS = {
"types": 0,
"config": 1,
"repo": 2,
"service": 3,
"runtime": 4,
"ui": 5,
}
ALLOWED_CROSS = {
# 每层可以依赖同层及以下所有层
"ui": {"runtime", "service", "repo", "config", "types"},
"runtime": {"service", "repo", "config", "types"},
"service": {"repo", "config", "types"},
"repo": {"config", "types"},
"config": {"types"},
"types": set(),
}
def check_import(self, source_file: str, import_path: str) -> CheckResult:
source_layer = self._get_layer(source_file)
target_layer = self._get_layer(import_path)
if target_layer not in self.ALLOWED_CROSS.get(source_layer, set()):
return CheckResult(
passed=False,
error=self._generate_fix_message(source_layer, target_layer)
)
return CheckResult(passed=True)
def _generate_fix_message(self, source: str, target: str) -> str:
return (
f"违反分层依赖: {source} 层不应直接依赖 {target} 层\n"
f"修复建议: 将共享逻辑下移到 {target} 层,"
f"或通过 Service 层作为中介层访问"
)
结构测试
用测试来验证架构约束:
# tests/test_architecture.py
import pytest
from custom_linter.architecture import check_layer_dependencies, check_module_boundaries
class TestArchitectureConstraints:
"""架构约束测试 — 确保 Agent 不会违反架构规则"""
def test_no_ui_imports_types_directly(self):
"""UI 层不应直接导入 Types 层"""
violations = check_layer_dependencies(
source_layer="ui",
forbidden_targets=["types"],
)
assert len(violations) == 0, f"发现违规: {violations}"
def test_no_circular_dependencies(self):
"""不应存在循环依赖"""
cycles = find_circular_dependencies("src/")
assert len(cycles) == 0, f"发现循环依赖: {cycles}"
def test_service_isolation(self):
"""不同业务域的 Service 不应互相依赖"""
violations = check_module_boundaries(
module_pattern="service/*",
allow_cross=False,
)
assert len(violations) == 0
def test_file_size_limits(self):
"""单文件不应超过规定行数"""
large_files = find_files_exceeding_lines("src/", max_lines=300)
assert len(large_files) == 0, f"过大文件: {large_files}"
品味编码化
将人类品味转化为机器规则
架构约束不仅包括宏观的依赖方向,还包括微观的"品味"——代码风格、命名约定、日志格式等不变式。
# custom_linter/style_rules.py
class StyleLinter:
"""编码化的人类品味"""
rules = [
# 结构化日志记录
{
"id": "no-console-log",
"pattern": r"console\.log\(",
"message": "禁止使用 console.log,请使用结构化日志 logger.info()",
"fix": "替换为: logger.info('message', {key: value})"
},
# 命名约定
{
"id": "schema-naming",
"pattern": r"class\s+(?!.*Schema).*Model",
"message": "Schema 类必须以 'Schema' 结尾",
"fix": "重命名为: {ClassName}Schema"
},
# 文件大小限制
{
"id": "file-size",
"check": "file_length",
"max_lines": 300,
"message": "文件超过 {max_lines} 行,请考虑拆分",
"fix": "将职责拆分到独立模块中"
},
# 可靠性要求
{
"id": "error-handling",
"pattern": r"await\s+\w+\([^)]*\)",
"message": "异步调用必须有错误处理",
"fix": "包裹在 try/except 中或使用 Result 类型"
},
]
一个重要的认知转变
只要输出是:
- 正确的 ✅
- 可维护的 ✅
- 对未来的 Agent 运行清晰可读的 ✅
就算达标。人类的品味通过审查评论、重构 PR 和 bug 修复被捕获, 然后编码到工具和规则中——一旦被捕获,就会持续应用于每一行代码。
分层设计
中央层面:强制执行边界
├── 正确性约束
├── 可重复性约束
└── 安全性约束
本地层面:允许自主权
├── 解决方案的表达方式
├── 具体算法选择
└── 实现细节
类比:
├── 平台团队定义铁律
└── 业务团队在铁律内自由发挥
CI 集成
约束即流水线
# .github/workflows/harness.yml
name: Harness Constraints
on: [pull_request]
jobs:
architecture:
name: 架构约束检查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 分层依赖检查
run: python tools/check_layer_deps.py
- name: 循环依赖检测
run: python tools/check_circular_deps.py
- name: 文件大小限制
run: python tools/check_file_sizes.py --max 300
- name: 命名规范检查
run: python tools/check_naming.py
- name: 结构化日志检查
run: python tools/check_logging.py
quality:
name: 质量评估
needs: architecture
runs-on: ubuntu-latest
steps:
- name: 运行评估
run: python -m harness evaluate --dataset golden_set
- name: 质量阈值检查
run: |
score=$(cat results/latest.json | jq '.overall.score')
if (( $(echo "$score < 0.75" | bc) )); then
echo "Quality score $score below threshold 0.75"
exit 1
fi
实践指南
行业案例:约束的回报
不同公司在实践中验证了"约束 > 指令"的原则:
Vercel:工具从 15 个简化到 2 个
├── 问题:Agent 面对 15 个工具时经常选错
├── 方案:简化为 read_file + edit_file 两个核心工具
├── 效果:准确率从 80% 提升到 100%
└── 启示:减少选择空间 = 提高决策质量
Cursor:约束 > 指令
├── 问题:自然语言指令经常被 Agent 忽略
├── 方案:将关键规则编码为代码约束(linter/CI)
├── 效果:违规率从 30% 降到接近 0%
└── 启示:Agent 无法违反它不能绕过的规则
Stripe:CI 限速
├── 问题:Agent 在 CI 中反复修复失败,浪费时间
├── 方案:限制 CI 重试最多 2 轮
├── 效果:节省 40% CI 资源,Agent 学会一次做对
└── 启示:合理的约束反而提升效率
三个案例指向同一个规律:
约束形式 效果 自然语言指令 → Agent 有时遵守,有时不遵守 linter 规则 → Agent 每次都遵守 CI 约束 → Agent 无法不遵守
确定性越强,合规率越高。
约束设计原则
| 原则 | 说明 |
|---|---|
| 机械可执行 | 规则应该是工具可以自动检查的 |
| 修复指令嵌入 | 错误信息应包含 Agent 可理解的修复步骤 |
| 渐进加强 | 先加最重要的约束,逐步增加 |
| 约束即文档 | 约束本身就是最好的架构文档 |
快速检查清单
✅ 架构约定是否通过代码强制执行(而非仅文档描述)?
✅ lint 错误信息是否包含修复指令?
✅ 是否有分层依赖检查?
✅ 是否有结构测试?
✅ CI 是否集成所有约束检查?
✅ 约束是否与 Agent 协同设计(而非仅面向人类)?
小结
参考文献:
- [1] Ryan Lopopolo, "Harness engineering: leveraging Codex in an agent-first world", OpenAI, 2026
- [2] Birgitta Böckeler, "Harness Engineering", martinfowler.com, 2026
- [3] "模型不是关键,Harness 才是", 微信公众号, 2026
下一章,我们将探讨垃圾收集——如何持续对抗 Agent 引入的熵增。