Activepieces工程师指出AI Coding Agent的表现完全取决于所在代码仓库的质量和组织方式。这是对AI编程环境的深度思考,对优化Agent工作流有启发价值。
Hi。我是 Activepieces 的一名工程师,这是一个开源自动化平台(想象一下 Zapier,但你可以自托管它并阅读源代码)。我以写 TypeScript 为生。现在我和机器人争论制表符和空格的问题。在过去的几个月里,我们的团队真的、真的投入到一个问题中:
为什么我们的 AI 编程智能体在这个代码库中表现很差,但在我笔记本上的空白 Next.js 项目中却表现得非常好?
剧透:这不是模型的问题。永远不是模型的问题。是我。是这个代码库。是我接下来约 6000 字时间试图说服你称之为"AI 驾驭系统"的东西。
以下是我的观点。我想小心对待它,因为互联网已经厌倦了"我们用 AI 提高了 10 倍生产力"的炒作帖。我也厌倦了。
Activepieces 有 12 多名工程师。我们的内部目标是每个工程师每天完成一个功能,最好从一个单一提示开始。我们还没有达到这个目标。但我们正在快速接近它,进度的速度才是真正有趣的。
我们是一个开源团队,试图在公开中找出如何设置一个真实的代码库,以便前沿模型实际上在其中有用。
我们的代码库是真实的。多租户。多版本。我们有一个企业级版本、一个云产品、一个前端、一个后端、一个引擎、一个 piece SDK,以及与数百个第三方 API 的集成。所以这不是一个玩具。
这篇文章是我们迄今为止的答案。它不是最终答案。它是将我们从"Claude Code 不断发明不存在的实体"转变为"Claude Code 刚刚交付了一个凭证管理器:一个扎实的计划、两次迭代,功能就可以投入生产"的答案。
💡 阅读时间检查:这是一篇长文。大约 30 分钟。有很多内容。我宁愿给你完整的画面,也不愿让你只有半成形的心理模型而在尝试使用时就崩溃。去喝杯咖啡。或者如果你想要核心内容,跳到"AI 驾驭系统的解剖"部分。
让我从 AI 编码讨论中让我疯狂的部分开始。
每两周,一个新模型发布。Twitter 失去理智。基准向前移动 3%。有人制作了一个图表。另一个人制作了一个反图表。有人发推说"Claude 已死,GPT 回来了。"六小时后:"GPT 已死,Gemini 回来了。"一位 YC 创始人说编程已经解决。一位脾气暴躁的后端工程师说编程已经注定失败。下周二重复。
同时,在试图真正使用这些工具的真实工程团队中,以下是发生的情况:
Engineer: "Add a webhook log feature."
AI: *creates a new entity*
AI: *forgets to register it in getEntities()*
AI: *writes a query without filtering by projectId*
AI: *imports from src/app/ee/ inside the CE codebase*
AI: *uses PUT instead of POST*
AI: *makes up a function called safeFetch that does not exist*
Engineer: "No not do that PLEASE..., Remember to use our brand colors next time...., F**ck your 18th grandma..."
那是模型的错吗?
有点。但主要不是。模型不知道你的代码库有多租户规则。它不知道 TypeORM 不会在你的项目中自动发现实体,你必须手动注册它们。它不知道 src/app/ee/ 是企业版,从社区版导入它会为自托管者破坏构建。它不知道你在三年前非常具体地决定每个创建和更新端点都使用 POST,因为 PATCH 语义曾经引起过一个 bug。
它不知道这些,因为你没有告诉它。你把一个前沿的大脑交给了一个有 200,000 个文件的鬼屋,没有地图、没有规则、没有词汇表、没有"这是我们说 piece 与 plugin 的含义",然后当它绊到耙子时你生气了。
💡 重新框架: 90% 的 AI 编程团队现在的瓶颈不是模型能力。是上下文工程。模型很好。模型很棒。模型被要求在没有图表的黑暗房间里做脑外科手术。
以下是没人想承认的事实:你的代码库有部落知识。很多。它生活在你高级工程师的头脑中。它生活在 2022 年的 Slack 线程中。它生活在没人能搜索的 PR 评论中。它生活在"我们不在这里这样做"的肌肉记忆中。
当一个新的人类工程师加入你的团队时,他们在几周的结对、代码审查和犯错中获得部落知识。到第三个月,他们就很有生产力。
当你让 AI 智能体入职时,除非你建立一个驾驭系统,否则你每个会话都从零开始这个过程。
好的,让我定义这个术语。我一直在使用它,就像你已经同意用我使用它一样,我们甚至还没有握手。
AI 驾驭系统(名词):代码库内的一组文件、文件夹、约定和基础设施,将 AI 智能体的原始功率转化为可靠的、项目特定的输出。它对你的 AI 就像鞍对马一样。那匹马已经很强了。鞍就是让你实际骑到某个地方的东西。
我会继续使用它。它比"agent scaffolding"(听起来像 2014 年的网络框架)、"context engineering"(技术上是对的但感觉不对)或"AI 编码设置"(描述性但无聊)更好的术语。驾驭系统是正确的隐喻。你不是在制造力量。你是在引导它。
一个真实的 AI 驾驭系统至少有这些部分。我们将逐个讲述每一个:
总是开启的指令,在每个会话中加载。架构规则、约定、陷阱。
安全反射。捕捉最昂贵的错误的微小规则。
按需上下文。功能文档、词汇表、当智能体需要时获取的模式。
编码化的工作流。斜杠命令或技能,将"做这五步的事情"变成一个命令。
作用域子智能体。保持在自己轨道上的专门智能体。
外部集成。MCP 给智能体访问你的工具(Linear、Postgres、浏览器)的权限。
会话卫生。清除上下文、并行工作和知道何时放弃并重新开始的实践。
如果你有所有这些,你有一个驾驭系统。如果你没有任何一个,你有一个 CLAUDE.md,里面有三个项目符号说"使用 TypeScript",你想知道为什么 AI 很差。
在我向你展示驾驭系统之前,我必须让你相信听起来很无聊但在我们整个工作流程中单一最高杠杆实践的东西:
驾驭系统存在以启用更好的规划。规划就是装运功能的原因。
让我解释。有一个幻想版本的 AI 编码是这样的:
"添加项目级分析仪表板。" [Enter] 45 分钟后,PR 是开放的,测试通过,Slack ping 触发。
这个幻想是真实的,但它是最后 10%。它是在你做了使其成为可能的工作后发生的。使其成为可能的工作看起来像这样:
你写一个清晰的简报。不是"添加分析。"类似的东西:"添加项目级分析仪表板,显示流运行计数、成功率和过去 7、30、90 天的平均持续时间。项目作用域。CE 功能。对具有 READ_RUN 权限的用户可见。"五分钟的写作。节省三十分钟的智能体猜测错误。
你进入规划模式。在 Claude Code 中,那是 Shift+Tab 两次。智能体不写一行代码。相反,它探索。它阅读相关的功能文档。它看现有的模式。它问你问题。("这应该被缓存吗?你想要实时的吗?时间范围选择器应该是查询参数还是状态?")它提出一个计划:要创建哪些文件、要更改哪些文件、API 形状是什么、测试会是什么样子。
你审查并细化计划。这是整个工作流程 80% 价值的来源。你还没有审查代码。你在审查意图。如果计划是错误的,代码将是错误的,你将花两个小时调试本应是五分钟计划修复的东西。修复计划。
你审查并优化该计划。这是整个工作流中 80% 价值的来源。你还不是在审查代码。你在审查意图。如果计划错了,代码就会错,你将花两个小时调试本应只需五分钟修复的计划问题。修复计划。
你退出计划模式,让它执行。现在它编写代码。由于你在前期花了十五分钟把计划搞对了,代码通常也是对的。
你退出计划模式,让它执行。现在它编写代码。由于你在前期花了十五分钟把计划搞对了,代码通常也是对的。
这就是工作流。它与"凭感觉编程"相反。它与"只管直接提示然后祈祷"相反。说实话,它和优秀工程师一直以来使用的工作流是一样的:动手之前先思考。唯一的区别是,现在你与一个阅读过比任何人都多的代码的伙伴一起思考。
💡 来自吃过苦头的人的专业建议:如果你已经在同一个问题上纠正过 Claude 两次,就停止纠正。执行 /clear 清除会话,用你刚学到的东西重新改写你的提示,然后重新开始。纠正循环会增加上下文负担。当你已经来回四次时,智能体就陷入了相互矛盾指令的迷雾中,会继续出错。一个有更好提示的干净会话总是优于一个有完美提示的被污染的会话。
这就是为什么 harness 很重要。Harness 是使计划模式工作的东西。没有 harness,计划模式就只是智能体随机读取文件并猜测。有了 harness,计划模式就是智能体读取你有意写的 60 行特性文档来回答它即将提出的问题,并生成一个实际基于你的代码库运作原理的计划。
好的。让我们谈论 harness 本身。
我们在 Activepieces 使用的心智模型是将智能体上下文视为分层缓存。不同的层在不同的时间加载,大小不同,所以模型在恰好的时刻获得正确的信息,而不会浪费上下文。三个原则:
小文件总是加载。大文件按需加载。
按特性的文档包含你无法通过阅读代码获得的部落知识(版本门控、副作用、注册陷阱)。
工作流被编纂为技能或斜杠命令,而不是散文。所以模型对常见任务有固定的路径。
以下是层级分解,包括我们在生产中实际运行的大小:
每个会话加载的总上下文:大约 150 行。Claude 在质量开始下降之前大约有 150 个指令槽位。我们全部使用。没有浪费。
现在让我为你详细介绍每一层。
这是每个会话中都会加载的文件,不管你在做什么。它是智能体在读其他内容之前首先要读的东西。基本上,它是你的代码库的宪法。
架构规则(技术栈是什么?拓扑是什么?)
有约束力的编码规则(无 any、无类型转换、命名参数函数、文件顺序)
命令(如何构建、lint、测试?)
项目特定的陷阱(多租户、版本门控等)
PR 规则(标签策略、分支命名)
不应该包含什么:
通用的"要有帮助并写好代码"之类的东西
智能体已经知道的任何东西(例如"使用 TypeScript 类型"是浪费字节)
长示例(改为链接到文件)
按特性的详细信息(那些放在特性文档中)
我们的根 AGENTS.md 大约 55 行。就这样。它非常简短,这是有意为之的。每一行都必须通过审核才能获得一席之地,因为每一行都会加载到每个会话中,而令牌不是免费的。
我们根文件中的一些规则:
多租户:每个查询都按 projectId 或 platformId 过滤。遗忘此规则会导致跨租户数据泄漏。这个规则在三个不同的地方。我们毫不掩饰。
版本:CE/EE/Cloud 通过 hooksFactory 分离。永远不要从 CE 导入 src/app/ee/。这会破坏自我托管者。大家都倒霉。
实体注册:TypeORM 不能自动发现。新实体必须添加到 getEntities()。两步陷阱是我们在写下来之前看到的最常见的错误。
HTTP:所有创建和更新都用 POST。永远不要 PUT 或 PATCH。这是一个惯例,不是物理定律,但有一个惯例意味着智能体不必猜测。
出站 HTTP:必须通过 safeHttp(SSRF 保护)。我们为数不多的安全关键惯例之一。
前端:通过 key 属性重置表单,而不是 form.reset()。服务器错误上报到 root.serverError。这些是来之不易的 react hook form 模式。
你会注意到这些不是"一般的最佳实践"。它们是"这是我们在这个仓库中的做法"。这就是重点。通用规则是无用的,因为模型已经知道它们。具体规则是黄金,因为模型无法知道它们。
💡 专业建议:在编写你的 AGENTS.md 时,问自己:"一个新的 frontier 模型能通过阅读我的代码想出这个吗?"如果是,就删除它。如果否,如果它需要部落知识,就写下来。你的 AGENTS.md 是你以一种很好的方式让你的部落失去其部落知识的地方。
我们还在每个包中保持 CLAUDE.md 文件:
/packages/server/AGENTS.md。Fastify + TypeORM + BullMQ 栈、控制器模式、电子邮件模板规则、N+1 预防。
/packages/web/CLAUDE.md。React + Tailwind + Shadcn + react hook form、useEffect 反模式、ICU i18n 语法。
/packages/shared/CLAUDE.md。Zod schema + z.infer 模型模式、关键枚举扩展、版本撞号策略。
/packages/pieces/CLAUDE.md。Piece SDK 快速入门、身份验证类型、piece 上下文 API。
/packages/server/engine/CLAUDE.md。引擎错误处理规则(ExecutionError 子类)。
这些只在智能体在该包中工作时加载,这保持了上下文精简。在处理前端特性?你不需要加载引擎错误处理规则。
这是我最喜欢的 harness 部分之一,因为它很便宜且高杠杆。
这些是小文件。每个三到五行。它们在每个会话中加载。它们不是架构文档。它们不是教程。它们是安全反射:捕捉最昂贵错误的简短指令。
每个文件只是几行"如果你在做 X,你必须也做 Y"。就这样。它们很便宜是因为它们很小。它们杠杆高是因为它们捕捉我们见过的最昂贵的 bug。跨租户数据泄漏。破碎的自我托管构建。缺失的迁移。SSRF 漏洞。
你可以把它们想象成智能体的"肌肉记忆"。如果你的 AGENTS.md 是工程手册,你的 rules 文件夹就是飞机座椅背后的塑封安全卡。简短。视觉化。始终存在。
💡 专业建议:一个很好的测试来判断什么属于 .claude/rules/:如果你曾经因为这种类型的错误而不得不还原 PR 或写过事后分析报告,那就是一条规则。规则是结晶化的伤疤组织。
这是我们花最长时间弄清楚的部分,也是我最引以为豪的部分。
我们有 40+ 个特性文档,每个模块一个:flows、pieces、agents、ai-providers、alerts、analytics、api-keys、app-connections、audit-logs、authentication、custom-domains、file-storage、flow-runs、folders、knowledge-base、mcp、oauth-apps、platform、projects、scim、secret-managers、signing-keys、store-entry、tables、templates、triggers、user-invitations、users、webhooks,加上仅限企业的 ee-* 变体。
每个文件大约 60 行。每个文件都有固定的形状:
摘要。一个段落。
关键文件。前端、后端、共享(带路径)。
版本可用性。CE / EE / Cloud。
域术语。指向词汇表的链接。
实体。逐列的 schema。
端点 / 服务方法。表格形式。
为什么这个存在?因为替代方案,坏的替代方案,是每次智能体处理,比如,webhooks 模块时,它都必须读 30 个文件来弄清楚发生了什么。这在令牌中很昂贵,在延迟中很昂贵,结果通常是错的,因为智能体不得不猜测文件之间的关系。
使用特性文档,智能体读一个约 60 行的文档并获得实体 schema、版本门控、副作用图、相关端点和规范的域术语。一次性。然后它去写代码。
我们还有一个 GLOSSARY.md。它是一个规范的术语表,按域集群组织(Automation Core、Data & Storage、Pieces & Integrations、Platform & Multi tenancy 等)。它包括一个"要避免的别名"列来对抗同义词漂移。当 AI 生成代码时发生的最糟糕的事情是当它为一个已经存在的概念创造一个新术语时。现在你有一个 WebhookEvent 和一个 WebhookHook 和一个 HookedWebhook,都指同一个东西,散布在整个代码库中。
💡 专业提示:不要试图一次性写完所有功能文档。你会精疲力竭,最后写出一堆垃圾。更好的方式是:每当你要在一个没有文档的模块中开发新功能时,先编写文档。把它作为 Plan Mode 的任务简报。这样,功能甚至还没发布,这份文档就已经体现了自身价值,而且你还为下一次开发多准备了一份文档。
.agents/skills/:编码化的工作流如果说功能文档描述的是代码库中有什么,那么技能描述的就是如何在代码库中做事。
技能是一条映射到固定操作流程的斜杠命令。你不再需要编写类似“要添加一个实体,首先创建 schema,然后在 getEntities() 中注册它,接着添加 migration……”这样的说明文字,而是创建一个名为 /add-entity 的技能,将这套流程写入其中,然后让 AI 智能体逐步执行。
目前,我们提供了九项技能:
/add-endpoint。Fastify Zod controller + securityAccess config + module registration。
/add-entity。TypeORM EntitySchema + getEntities() + repository pattern。
/add-feature。全栈功能添加:shared types、entity、migration、service、controller、frontend、tests。
/db-migration。通过 CLI 生成,将 MigrationInterface 修改为 Migration,设置 breaking/release/down(),完成注册,并处理 PGlite 与 CONCURRENTLY 的差异。
/piece-builder。构建第三方集成:研究 API,通过 CLI 搭建脚手架,实现 actions 和 triggers,配置 tsconfig,构建并测试。
/ubiquitous-language。在开发任何新功能之前,强制检测功能是否重叠。
/agent-browser。用于测试的浏览器自动化 CLI。
/playwright-e2e-testing。Playwright 模式参考。
/mintlify。文档站点编写约定。
/piece-builder 技能是渐进式披露的绝佳示例。顶层的 SKILL.md 是一份检查清单,但它还附带了八份子参考资料:auth-patterns、action-patterns、trigger-patterns、props-patterns、common-patterns、ux-guidelines、output-quality、piece-types。这些模式总计约 2,000 行。AI 智能体只会在执行到相应步骤时,加载当时需要的子参考资料。因此,你可以获得极其深入的专业能力,而不必让 AI 智能体一开始就吞下 2,000 行内容。
/ubiquitous-language 技能的有趣之处则有所不同。它是一项负向技能。它的职责是阻止 AI 智能体直接编写代码,而是先检查你要求的功能是否已经存在,只是可能使用了不同的名称。它会检查 .agents/features/、components、routes、shared types 和 plan flags,并维护术语表。正是这项技能,防止 AI 智能体在六个月内第三次重新发明 webhooks,仅仅因为它不知道我们已经有 webhooks 了。
💡 专业提示:技能非常有用,因为它们编码了“这是我们总会忘记的事情”。每当你进行代码审查并写下“你忘记添加 migration”时,这就是一项技能。每当你写下“你需要在 getEntities() 中注册它”时,这也是一项技能。技能取代了代码审查意见,因为 AI 智能体不会再犯同样的错误。
.claude/agents/:专用子智能体它们并不比主智能体更强大,也不比主智能体更聪明。它们能访问的东西更少——这是有意为之,而且这正是关键所在。
每个子智能体都有固定指定的模型(sonnet)、明确的工具白名单,以及一段说明其职责范围的简短介绍。重点并不是“这个智能体拥有特殊能力”,而是限定范围。Web 智能体不会进入 server 目录树。changelog 智能体不会修改源代码。server 智能体也不会突然决定重构你的 React 组件。
它的实用程度远超你的想象。当主智能体把后端任务委派给 server 子智能体时,该子智能体只会加载与 server 相关的上下文。它不会意外游荡到 frontend,也不会因为阅读了其他领域的文档而把自己搞糊涂。它在一个更小、更专注的世界中工作,因此也能在这个世界中表现得更好。
💡 专业提示:子智能体并不是为了“赋予智能体更多能力”,而是为了“减少智能体把事情搞砸的方式”。约束本身就是一种功能。
.agents 符号链接技巧好吧,这一节全是专业技巧,而我对此非常兴奋。
问题在于,现在大概已经有九种 AI 编程工具。Claude Code 需要 .claude/,Cursor 需要 .cursor/,Aider 需要 .aider/,Continue 需要 .continue/。每出现一种新工具,它都想要一个属于自己的目录,里面装满它自己的一套约定。而它们需要的内容又大致相同:rules、skills 和 agent configs。
如果你用最直接的方式支持所有工具,就需要维护九份相同文件的副本。这将是一场维护噩梦。每当你更新一条规则时,都必须记得在九个地方同步更新。你做不到的。你一定会忘记。内容会逐渐产生偏差。最终,不同的 AI 智能体会按照不同的规则运行,而你只能困惑于它们为什么会生成不同的代码。
我们的解决方案是:只保留一个事实来源,然后把它符号链接到各个位置。
我们将 .agents/ 作为规范目录。skills、rules、feature docs——所有内容都放在 .agents/ 中。然后创建符号链接:
.claude/skills -> ../.agents/skills
.cursor/rules -> ../.agents/rules
# ...etc
下个月出现新工具时,我们只需添加一个符号链接。完成。我们已经支持它了。内容仍然只保存在一个地方。
这种做法听起来只是一个微小的实现细节,实际上却是一个承重级决策。整套支撑体系必须与工具无关,否则它终将消亡。模型会变化,工具也会变化。你的支撑体系中的内容——你的约定、功能文档和技能——才是应该比任何单一 CLI 都更加长寿的部分。
💡 专业提示:即使你现在只使用 Claude Code,也要按照明天可能需要支持另外三种工具的方式来组织支撑体系。将内容保存在 .agents/(或者你喜欢的其他通用名称)中,再从 .claude/ 创建符号链接。当你决定评估 Cursor 或某个新智能体时,你会感谢过去的自己。
我们还维护了几个支撑体系级别的配置文件:
.claude/settings.json + .claude/settings.local.json。Permissions、env vars、hooks。
claude/worktrees/。用于基于独立 worktree 的并行运行(稍后会进一步介绍)。
到目前为止,我所描述的一切都存在于你的仓库中。但真正的工程师所做的工作并不只是“编辑文件”,而是“编辑文件,然后到 Linear 查看工单详情,再查询数据库以了解生产数据的实际情况,接着在浏览器中测试变更,然后编写一份 chan