☰
Harness Engineering 在软件工程层面的理论与实践:用 AGENTS.md 与 Linter 搭建 AI Coding 质量门禁
2026/9/29 10:31:27 网站建设 项目流程

1. 为什么 AI Coding 需要 Harness Engineering

你可能已经习惯了让 AI 帮你补全函数、生成单测、甚至整段重构。但真正把 AI Coding 放进一个持续交付的团队项目里,问题会立刻暴露:Agent 今天写的代码能跑,明天就绕过了分层;这次生成的 import 没问题,下次就把 UI 层直接连到了数据库。代码量越大,这种“隐性腐化”越难靠人工 Review 拦住。

Harness Engineering 要解决的就是这件事。它不是让模型更聪明,而是给模型套上一副“马具”——用 AGENTS.md 定义行为边界,用 Linter 把架构约束变成可执行的检查,让 AI 在写代码之前就知道什么不能做,在写完代码之后立刻收到可操作的反馈。这套思路在 OpenAI 的 Codex 实验里被验证过:人类工程师不再逐行写代码,而是设计环境、定义规则、搭建反馈回路。

这篇文章面向正在把 AI Coding 引入日常开发的工程师。我会给出可直接复制的 AGENTS.md 骨架、Python 与 JavaScript 两套 Linter 配置片段、本地验证命令,以及如何通过 TaoToken 统一 Key/API 通道接入 AI 工具完成端到端校验。你不需要先成为 AI 专家,只要有一个能跑测试的项目,就可以跟着做。

2. TaoToken 前置:统一 Key 与 API 通道

在搭建 Harness 之前,先解决一个现实问题:你的 AI 工具可能不止一个。Codex CLI、Claude Code、Cursor、自建脚本,每个都配一套 Key 和 Base URL,管理成本高,切换模型时还要改环境变量。TaoToken 的作用是把这些统一到一个入口。

TaoToken 是一个 AI 模型 API 聚合服务,提供兼容 OpenAI 风格的接口。你可以在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后拿到 API Key,然后在各个工具里把 Base URL 指向 https://taotoken.net/api。这样无论是跑 Codex 做代码审查,还是用 Claude 做交叉评审,都走同一个 Key,计费和额度也集中管理。

具体操作分三步。第一步,登录后进入控制台创建 API Key,建议按项目或按工具分别建 Key,方便后续排查用量。第二步,在本地环境变量里配置:

export TAOTOKEN_API_KEY="sk-你的key" export OPENAI_BASE_URL="https://taotoken.net/api" export OPENAI_API_KEY="$TAOTOKEN_API_KEY"

第三步,验证通道是否通。用 curl 发一个最小请求:

curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "reply with ok"}] }'

如果返回里有choices字段,说明通道正常。这一步很关键,因为后面 Agent 自动审查、Linter 报错修复都依赖这个通道。如果你更习惯用现成的对话界面先试模型,可以直接打开模型对话页面;如果准备长期跑编码 Agent,建议了解 Coding Plan 的额度方案;需要管理多个 Key 时,API Keys 页面可以随时创建和吊销。

3. 可复制配置:AGENTS.md 骨架与 Linter 规则

3.1 AGENTS.md 骨架

AGENTS.md 是 Agent 每次会话开始时自动读取的文件。它的核心原则是“导航而非内容”——保持在一百行左右,指向更深层的 docs/ 目录,而不是把所有规则堆在一起。下面是一个可以直接改用的骨架:

# AGENTS.md ## 项目简介 前后端分离的订单管理系统,前端 React,后端 FastAPI,数据库 MySQL。 ## 技术栈 - 前端:JavaScript + React 19 + Vite 6 - 后端:Python 3.12 + FastAPI + SQLAlchemy - 数据库:MySQL 8.0 - 缓存:Redis 7 ## 快速开始 ### 后端 cd backend && uv sync uv run alembic upgrade head uv run uvicorn app.main:app --reload ### 前端 cd frontend && pnpm install pnpm dev ## 测试 make test-backend # 覆盖率要求 >= 80% make test-frontend make test-all ## 架构原则 - 后端依赖方向:types -> config -> repo -> service -> api,单向流动,禁止反向 - 前端依赖方向:types -> config -> services -> hooks -> pages,单向流动 - 前后端只通过 REST API 通信,前端不直接访问数据库 - 详见:docs/architecture/dependency-rules.md ## 编码规范 - 后端:Ruff(Lint + 格式化),mypy(类型检查) - 前端:ESLint + Prettier - 单文件不超过 200 行,单函数不超过 30 行 ## 常见陷阱 - 不要在 repo 层写业务逻辑,只做 CRUD - 不要在 api 层直接操作数据库,必须经过 service 层 - 前端不要用 fetch 直接调 API,统一走 services/ 层 - 数据库变更必须通过 alembic 迁移,禁止手动改表结构 ## 深入阅读 - 架构详情:docs/architecture/overview.md - 设计约束:docs/design/constraints.md - 技术债追踪:docs/plans/tech-debt.md

这个文件的关键在于“常见陷阱”部分。每次 Agent 犯错,你就把教训写进去,下次它就不会再犯。这比反复在 Prompt 里提醒有效得多。

3.2 后端 Linter:import-linter 强制分层

Python 项目用 import-linter 把分层规则变成可执行检查。在 backend 目录下创建.importlinter:

[importlinter] root_packages = app [importlinter:contract:backend-layers] name = 后端分层架构约束 type = layers layers = api service repo config types containers = app

这条规则的含义是:api 可以 import service,service 可以 import repo,但 repo 不能反过来 import service。一旦违反,lint-imports会直接报错并指出违规的 import 路径。配合 Ruff 做代码质量检查,在pyproject.toml里配置:

[tool.ruff] line-length = 100 [tool.ruff.lint] select = ["E", "F", "I", "N", "UP", "B", "SIM"] ignore = ["E501"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"]

3.3 前端 Linter:eslint-plugin-boundaries

前端用 eslint-plugin-boundaries 做同样的约束。先安装依赖:

cd frontend && pnpm add -D eslint @eslint/js eslint-plugin-boundaries

然后在eslint.config.js里定义元素类型和允许的依赖方向:

import boundaries from 'eslint-plugin-boundaries'; export default [ { files: ['src/**/*.{js,jsx}'], plugins: { boundaries }, rules: { 'boundaries/element-types': ['error', { default: 'disallow', rules: [ { from: 'types', allow: [] }, { from: 'config', allow: ['types'] }, { from: 'services', allow: ['types', 'config'] }, { from: 'hooks', allow: ['types', 'config', 'services'] }, { from: 'pages', allow: ['types', 'config', 'services', 'hooks'] }, { from: 'components', allow: ['types', 'config', 'hooks'] }, ], }], }, settings: { 'boundaries/elements': [ { type: 'types', pattern: 'src/types/**' }, { type: 'config', pattern: 'src/config/**' }, { type: 'services', pattern: 'src/services/**' }, { type: 'hooks', pattern: 'src/hooks/**' }, { type: 'pages', pattern: 'src/pages/**' }, { type: 'components', pattern: 'src/components/**' }, ], }, }, ];

这样当 Agent 在 pages 层直接 import repo 或数据库相关模块时,ESLint 会立刻报错,而不是等到运行时才暴露问题。

4. 验证请求与成功结果

配置写完之后,必须验证门禁真的能拦住违规代码。我建议用“故意写错再修复”的方式做端到端校验。

4.1 后端验证

先在后端制造一个反向依赖。在app/repo/order_repo.py里加一行:

from app.service.order_service import OrderService # 故意违规

然后运行:

cd backend && uv run lint-imports

预期输出类似:

app.repo.order_repo -> app.service.order_service (api -> service -> repo) LINTER ERROR: app.repo.order_repo -> app.service.order_service violates contract '后端分层架构约束'

看到这个报错,说明门禁生效。删掉违规 import,再跑一次,应该输出Contracts: 1 kept, 0 broken。

4.2 前端验证

在前端src/pages/OrderList.js里故意写:

import { getUserFromDB } from '../repo/userRepo'; // 违规

运行:

cd frontend && pnpm lint

预期报错:

error Dependency violation: 'pages' cannot import from 'repo' Allowed: types, config, services, hooks

4.3 接入 AI 工具做自动修复

门禁报错之后,让 Agent 根据报错信息自动修复。用 TaoToken 通道跑 Codex CLI:

npx codex --approval-mode full-auto \ "运行 make lint,根据报错信息修复所有架构违规,不要改变业务逻辑"

Agent 会读取 AGENTS.md 里的架构原则,结合 Linter 的具体报错,把违规 import 改成通过 service 层调用。修复完成后再次运行make lint,全部通过即表示闭环成立。如果你更想先手动确认模型输出质量,可以在模型对话里贴上报错信息,让它给出修复方案再落地。

5. 本篇常见错排查

5.1 lint-imports 报 “Could not find module”

通常是root_packages配置和实际包名不一致。检查.importlinter里的root_packages = app是否对应你项目里真实的 Python 包目录名。如果后端代码在src/app/下,需要改成root_packages = src.app或调整工作目录。

5.2 ESLint 报 “boundaries/element-types rule not found”

说明插件没装成功或配置里没注册。确认pnpm add -D eslint-plugin-boundaries执行成功,并且eslint.config.js的plugins字段里有boundaries。如果用的是旧版.eslintrc,配置方式不同,建议统一升级到 flat config。

5.3 Agent 不读 AGENTS.md

不同工具读取规则文件的位置不同。Codex 默认读项目根目录的 AGENTS.md,Claude Code 读 CLAUDE.md,部分工具读.cursorrules。如果你用的工具不识别 AGENTS.md,可以在工具配置里显式指定,或者建一个软链接:

ln -s AGENTS.md CLAUDE.md

5.4 TaoToken 请求返回 401

先确认环境变量TAOTOKEN_API_KEY是否在当前 shell 生效,用echo $TAOTOKEN_API_KEY检查。如果 Key 正确但仍 401,检查 Base URL 是否写成了https://taotoken.net/api而不是带/v1的完整路径——不同工具对 Base URL 的拼接方式不同,OpenAI 兼容工具通常只需要到/api。需要重新生成 Key 时,去 API Keys 页面操作。

5.5 Linter 通过但运行时仍然出错

Linter 只能检查静态的 import 关系,不能覆盖运行时动态调用。比如通过字符串反射调用、事件总线跨层通信,Linter 是拦不住的。这类问题需要在 AGENTS.md 的“常见陷阱”里明确写出禁止模式,并配合集成测试覆盖关键路径。

6. 把门禁接进 CI 与日常流程

本地验证通过后,把检查接进 CI,让每次 PR 都自动跑一遍。后端质量门禁的 GitHub Actions 片段:

name: Backend Quality Gate on: [pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 - run: cd backend && uv sync - run: cd backend && uv run ruff check app/ tests/ - run: cd backend && uv run mypy app/ - run: cd backend && uv run lint-imports

前端同理,把pnpm lint和pnpm test串进 workflow。这样 Agent 提交的 PR 如果违反架构约束,CI 会直接失败,Agent 收到失败反馈后可以自动修复,形成“违规 -> 检测 -> 修复”的闭环。

最后分享一个实用技巧:把 Linter 的报错信息格式改成对 Agent 友好的结构。普通报错只说“不允许”,Agent 需要自己推断怎么改;如果你在自定义规则里输出“违规文件、违规层级、允许的层级、修复建议”,Agent 一次修复的成功率会明显提高。这个改动不大,但能省下大量来回调试的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询