title: 从一次 tenantId 联调 bug,看我们该怎么给 AI 项目补齐 harness author: Gamehu date: 2026-03-07 20:30:00 tags:
前几天我读了 OpenAI 那篇文章:Harness engineering: leveraging Codex in an agent-first world。
我读完最大的感受不是“AI 又强了”,而是另一个更接地气的结论:
很多时候,不是模型不够强,而是你的工程环境没有把“正确性”暴露给模型。
这两天我正好在自己项目里处理一个非常典型的问题:运营端新增门店和校验门店名时,如果和其它租户重名会误报;员工后台接口也有类似的租户范围问题。表面上看只是个 tenantId bug,但整个过程把一个事实暴露得很彻底:
AI 想高效干活,前提不是 prompt 更长,而是 harness 更清楚。
最初的现象其实很迷惑:
admin/store/checkNametenantId如果只看 Controller 参数,你会以为“不是都传进来了吗”。
但真正往下追,就会发现系统里其实混着两套租户来源:
tenantIdTenantContextHolder理论上,TenantContextHolder 也不是错的。因为网关或 filter 确实会从 header 里解析租户并写入上下文。问题在于:
后台管理接口的业务语义,不是‘当前会话属于哪个租户’,而是‘当前运营账号要操作哪个目标租户的数据’。
这两个概念,在多租户后台系统里根本不是一回事。
所以这次真正的问题不是一句“代码写错了”,而是:
系统没有把“哪一个租户才是业务真相”表达得足够显式。
这就是 harness 问题。
那篇文章里有几层意思我特别认同,我这里不逐字复述,只说我自己的理解。
如果模型只能读代码,看不到:
那它就很容易陷入一种“静态推理正确,动态结论错误”的状态。
这次我们项目里就连续遇到了:
checkName 返回 true/false 的语义,调用方理解反了这些都不是大算法问题,而是工程上下文没有被收束成一个可验证的工作台。
以前很多项目的 AGENTS.md、模块说明文档,最后都会越写越长。
每次踩坑补一条,最后谁都不想看,AI 也很难稳定执行。
所以我这次顺手把项目里的规则做了一个调整:
AGENTS.md 和 CLAUDE.md 保留高层原则docs/testing/这不是为了“文档好看”,而是为了让规则能被持续修正,而不是散落在三个地方互相漂移。
{% asset_img 1.jpg %}
如果规则只是:
tenantId那它还是有点抽象。
真正有效的规则必须长成下面这样:
tenantId=A/B一旦规则写到这个粒度,AI 和人都更难自欺欺人。
结合这次联调前后对比,我最后把项目里的规则改成了三层。
我在项目根目录 AGENTS.md 和 goodsop-app-server/CLAUDE.md 里增加了一个明确入口:
docs/testing/README.mddocs/testing/admin-http-harness.mddocs/testing/tenant-data-scope.md意思很简单:
入口文档只负责告诉 AI“去哪儿看”,真正经常变化的实战规则集中维护。
这次新增的 admin-http-harness.md 里,我重点固化了几件事:
http://localhost:9999tenantId这几条看起来朴素,但非常关键。因为 agent 一旦没有这些硬边界,就会在“如何拿 token”“是不是该直连服务”“这个请求到底算不算验证”上浪费很多轮次。
tenant-data-scope.md 里,我把这次最核心的结论直接写死了:
tenantId 为准TenantContextHolder 是上下文机制,不是后台业务真相checkName/checkPhone 必须同时校验接口契约和逻辑删除语义尤其是第三点,这次特别有代表性。
我们一开始看到有些门店名“库里明明有记录,接口却返回可用”,很容易怀疑代码没改对。
后来一查数据库才发现:那几条是逻辑删除记录。也就是说,数据库里有行,不等于业务上仍占用名称。
这个结论如果不被写进规则里,下次还会重复争论。
{% asset_img 2.jpg %}
如果只看代码,这次改动其实不算复杂,核心就是两件事。
tenantIdTenantContextHoldercheckName 的返回在失败时没有稳定给出 data=falseadmin/store/*、admin/consultant/* 显式把 tenantId 往下传TenantBroker.applyAs(tenantId, ...)checkName 改成:
data=truedata=falsetenantId=5584 与 tenantId=1 对照请求这里最关键的,不是“把某个 if 改对了”,而是从“我觉得这样应该对”变成了“我能证明它对,而且能解释为什么”。
这就是 harness 的价值。
{% asset_img 3.jpg %}
很多人谈 AI Coding,喜欢把重点放在模型选择、prompt 技巧、上下文窗口大小。
这些当然重要,但我现在越来越觉得,对真实业务项目来说,下面这些东西更值钱:
不是“你自己去测一下”,而是给出:
很多团队的问题不是没有规则,而是规则散在聊天记录、群公告、项目文档、某个人脑子里。
一旦 AI 进场,这种问题会被放大得更厉害。
因为 AI 特别依赖“哪个文档才是 system of record”。
这次规则改完以后,我更想补的是一套更完整的 harness,而不只是几段说明文档。
如果继续做,我下一步大概率会补这些东西:
scripts/verify-admin-store.sh
page/checkName/create/closescripts/verify-admin-consultant.sh
page/checkPhone/create/update/resigndata/code/msg 这种容易被误解的接口因为写到最后我越来越确信一件事:
AI 工程化的竞争力,不是“谁的模型更像天才”,而是“谁先把自己的真实环境整理成一个不会误导 agent 的工作台”。
OpenAI 那篇文章给我的最大提醒,是别把 agent 当成一个只会补代码的聊天机器人。
它更像一个能力很强、速度很快,但极度依赖环境质量的工程协作者。
如果你的环境是模糊的:
那 AI 就会在这些噪音里反复打转。
但如果你把 harness 补起来,很多原来需要人肉盯着的事情,就会突然顺很多。
所以这次一个看似普通的 tenantId bug,最后给我的启发反而比 bug 本身更大:
以后优化 AI Coding,不只是继续追模型,也要继续做环境。