构建生产级AI智能体的六层设计模式与工程实践
1. 项目概述从“模型循环”到“生产级智能体”的鸿沟如果你最近在捣鼓AI智能体尤其是那些能写代码的AI助手你肯定对User - LLM - tool_use - execute - loop这个循环不陌生。这个模型循环简单到可以画在餐巾纸上它描绘了智能体工作的核心原理用户输入大模型思考并决定调用工具执行工具然后循环往复。听起来很美好对吧但当你真正想把一个这样的智能体投入生产环境让它可靠、安全、大规模地运行时你会发现餐巾纸上的循环图只是个开始真正的挑战在于循环之外的一切。这就是“缰绳”概念的由来。Anthropic在构建Claude Code时将围绕这个核心循环、确保其稳定运行的一整套工程体系称为Harness我更喜欢把它翻译成“缰绳”或“控制框架”。你可以把大模型想象成一匹拥有无穷潜力的野马而Harness就是骑手手中的缰绳、马鞍和全套装备。没有缰绳马儿可能四处乱窜甚至带来危险有了设计精良的缰绳你才能驾驭它的力量去往你想去的地方。keli-wen/agentic-harness-patterns-skill这个项目正是关于如何打造这样一套“缰绳”的实战指南。它不是又一个教你如何写提示词的教程也不是对某个API的简单封装。它是基于对Claude Code这个拥有超过51.2万行TypeScript代码的生产级AI编程助手运行时进行系统性源码分析后提炼出的六层设计模式和十一项深度参考。这些模式是跨运行时、可移植的设计原则无论你是在构建类似Claude Code、Codex CLI的智能体运行时还是在开发自定义的智能体插件或多智能体协调系统都能从中获得直接的启发和可落地的方案。2. 核心设计模式深度解析构建可靠智能体的六层基石为什么是六层因为在生产环境中一个智能体要可靠工作远不止是调用API那么简单。这六层模式层层递进共同构成了智能体从“能跑”到“好用”再到“可靠”的完整支撑体系。每一层都解决了一个关键的生产级问题。2.1 记忆层解决“会话失忆”难题智能体最让人头疼的问题之一就是“金鱼记忆”——每次对话都像第一次见面。简单的聊天记录持久化远远不够。Claude Code的实践揭示了一个关键洞察记忆必须分层管理。核心模式三类记忆三种信任等级指令记忆由人类精心编写和审核的长期记忆比如项目规范、团队约定、核心工作流程。这部分记忆可信度最高需要持久化存储并且变更需要人工审核。它相当于智能体的“长期行为准则”。自动记忆由智能体在会话中自动生成和写入的记忆例如“用户更喜欢用双引号”、“上次重构时在这个文件遇到了XX问题”。这部分记忆需要谨慎对待因为它可能包含错误或临时性信息。Claude Code的做法是将其与指令记忆物理隔离并可能引入自动过期或人工复核机制。会话提取记忆这是一种后台衍生的记忆。智能体在运行过程中会自动分析代码变更、错误日志、用户反馈并从中提取出潜在的模式或经验将其结构化后存入记忆。例如频繁出现的某个特定类型的编译错误及其解决方案。这部分记忆的信任度最低通常作为“建议”或“上下文”提供给模型而非“指令”。实操心得千万不要把所有记忆混在一个“记忆池”里。我在早期项目中吃过亏智能体把用户随口一提的临时偏好当成了金科玉律导致后续行为跑偏。正确的做法是为每类记忆建立独立的存储、加载和失效策略。指令记忆启动时全量加载自动记忆按会话或相关性加载提取记忆则作为可选的背景信息。2.2 技能层告别“工作流复读机”你是否厌倦了每次都要向智能体重新解释一整套复杂的代码审查或部署流程技能系统的本质是将这些可复用的工作流封装成懒加载的指令集。关键设计廉价发现与按需加载智能体的上下文窗口是宝贵资源。技能发现机制必须极其“廉价”——消耗的Token最好只占窗口的1%左右。Claude Code的模式是使用YAML Frontmatter作为技能“名片”里面只包含技能名称、触发关键词、简短描述和预估成本。只有当智能体真正决定激活某个技能时才会加载其完整的指令体。这里有个精妙的设计将最关键的触发语言放在技能描述的前部。因为大模型在处理长文本时存在“尾部忽略”的倾向。把“这个技能是干什么的、什么时候用”放在最前面即使后面部分因上下文限制被截断模型也能做出基本正确的判断。2.3 工具与安全层在力量与危险之间走钢丝给智能体强大的工具如文件读写、命令执行、API调用意味着赋予它巨大的能力也带来了巨大的风险。安全设计必须是默认且深入的。核心原则默认失败关闭任何工具调用在权限验证失败时必须明确拒绝而不是静默跳过或返回模拟数据。Claude Code的权限管道设计得像一个严格的安检流程它不仅有“通过/拒绝”的判断还会产生副作用记录每一次拒绝用于审计、根据上下文转换工具的模式如从“读写”降级为“只读”、并实时更新智能体的内部状态例如标记当前会话处于“高风险”模式。另一个重要模式是基于每次调用的并发控制而不是基于整个工具。这意味着即使同一个工具被频繁调用每次调用都需要独立通过并发检查防止单个工具被滥用导致系统过载。2.4 上下文工程层让智能体“看见”该看的上下文管理是智能体性能的命脉。塞太多浪费Token且干扰判断塞太少模型缺乏必要信息。Claude Code将其归纳为四种核心操作选择即时加载。不是一次性把所有文件都塞进上下文而是根据智能体当前的任务预测它可能需要什么在它真正需要的那一刻才去加载。这需要建立一套高效的文件索引和相关性预测机制。写入学习循环。智能体在解决问题过程中产生的新知识如对代码的理解、总结的规律需要被有选择地、结构化地写回上下文供后续步骤使用形成一个持续学习的闭环。压缩反应式精简。当上下文接近上限时不能粗暴地截断。Claude Code采用“快照标签”和“恢复指针”的方式将大段的、暂时不重要的代码替换为一个带有元数据的摘要如果后续需要细节可以通过指针快速恢复。隔离委托边界。当启动子智能体处理特定任务时需要严格划定它能访问的上下文范围通常是零继承或单级继承防止信息泄露和权限扩散就像给子进程设定一个“沙盒”。2.5 多智能体协调层并行而不混乱当任务复杂到需要多个智能体协同工作时如何避免它们互相冲突或陷入混乱这里有三种经过验证的协调模式协调者模式一个主智能体协调者负责分解任务和汇总结果它自己不执行具体工作而是将子任务分发给多个零继承的工作智能体。工作智能体只接收完成任务所必需的最小信息集。分叉模式主智能体创建一个或多个继承了其全部或部分上下文的子智能体去并行处理不同的任务分支。完成后子智能体的状态和结果被合并回主智能体。这适用于任务高度相关、需要共享大量背景知识的场景。蜂群模式一组平等的智能体共享一个扁平的任务列表各自从中领取任务。它们之间通过共享的、结构化的内存如黑板模型进行间接通信。这种模式适合任务相对独立、可大规模并行的场景。关键点协调者的核心职责不是简单地转发任务而是综合理解各个子智能体的产出去重、解决冲突、提炼出最终答案。2.6 生命周期与可扩展性层为智能体注入秩序生产系统需要明确的启动、运行、关闭流程以及可插拔的扩展点钩子。这一层确保了智能体框架的健壮性和可维护性。钩子信任是全有或全无的一个钩子要么被完全信任拥有较大的执行权限要么不被信任其行为受到严格限制。不存在中间地带这简化了安全模型。任务驱逐是两阶段的当需要终止一个长时间运行的任务时先发送一个“优雅关闭”信号给它一个清理现场的机会如果超时则进行强制终止。这避免了资源泄漏和数据损坏。启动顺序是依赖导向的框架的启动过程像初始化一个依赖注入容器组件的初始化顺序由它们的依赖关系决定。其中“信任”是一个关键的转折点——在信任边界被建立之前许多高风险的操作是被禁止的。3. 从模式到实践构建你自己的智能体缰绳理解了这六层模式我们该如何动手直接照搬Claude Code的代码可能不现实因为你的技术栈和需求可能不同。但我们可以遵循其提炼出的“黄金法则”从最痛点开始。3.1 第一步诊断你的核心痛点不要试图一次性实现所有模式。对照下表找到你最迫切需要解决的问题你遇到的症状最可能相关的模式建议的第一个行动智能体记不住上次对话的内容每次都要重说。记忆在你的会话状态中明确区分“系统指令”持久化和“会话历史”临时性。为系统指令建立独立的存储和加载机制。每次都要花大量篇幅向智能体解释固定的工作流。技能将你最常用的一个工作流如“运行单元测试”写成一份独立的Markdown文档并为其设计一个简单的触发词如#run_tests。修改你的提示词模板使其能引用并加载这份外部文档。担心智能体执行rm -rf /或调用危险API。工具与安全为你最危险的一个工具如“执行Shell命令”实现一个“权限门”。在工具执行前加入一个硬编码的权限检查逻辑比如检查命令中是否包含rm、format等危险关键词并立即拒绝。上下文总是爆炸或者智能体看不到关键文件。上下文工程实现一个最简单的“选择”逻辑在智能体试图读取一个文件时不是直接给全文而是先给一个文件列表和摘要让它“选择”需要深入查看的文件你再加载选中文件的内容。需要多个智能体分工但它们互相干扰或重复工作。多智能体协调尝试实现“协调者模式”。主智能体只负责拆解任务和总结将具体的编码任务分发给一个配置了严格上下文隔离只能看到指定文件的子智能体进程。想在某些关键节点如任务开始前、结束后插入自定义逻辑。生命周期定义两个钩子beforeTaskStart和afterTaskComplete。在智能体主循环的相应位置调用这些钩子如果存在。这是你插件系统的基础。3.2 实现记忆层的具体策略让我们以“记忆层”为例深入一个可落地的实现方案。假设我们使用Node.js环境。1. 设计记忆存储结构不要用一个JSON文件存所有东西。建议按类型分目录memory/ ├── instructions/ # 指令记忆 │ ├── project_guidelines.md │ ├── code_style.md │ └── review_checklist.md ├── auto_memory/ # 自动记忆 │ ├── session_20240515.json │ └── user_preferences.json └── extracted/ # 提取记忆 └── common_errors_fixes.json2. 实现记忆加载器class MemoryManager { constructor() { this.instructions new Map(); this.autoMemories []; this.extractedInsights []; } async loadCoreInstructions() { // 启动时加载所有指令记忆 const instructionDir ./memory/instructions/; const files await fs.readdir(instructionDir); for (const file of files) { const content await fs.readFile(path.join(instructionDir, file), utf-8); const key path.basename(file, .md); this.instructions.set(key, content); } return 已加载 ${this.instructions.size} 条核心指令。; } async loadRelevantAutoMemory(sessionTopic, limit 5) { // 根据会话主题从自动记忆中加载最相关的几条 // 这里可以简单实现为关键词匹配生产环境可用向量检索 const allMemories await this.loadAllAutoMemories(); const relevant allMemories .filter(m this.calculateRelevance(m.content, sessionTopic) 0.5) .slice(0, limit); this.autoMemories relevant; return relevant.map(m [历史记录] ${m.content}).join(\n); } async saveAutoMemory(agentAction, result, context) { // 智能体执行重要操作后自动生成记忆 const memoryEntry { id: auto_${Date.now()}, timestamp: new Date().toISOString(), action: agentAction, resultSummary: this.summarizeResult(result), context: context, // 可以添加一个“置信度”字段由模型或规则生成 confidence: medium }; // 保存到文件可按日期分片 const filePath ./memory/auto_memory/session_${new Date().toISOString().split(T)[0]}.json; await this.appendToFile(filePath, memoryEntry); } }3. 在提示词中集成记忆在你的系统提示词模板中预留位置动态插入记忆你是一个AI编程助手。以下是你的核心工作准则 {{CORE_INSTRUCTIONS}} 以下是本次会话相关的历史记录和用户偏好 {{RELEVANT_AUTO_MEMORY}} 请基于以上信息完成用户的任务。避坑指南自动记忆的保存时机很重要。不要每句话都存这会产生大量噪音。应该在智能体完成一个有明确产出的步骤后如成功修复一个错误、完成一个函数重构再触发记忆保存。同时记忆的总结summarizeResult最好也由智能体自己生成这比单纯保存原始输出更有价值。3.3 实现技能系统的懒加载机制技能系统的核心是“廉价发现”。下面是一个简化版的技能注册与发现实现1. 技能元数据契约YAML Frontmatter每个技能文件开头必须是标准的YAML Frontmatter。--- name: “run_unit_tests” description: “运行项目的单元测试套件并解析测试结果。” trigger_keywords: [“test”, “unit test”, “run tests”] estimated_cost: 120 # 预估加载该技能完整内容所需的token数 version: “1.0” --- 这里是技能的完整、详细的指令内容可能很长...2. 技能发现服务class SkillRegistry { constructor(skillsDir) { this.skillsDir skillsDir; this.skillIndex []; // 只存储元数据 } async buildIndex() { const files await fs.readdir(this.skillsDir); for (const file of files) { const content await fs.readFile(path.join(this.skillsDir, file), utf-8); const frontmatter this.extractFrontmatter(content); // 解析YAML this.skillIndex.push({ ...frontmatter, filePath: file, fullContent: null // 懒加载初始为空 }); } // 按常用度或名称排序 this.skillIndex.sort((a, b) a.name.localeCompare(b.name)); } getSkillListForPrompt() { // 生成供模型选择的廉价列表只包含名称、描述和关键词 return this.skillIndex.map(s - [${s.name}] ${s.description} (触发词: ${s.trigger_keywords.join(, )}) ).join(\n); } async loadSkillContent(skillName) { // 只有当模型决定使用某个技能时才加载其完整内容 const skill this.skillIndex.find(s s.name skillName); if (!skill) throw new Error(Skill not found: ${skillName}); if (skill.fullContent null) { const fullPath path.join(this.skillsDir, skill.filePath); const content await fs.readFile(fullPath, utf-8); skill.fullContent this.stripFrontmatter(content); // 去掉Frontmatter只留内容 } return skill.fullContent; } }3. 在智能体循环中集成在每次循环开始时将廉价的技能列表getSkillListForPrompt注入系统提示词。当模型输出中包含类似use_skill:run_unit_tests的标记时运行时拦截这个输出调用loadSkillContent加载完整技能指令将其插入下一轮对话的上下文然后让模型基于这个增强的上下文继续执行。注意事项技能描述的撰写质量直接决定发现效率。务必把最核心的、区别于其他技能的功能和最可能出现的触发词放在描述的前一两句。因为上下文窗口有限后面的描述可能被截断。4. 高级主题与避坑实战当你基本实现了上述模式智能体开始稳定工作后你会遇到一些更微妙、更棘手的问题。以下是我从实际项目以及分析Claude Code中总结出的高级避坑指南。4.1 上下文管理的“幽灵”问题过时缓存你实现了“选择”模式智能体能按需加载文件了。但有一天用户在外部的IDE中修改了一个文件而你的智能体还在使用内存中缓存的旧版本导致它的建议完全偏离。问题根源简单的文件缓存没有失效机制。解决方案实现一个带版本戳的缓存系统。class FileCache { constructor() { this.cache new Map(); // key: filePath, value: { content, mtime, version } } async getFile(filePath) { const stats await fs.stat(filePath); const cached this.cache.get(filePath); if (cached cached.mtime stats.mtime.toISOString()) { // 文件未修改返回缓存 return cached.content; } // 文件已修改或未缓存重新读取 const content await fs.readFile(filePath, utf-8); this.cache.set(filePath, { content, mtime: stats.mtime.toISOString(), version: (cached?.version || 0) 1 }); // 通知上下文管理器该文件已更新相关上下文可能需要失效 this.notifyContextManager(filePath, updated); return content; } }更复杂的场景下你需要监听文件系统的变更事件如Node.js的fs.watch或者让智能体在每次可能涉及文件读写的工具调用前主动声明它将要访问的文件路径由“缰绳”来保证它获取的是最新内容。4.2 多智能体协调中的“共识崩溃”你采用了“蜂群模式”三个智能体同时处理一个复杂问题的不同部分。但它们各自得出的结论互相矛盾并且都试图说服对方陷入无休止的辩论循环。问题根源缺乏一个权威的决策合成机制。解决方案引入一个明确的“裁决阶段”和“事实锚点”。定义输出模板在任务开始前就强制规定每个子智能体的输出必须遵循一个严格的JSON模板包含结论、主要证据、置信度和待澄清点等字段。设立裁决者这个裁决者可以是一个简单的规则引擎如“置信度高的胜出”也可以是另一个专门的“裁决智能体”它的提示词被设计为专注于评估矛盾、寻找共同点、并基于原始需求做出最终判断。事实锚点提供一份所有智能体都必须遵守的、不可争议的“事实清单”如项目需求文档、API接口规范。任何与事实锚点冲突的结论将被直接否决。// 一个简单的规则裁决器示例 function resolveConflict(agentOutputs) { // 1. 检查是否有结论直接违反事实锚点 const violations agentOutputs.filter(o violatesAnchor(o.conclusion, factAnchors)); if (violations.length 0) { return { finalDecision: 否决。结论 ${violations[0].conclusion} 与项目规范冲突。, reason: 违反事实锚点 }; } // 2. 如果结论一致直接通过 const uniqueConclusions [...new Set(agentOutputs.map(o o.conclusion))]; if (uniqueConclusions.length 1) { return { finalDecision: uniqueConclusions[0], reason: 共识达成 }; } // 3. 按置信度排序选择最高的 const sorted agentOutputs.sort((a, b) b.confidence - a.confidence); return { finalDecision: sorted[0].conclusion, reason: 最高置信度${sorted[0].confidence}, alternatives: sorted.slice(1).map(o o.conclusion) }; }4.3 工具安全中的“权限逃逸”你为每个工具都设置了权限检查但智能体通过巧妙的工具组合间接完成了它本无权限进行的操作。例如它没有直接删除文件的权限但它有写文件的权限于是它写了一个脚本然后有执行脚本的权限脚本里包含了删除命令。问题根源权限检查是孤立的、静态的没有考虑工具链的上下文。解决方案实施“动态权限链”和“意图分析”。权限上下文传递当一个工具A调用另一个工具B时或智能体短时间内连续调用有因果关系的工具权限系统应检查整个调用链的复合权限而不仅仅是单个工具。意图预审在智能体输出工具调用请求tool_use时运行时可以对其进行一次轻量级的“意图分析”。例如检测连续的工具调用是否在试图绕过某个安全边界。这可以通过一个简单的规则引擎或一个专门的小模型来实现。操作日志与实时审计所有工具调用无论成功与否都必须被详细日志记录并实时流式传输到一个监控面板。对于高风险操作如涉及文件删除、网络访问、系统命令甚至可以设置一个“二次确认”机制需要用户或管理员在短时间内批准。class SecurityAuditor { constructor() { this.sessionToolHistory []; } checkToolChainSafety(currentToolCall, history) { // 分析最近的历史看是否存在危险的模式 const recentHistory history.slice(-5); // 检查最近5次调用 const pattern this.detectDangerousPattern(recentHistory, currentToolCall); if (pattern FILE_DELETE_VIA_SCRIPT) { // 检测到模式先写脚本文件再执行脚本 return { allowed: false, reason: 安全策略禁止通过脚本执行间接文件删除操作。 }; } // ... 其他模式检测 return { allowed: true }; } detectDangerousPattern(history, current) { const tools history.map(h h.name).concat(current.name); const toolString tools.join( - ); if (toolString.includes(write_file) toolString.includes(execute_shell) current.args?.command?.includes(rm)) { return FILE_DELETE_VIA_SCRIPT; } return null; } }5. 性能优化与规模化考量当你的智能体开始处理大型项目或高频请求时性能会成为瓶颈。以下是几个关键的优化方向。5.1 上下文压缩的智能策略粗暴的截断会丢失关键信息。Claude Code采用的“反应式压缩”值得借鉴识别“冷”内容长时间未被提及或引用的代码块、旧的对话历史可以被标记为可压缩。生成智能摘要不要仅仅删除。用一个小模型或规则为这些“冷”内容生成一个简短的、结构化的摘要例如[函数calculateRisk位于src/engine/risk.js:50-120功能基于输入参数计算风险评分上次提及3轮对话前]。保留恢复指针在摘要中嵌入一个唯一指针如文件路径和行号范围。如果后续对话中模型需要细节缰绳可以根据指针快速将原始内容重新加载到上下文中。5.2 技能与工具的热加载在生产环境中你可能需要在不重启服务的情况下添加新技能或工具。技能热加载SkillRegistry可以监听技能目录的变化。当新的.md文件加入或旧文件被修改时自动重新构建内存中的技能索引。对于已加载的技能内容可以采用惰性更新即下次被请求时再加载新版本。工具热注册设计一个工具注册中心。新的工具可以通过一个标准的接口如一个registerTool函数动态注册自己。权限和安全检查逻辑可以作为装饰器在注册时自动包裹工具函数。5.3 多智能体系统的资源池化频繁创建和销毁智能体进程或LLM会话开销巨大。可以引入一个智能体资源池。池化管理初始化一组“冷”智能体实例已加载基础模型和系统提示。任务队列传入的任务被放入队列。空闲的智能体从池中取出为其注入具体的任务上下文会话历史、相关记忆、技能执行任务。上下文清理任务完成后清除该智能体实例中与任务相关的临时上下文将其状态重置回“冷”状态放回池中供下一个任务使用。这避免了为每个请求都支付完整的模型初始化成本。6. 测试与监控确保缰绳始终牢固没有测试的框架是危险的。对于智能体缰绳测试需要分为多个层面。6.1 单元测试测试工具与权限为每个工具函数编写单元测试特别是权限检查逻辑。describe(File Write Tool Permission Gate, () { it(should allow writing to user-owned project directory, async () { const result await permissionGate.check(write_file, { path: ./project/src/file.js }, userContext); expect(result.allowed).toBe(true); }); it(should deny writing to system root directory, async () { const result await permissionGate.check(write_file, { path: /etc/config.js }, userContext); expect(result.allowed).toBe(false); expect(result.reason).toContain(outside allowed scope); }); it(should deny if file extension is not allowed, async () { const result await permissionGate.check(write_file, { path: ./project/script.exe }, userContext); expect(result.allowed).toBe(false); }); });6.2 集成测试测试技能与记忆的交互模拟完整的用户会话测试技能是否能被正确触发和加载记忆是否能被正确保存和召回。test(Skill triggering and memory recall integration, async () { // 1. 模拟用户要求运行测试 const session new AgentSession(); await session.initialize(); const response1 await session.processUserInput(请运行单元测试。); // 验证响应中是否包含了技能触发和执行的痕迹 expect(response1).toContain(正在加载测试技能); expect(response1).toContain(测试通过); // 2. 模拟用户稍后再次询问测试相关事宜 const response2 await session.processUserInput(刚才的测试结果里有失败的吗); // 验证智能体是否从自动记忆中回忆起了之前的测试会话 expect(response2).toContain(根据之前的运行记录所有测试均已通过); });6.3 混沌测试测试系统的韧性模拟各种异常情况确保你的缰绳不会让智能体“脱缰”。工具异常模拟工具调用超时、返回错误、抛出异常。缰绳是否能优雅处理并给用户清晰的反馈而不是崩溃或陷入死循环上下文污染故意向上下文注入混乱、矛盾或恶意的信息。智能体的输出是否仍然稳定、可控权限边界测试尝试设计各种边缘案例测试权限系统是否能有效阻止越权行为。6.4 监控与可观测性在生产环境中你需要清晰的监控指标性能指标每次工具调用的耗时、每次LLM交互的耗时、上下文长度分布。业务指标任务成功率、用户满意度如果有反馈机制、技能调用频率。安全与审计日志所有被拒绝的权限检查、所有高风险工具调用必须详细记录who, what, when, where, why。成本指标Token使用量按模型细分这对于控制预算至关重要。建立一个仪表盘实时展示这些指标。设置告警例如当权限拒绝率异常升高时可能意味着有攻击尝试或智能体行为出现了系统性偏差。构建一个生产级的AI智能体核心挑战已经从“如何让模型理解并执行”转变为“如何为模型的能力套上一个可靠、安全、高效的控制框架”。agentic-harness-patterns项目提炼的这六层模式为我们提供了绝佳的地图。记住没有银弹最好的缰绳永远是那个最贴合你特定业务需求、技术栈和风险承受能力的缰绳。从你最痛的一个点开始应用一个模式测试迭代再逐步扩展。在这个过程中持续观察、记录你的智能体行为不断调整和加固你的设计你才能真正驾驭AI的力量让它成为你团队中一名稳定、高效的成员。