[{"content":"前言 Skill 是目前 Agent 最灵活的扩展方式——一个文件夹 + 一个 SKILL.md 文件，就能让 AI 助手获得特定能力。\n但灵活性也是双刃剑：怎么写才算好？什么时候该拆 skill？什么时候该退休？\nPhilipp Schmid 在实践中总结了 8 条经验，今天我们用大白话 + 实战案例把它讲透。\n我随便测试下\n","permalink":"https://blog.lucasma.cc/posts/test-post/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eSkill 是目前 Agent 最灵活的扩展方式——一个文件夹 + 一个 \u003ccode\u003eSKILL.md\u003c/code\u003e 文件，就能让 AI 助手获得特定能力。\u003c/p\u003e\n\u003cp\u003e但灵活性也是双刃剑：怎么写才算好？什么时候该拆 skill？什么时候该退休？\u003c/p\u003e\n\u003cp\u003ePhilipp Schmid 在实践中总结了 8 条经验，今天我们用大白话 + 实战案例把它讲透。\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e我随便测试下\u003c/p\u003e","title":"Test Post"},{"content":"前言 Skill 是目前 Agent 最灵活的扩展方式——一个文件夹 + 一个 SKILL.md 文件，就能让 AI 助手获得特定能力。\n但灵活性也是双刃剑：怎么写才算好？什么时候该拆 skill？什么时候该退休？\nPhilipp Schmid 在实践中总结了 8 条经验，今天我们用大白话 + 实战案例把它讲透。\n一、先搞清楚 Skill 到底是什么 Skill 由三层构成：\ngraph TD A[\u0026#34;Skill = 文件夹 + SKILL.md\u0026#34;] --\u0026gt; B[\u0026#34;Frontmatter\\nname + description\u0026#34;] A --\u0026gt; C[\u0026#34;Body\\n指令正文\u0026#34;] A --\u0026gt; D[\u0026#34;Helper Files\\n辅助脚本/资源\u0026#34;] B --\u0026gt; E[\u0026#34;始终加载\u0026#34;] C --\u0026gt; F[\u0026#34;Skill 触发后才加载\u0026#34;] D --\u0026gt; G[\u0026#34;按需加载\u0026#34;] Skill 分两类：\nCapability Skill：赋予 Agent 原本不会的能力（比如「用 Python 操作 Excel」） Guidance Skill：规范 Agent 的行为方式（比如「代码审查规范」） 实战建议：如果你发现同一个 skill 里既教 Agent 做什么、又教 Agent 怎么做，十有八九该拆成两个。\n二、把 Description 写成「触发器」，别写成广告语 Description 是 Agent 决定是否激活这个 Skill 的唯一依据。写不好，skill 要么永远不触发，要么乱触发。\n❌ 错误示范：\n\u0026ldquo;Helps with documents\u0026rdquo;\n✅ 正确示范：\n\u0026ldquo;Create, edit, and analyze .docx files. Use for tracked changes, comments, formatting, or text extraction. Do NOT use for plain text files or spreadsheets.\u0026rdquo;\n好 Description 的公式：\nUse when [具体场景] + Do NOT use when [禁忌场景] + what it does [做什么] 实战经验：光把 Description 写精准，有些 Skill 的效果就能提升 50%。\n三、Instructions 要「点菜式」，不要「流水席」 很多人写 Skill 的最大误区：把 Skill 写成一篇说明书。\n研究已经证明：上下文越长、越详细，Agent 的表现反而越差。Agent 很聪明，你的 job 不是告诉它所有事，而是告诉它它不知道的事。\ngraph LR A[\u0026#34;Agent 已经会的\u0026#34;] --\u0026gt; B[\u0026#34;不用写\u0026#34;] C[\u0026#34;Agent 不知道的\u0026#34;] --\u0026gt; D[\u0026#34;写到 SKILL.md\u0026#34;] D --\u0026gt; E[\u0026#34;效果更好\u0026#34;] 正确示范：只写约束和目标，不写执行路径。\n❌ 啰嗦版：\nStep 1: Read the file Step 2: Parse the JSON Step 3: Extract the \u0026#34;name\u0026#34; and \u0026#34;version\u0026#34; fields Step 4: Update the version number ✅ 简洁版：\nGoal: Bump the version field in package.json. Constraint: Follow semver format (major.minor.patch). 如果这件事「步骤顺序不能乱，换了顺序就坏」，这不是 skill 的问题，这是脚本的场景——直接写脚本，别用 skill。\n四、分层加载：让 Skill 保持苗条 不要把所有东西都塞进一个 SKILL.md。Agent 加载内容是分层的：\ngraph TD A[\u0026#34;SKILL.md Frontmatter\u0026#34;] --\u0026gt; B[\u0026#34;始终加载\\nname + description\u0026#34;] A --\u0026gt; C[\u0026#34;SKILL.md Body\u0026#34;] C --\u0026gt; D[\u0026#34;触发后才加载\\n保持 \u0026lt; 500 行\u0026#34;] A --\u0026gt; E[\u0026#34;Helper Files\\nreference/脚本/资源\u0026#34;] E --\u0026gt; F[\u0026#34;按需加载\u0026#34;] style B fill:#e1f5fe style D fill:#fff3e0 style F fill:#e8f5e9 如果你的 Skill 覆盖多个主题（比如同时覆盖 AWS 和 GCP），拆成独立的 reference 文件，Agent 只读它需要的那个。\n小技巧：reference 文件超过 500 行？加个目录 + 行号提示，Agent 可以跳读。\n五、给 Agent「自由度」，别当「保姆」 这是最容易犯的错误：把 Skill 写成一步步的 workflow。\n当你写「Step 1 做什么，Step 2 做什么」的时候，你其实是在剥夺 Agent 的适应能力和纠错能力。\ngraph LR A[\u0026#34;写具体步骤\u0026#34;] --\u0026gt; B[\u0026#34;Agent 没灵活性\u0026#34;] B --\u0026gt; C[\u0026#34;遇到新情况就卡住\u0026#34;] D[\u0026#34;写目标和约束\u0026#34;] --\u0026gt; E[\u0026#34;Agent 自己规划路径\u0026#34;] E --\u0026gt; F[\u0026#34;能适应、能纠错\u0026#34;] 核心原则：告诉 Agent 你想要什么结果，别教它怎么走路。\n六、想清楚「什么时候不该触发」 这是被 90% 的人忽略的一点。\n如果一个 Skill 的 Description 写「适用于所有编码任务」，那它会 hijack 每一个请求——Agent 会把它用在不该用的地方。\n# 正面案例：明确边界 description = \u0026#34;\u0026#34;\u0026#34; Use when working with PDF files. Do NOT use for: - General document editing - Spreadsheets - Plain text files \u0026#34;\u0026#34;\u0026#34; 测试的时候，不仅要测「应该触发」的场景，还要测「不应该触发」的场景。不然你只会优化一个方向。\n七、发布前必须测试，别赌概率 Agent 的输出是非确定性的（nondeterministic）——同一个 prompt，跑 3 次可能有 3 个结果。\n所以：\n单次测试不够，至少跑 10-20 个不同的 prompt 多次试验：每个 prompt 跑 3-5 次，看结果的分布，不只看一次成功与否 隔离环境：每次跑之前清空 context，context 污染会掩盖真实问题 graph TD A[\u0026#34;写好 Skill\u0026#34;] --\u0026gt; B[\u0026#34;准备 10-20 个测试 prompt\u0026#34;] B --\u0026gt; C[\u0026#34;混合三类：\\n应触发的 / 不应触发的 / 边缘的\u0026#34;] C --\u0026gt; D[\u0026#34;每个 prompt 跑 3-5 次\u0026#34;] D --\u0026gt; E[\u0026#34;统计成功率分布\u0026#34;] E --\u0026gt; F[\u0026#34;修复 Description 还是 Instructions?\u0026#34;] F --\u0026gt; G[\u0026#34;迭代优化\u0026#34;] G --\u0026gt; D 验收标准要可量化，不要「看起来对了」：\n输出能编译吗？ 调用了正确的 API 吗？ 遵循了指定的步骤吗？ 大多数问题其实在 Description，不在 Instructions。测试时先检查触发是否精准。\n八、知道什么时候该让 Skill「退役」 这是最反直觉的一点：当模型已经把 Skill 的价值「学到」了，这个 Skill 就该退休了。\n尤其是 Capability Skill——模型每年都在变强，原来需要 skill 才能做的事，模型可能已经内化了。\n验证方法： 1. 跑一次 eval，不加载这个 Skill 2. 如果 eval 通过 → Agent 已经掌握了 → 退休这个 Skill 保留 Skill 的成本其实不低：维护、更新、context 占用。当它不再贡献价值，就果断让它退出。\n总结：8 条清单 序号 核心要点 一句话 1 搞清 Skill 三层结构 Frontmatter / Body / Helpers 分层加载 2 Description 是触发器 写「什么时候用」+「什么时候不用」 3 Instructions 要精简 只写 Agent 不知道的，别写成说明书 4 保持苗条 超过 500 行就拆分 reference 文件 5 给 Agent 自由度 写目标 + 约束，不写步骤 6 考虑负面场景 测试「应该触发」+「不应该触发」 7 测试要充分 10-20 个 prompt × 3-5 次，统计分布 8 知道何时退休 eval 通过就退役，别舍不得 参考链接 原文：8 Tips for Writing Agent Skills by Philipp Schmid，2026 年 4 月 13 日\n欢迎关注收藏我，获取更多硬核技术干货！\n","permalink":"https://blog.lucasma.cc/posts/8-tips-writing-agent-skills/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eSkill 是目前 Agent 最灵活的扩展方式——一个文件夹 + 一个 \u003ccode\u003eSKILL.md\u003c/code\u003e 文件，就能让 AI 助手获得特定能力。\u003c/p\u003e\n\u003cp\u003e但灵活性也是双刃剑：怎么写才算好？什么时候该拆 skill？什么时候该退休？\u003c/p\u003e\n\u003cp\u003ePhilipp Schmid 在实践中总结了 8 条经验，今天我们用大白话 + 实战案例把它讲透。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一先搞清楚-skill-到底是什么\"\u003e一、先搞清楚 Skill 到底是什么\u003c/h2\u003e\n\u003cp\u003eSkill 由三层构成：\u003c/p\u003e\n\u003cpre class=\"mermaid\"\u003egraph TD\n    A[\u0026#34;Skill = 文件夹 + SKILL.md\u0026#34;] --\u0026gt; B[\u0026#34;Frontmatter\\nname + description\u0026#34;]\n    A --\u0026gt; C[\u0026#34;Body\\n指令正文\u0026#34;]\n    A --\u0026gt; D[\u0026#34;Helper Files\\n辅助脚本/资源\u0026#34;]\n    \n    B --\u0026gt; E[\u0026#34;始终加载\u0026#34;]\n    C --\u0026gt; F[\u0026#34;Skill 触发后才加载\u0026#34;]\n    D --\u0026gt; G[\u0026#34;按需加载\u0026#34;]\n\u003c/pre\u003e\n\n\u003cp\u003eSkill 分两类：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eCapability Skill\u003c/strong\u003e：赋予 Agent 原本不会的能力（比如「用 Python 操作 Excel」）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGuidance Skill\u003c/strong\u003e：规范 Agent 的行为方式（比如「代码审查规范」）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e实战建议：如果你发现同一个 skill 里既教 Agent 做什么、又教 Agent 怎么做，十有八九该拆成两个。\u003c/p\u003e","title":"写好 Agent Skill 的8个实战技巧"},{"content":"痛点：为什么你的AI Agent总是\u0026quot;失忆\u0026quot;？ 用过 AI Agent 的同学一定遇到过这种情况：\n上一轮对话你告诉它「我的项目叫 XXX」，下一轮它就忘了。\n你让它先查文件、再写代码、再测试，它全做完了，但最后问你「测试结果是什么」时，它一脸茫然——因为它不记得自己刚才做了什么。\n这不是 Bug，这是记忆缺失。\n大模型（LLM）本身是无状态的（Stateless）：每一次 API 调用都是独立的，模型不会「记住」之前发生的事。\n那么问题来了——如何让 AI Agent 拥有记忆？\n今天我们从原理到实现，系统性地拆解 LLM Agent 的记忆系统设计。\n一、记忆系统的本质：让模型「看到」历史 先厘清一个核心概念：所谓 AI Agent 的「记忆」，本质上就是把历史上下文塞进下一轮 Prompt 里。\n模型自己没有记忆，我们要做的是：\ngraph LR A[\u0026#34;用户输入\u0026#34;] --\u0026gt; B[\u0026#34;检索记忆\u0026#34;] B --\u0026gt; C[\u0026#34;拼接 Context\u0026#34;] C --\u0026gt; D[\u0026#34;LLM 推理\u0026#34;] D --\u0026gt; E[\u0026#34;模型输出\u0026#34;] E --\u0026gt; F[\u0026#34;写入新记忆\u0026#34;] F --\u0026gt; B 整个流程闭环：检索 → 拼接 → 推理 → 写入 → 下一轮检索。\n所以记忆系统的核心模块只有三个：\n记忆存储（Memory Storage）—— 记忆怎么存？ 记忆检索（Memory Retrieval）—— 需要用时怎么找？ 记忆写入（Memory Write）—— 新信息怎么记录？ 二、记忆存储：三种主流方案 2.1 方案一：全量上下文（Full Context） 最暴力的方案——把历史对话全部塞进 Context Window。\nmessages = [ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;你是助手\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;第一轮对话\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;第一轮回复\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;第二轮对话\u0026#34;}, # ... 所有历史 ] response = chat(messages) 优点：实现简单，零额外逻辑 缺点：Context 长度有限（4K~128K），对话一长就爆；每次调用都重复发送历史，成本线性增长\n适用场景：短期单轮对话、演示 Demo\n2.2 方案二：摘要式记忆（Summarized Memory） 对话过程中定期对历史做摘要，保留「要点」丢弃「细节」：\ngraph TD A[\u0026#34;原始对话日志\u0026#34;] --\u0026gt; B{\u0026#34;是否达到摘要阈值?\u0026#34;} B --\u0026gt;|否| Z[继续累积] B --\u0026gt;|是| C[\u0026#34;LLM 摘要生成\u0026#34;] C --\u0026gt; D[\u0026#34;摘要存储\u0026#34;] D --\u0026gt; E[\u0026#34;原始日志可删除\u0026#34;] E --\u0026gt; F[\u0026#34;用于下一轮检索\u0026#34;] def summarize_if_needed(conversation_history: list, threshold: int = 10): if len(conversation_history) \u0026gt;= threshold: summary_prompt = f\u0026#34;\u0026#34;\u0026#34;请摘要以下对话的核心要点，保留关键信息： {conversation_history}\u0026#34;\u0026#34;\u0026#34; summary = llm.chat([{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: summary_prompt}]) return {\u0026#34;summary\u0026#34;: summary, \u0026#34;original_count\u0026#34;: len(conversation_history)} return None 优点：控制 Context 长度，适合长对话 缺点：摘要损失信息；摘要本身也消耗 Token；摘要时机难以把握\n适用场景：长程对话（客服、长文档分析）\n2.3 方案三：向量数据库 + RAG（推荐方案） 这是目前最主流的 Agent 记忆架构——用向量数据库做语义检索：\ngraph TD A[\u0026#34;新对话\u0026#34;] --\u0026gt; B[\u0026#34;Embedding 模型\u0026#34;] B --\u0026gt; C[\u0026#34;Query 向量\u0026#34;] C --\u0026gt; D[\u0026#34;向量数据库检索\u0026#34;] D --\u0026gt; E[\u0026#34;Top-K 相关记忆\u0026#34;] E --\u0026gt; F[\u0026#34;拼入 Context\u0026#34;] F --\u0026gt; G[\u0026#34;LLM 推理\u0026#34;] H[\u0026#34;新经验\u0026#34;] --\u0026gt; I[\u0026#34;Embedding\u0026#34;] I --\u0026gt; J[\u0026#34;写入向量数据库\u0026#34;] 核心流程：\n写入时：对话内容 → Embedding → 向量存储 读取时：当前问题 → Embedding → 相似度搜索 → Top-K 召回 → 拼入 Prompt from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings # 写入记忆 def write_memory(text: str, vectorstore: Chroma): vectorstore.add_texts([text]) # 检索记忆 def retrieve_memory(query: str, vectorstore: Chroma, k: int = 5) -\u0026gt; list[str]: docs = vectorstore.similarity_search(query, k=k) return [doc.page_content for doc in docs] 优点：语义检索准确、可扩展、支持多种向量数据库 缺点：引入额外组件（Milvus/Chroma/Pinecone）；检索质量依赖 Embedding 模型\n适用场景：生产级 Agent、需要长期记忆的场景\n三、记忆分层架构：MOS Memory Architecture 一个完善的 Agent 记忆系统通常采用三层记忆结构：\ngraph TD subgraph 注意力记忆 \u0026#34;Attention Memory（最近对话）\u0026#34; A1[\u0026#34;当前 Session\\n最近 5-10 轮\u0026#34;] end subgraph 语义记忆 \u0026#34;Semantic Memory（核心知识）\u0026#34; B1[\u0026#34;向量数据库\\n长期存储\u0026#34;] B2[\u0026#34;RAG 检索\u0026#34;] end subgraph 程序记忆 \u0026#34;Procedural Memory（系统配置）\u0026#34; C1[\u0026#34;AGENTS.md\u0026#34;] C2[\u0026#34;SKILL.md\u0026#34;] C3[\u0026#34;工具定义\u0026#34;] end A1 --\u0026gt; D[\u0026#34;Context 组装\u0026#34;] B2 --\u0026gt; D C1 --\u0026gt; D C2 --\u0026gt; D C3 --\u0026gt; D D --\u0026gt; E[\u0026#34;LLM 推理\u0026#34;] 3.1 注意力记忆（Attention Memory） 定义：当前对话窗口内、模型直接「能看到」的内容\n通常保留最近 N 轮对话或最近 M Token，实现最简单：\nclass AttentionMemory: def __init__(self, max_turns: int = 10): self.messages: list[dict] = [] self.max_turns = max_turns def add(self, role: str, content: str): self.messages.append({\u0026#34;role\u0026#34;: role, \u0026#34;content\u0026#34;: content}) # 超过阈值，删除最早的回合 if len(self.messages) \u0026gt; self.max_turns * 2: self.messages = self.messages[2:] def get_context(self) -\u0026gt; list[dict]: return self.messages 3.2 语义记忆（Semantic Memory） 定义：长期存储的核心知识，通过向量检索召回\n通常按记忆类型分类存储：\n记忆类型 说明 召回场景 user_profile 用户基本信息、偏好 个性化回复 project_context 当前项目/任务背景 技术决策、代码风格 conversation_summary 对话摘要 长程任务状态恢复 tool_output 工具执行结果缓存 避免重复调用 class SemanticMemory: def __init__(self, vectorstore: Chroma): self.store = vectorstore self.metadata_filter = {} # 可按 type 过滤 def write(self, text: str, memory_type: str, session_id: str): self.store.add_texts( texts=[text], metadatas=[{\u0026#34;type\u0026#34;: memory_type, \u0026#34;session_id\u0026#34;: session_id}] ) def retrieve(self, query: str, memory_types: list[str] | None = None, k: int = 5) -\u0026gt; list[dict]: return self.store.similarity_search_with_score( query, k=k, filter={\u0026#34;type\u0026#34;: {\u0026#34;$in\u0026#34;: memory_types}} if memory_types else None ) 3.3 程序记忆（Procedural Memory） 定义：Agent 的「本能」——系统Prompt、工具定义、工作流配置\n这部分不需要检索，直接写入 System Prompt：\nSYSTEM_PROMPT = \u0026#34;\u0026#34;\u0026#34;你是一个专业的 AI 编程助手。 ## 系统能力 - 可以使用 Read/Edit/Write 工具操作本地文件 - 可以使用 Exec 工具执行 Shell 命令 - 可以使用 Browser 工具控制浏览器 ## 工作流程 当用户提出需求时，你应该： 1. 理解需求并拆解任务步骤 2. 按步骤执行，及时反馈进度 3. 完成后总结做了什么、结果是什么 ## 约束 - 不要执行删除、格式化等危险操作 - 遇到错误先分析原因，再尝试修复 \u0026#34;\u0026#34;\u0026#34; 四、记忆检索策略：不是召回越多越好 4.1 基础相似度检索的问题 naive 的向量检索有两个典型问题：\n问题1：召回「多」但不「准」\n比如问「上次那个Python项目的测试怎么跑」，检索可能召回一堆关于「Python」和「测试」的内容，但具体是哪个项目、哪个测试文件，模型还是分不清。\n问题2：召回内容「打架」\n不同记忆片段对同一件事的说法不一致，模型不知道该信哪个。\n4.2 解决方案：混合检索 + 重排序 graph TD A[\u0026#34;Query\u0026#34;] --\u0026gt; B[\u0026#34;关键词检索 BM25\u0026#34;] A --\u0026gt; C[\u0026#34;向量语义检索\u0026#34;] B --\u0026gt; D[\u0026#34;融合结果\u0026#34;] C --\u0026gt; D D --\u0026gt; E[\u0026#34;重排序模型 rerank\u0026#34;] E --\u0026gt; F[\u0026#34;Top-K 精准输出\u0026#34;] from langchain.retrievers import EnsembleRetriever # 混合检索 retriever = EnsembleRetriever( retrievers=[ bm25_retriever, # 关键词 vectorstore.as_retriever(search_kwargs={\u0026#34;k\u0026#34;: 10}) # 向量 ], weights=[0.3, 0.7] # 权重分配 ) results = retriever.get_relevant_documents(query) 4.3 Context 压缩：让每条记忆更「精」 检索回来的记忆可能很长，直接塞 Context 会浪费 Token。用「Context 压缩器」精简每条记忆：\nfrom langchain.compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) from langchain.retrievers import ContextualCompressionRetriever compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever ) # 返回的是压缩后的精简片段 compressed_docs = compression_retriever.get_relevant_documents(query) 五、记忆写入时机：什么时候该「记」？ 5.1 被动写入 vs 主动写入 类型 触发方式 示例 被动写入 对话轮次达到阈值 每 10 轮对话后写入摘要 被动写入 Session 结束时 对话关闭前统一摘要 主动写入 关键操作完成后 「已创建文件 /src/main.py」 主动写入 用户明确告知 「记住，我的名字叫张三」 主动写入 检测到重要实体 「识别到项目名 code-minions」 5.2 主动写入的触发规则 一个实用的规则引擎：\nclass MemoryWritePolicy: def should_write(self, message: dict, context: dict) -\u0026gt; bool: content = message[\u0026#34;content\u0026#34;] # 规则1：用户明确声明 if re.match(r\u0026#34;记住，.*\u0026#34;, content): return True # 规则2：执行了外部操作 if message.get(\u0026#34;role\u0026#34;) == \u0026#34;system\u0026#34; and \u0026#34;tool_output\u0026#34; in content: return True # 规则3：识别到关键实体（项目名、人名、技术栈） entities = extract_entities(content) # 用 NER 或规则 if len(entities) \u0026gt; 2: return True # 规则4：对话涉及长期项目上下文 if context.get(\u0026#34;task_type\u0026#34;) == \u0026#34;project_work\u0026#34;: return True return False 六、实战：实现一个简易记忆系统 完整代码示例：\nfrom langchain.llms import OpenAI from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.memory import ConversationBufferMemory from langchain.agents import AgentExecutor, ZeroShotAgent from langchain.prompts import PromptTemplate class SimpleAgentMemory: def __init__(self, llm, vectorstore_path: str = \u0026#34;./memory_db\u0026#34;): self.llm = llm self.embeddings = OpenAIEmbeddings() self.vectorstore = Chroma( persist_directory=vectorstore_path, embedding_function=self.embeddings ) # 注意力记忆：最近 10 轮 self.attention = ConversationBufferMemory(memory_key=\u0026#34;chat_history\u0026#34;, k=10) def retrieve(self, query: str, k: int = 5) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;从向量数据库检索相关记忆\u0026#34;\u0026#34;\u0026#34; docs = self.vectorstore.similarity_search(query, k=k) return \u0026#34;\\n\u0026#34;.join([f\u0026#34;- {d.page_content}\u0026#34; for d in docs]) def write(self, text: str, memory_type: str = \u0026#34;general\u0026#34;): \u0026#34;\u0026#34;\u0026#34;写入记忆到向量数据库\u0026#34;\u0026#34;\u0026#34; self.vectorstore.add_texts( texts=[text], metadatas=[{\u0026#34;type\u0026#34;: memory_type}] ) self.vectorstore.persist() def chat(self, user_input: str) -\u0026gt; str: # Step 1: 检索相关记忆 relevant_memory = self.retrieve(user_input) # Step 2: 构建 Prompt（注意力记忆 + 语义记忆） attention_context = self.attention.load_memory_variables({})[\u0026#34;chat_history\u0026#34;] prompt = f\u0026#34;\u0026#34;\u0026#34;你是 AI 助手。以下是相关记忆： {relevant_memory} 当前对话历史： {attention_context} 用户：{user_input} 助手：\u0026#34;\u0026#34;\u0026#34; # Step 3: LLM 推理 response = self.llm(prompt) # Step 4: 更新注意力记忆 self.attention.save_context( {\u0026#34;input\u0026#34;: user_input}, {\u0026#34;output\u0026#34;: response} ) # Step 5: 关键内容写入语义记忆 if self._is_important(user_input) or self._is_important(response): self.write(f\u0026#34;用户问：{user_input}\\n助手答：{response}\u0026#34;) return response def _is_important(self, text: str) -\u0026gt; bool: # 简单规则：包含项目名、技术决策、用户偏好等 keywords = [\u0026#34;项目\u0026#34;, \u0026#34;记住\u0026#34;, \u0026#34;偏好\u0026#34;, \u0026#34;配置\u0026#34;, \u0026#34;名字\u0026#34;, \u0026#34;我的\u0026#34;] return any(kw in text for kw in keywords) # 使用 memory = SimpleAgentMemory(llm=OpenAI(temperature=0)) response = memory.chat(\u0026#34;我叫张三，在做一个叫code-minions的项目\u0026#34;) print(response) 七、生产级注意事项 7.1 记忆存储的分桶策略 不要把所有记忆存在一个 collection 里，按会话和主题分桶：\n# 索引结构设计 collection_name = f\u0026#34;{user_id}_{topic}_{session_id}\u0026#34; # 例如：user123_code-minions_session-001 7.2 记忆的 TTL（过期策略） 记忆类型 TTL 说明 用户偏好 30天 长期有效，定期确认 项目上下文 Session级 项目结束后可删除 对话摘要 7天 定期清理过时的摘要 工具输出 24小时 及时清理陈旧状态 7.3 记忆一致性 多 Agent 共享记忆时，要处理并发写入和版本冲突：\n# 乐观锁写入 def write_with_version(vectorstore, text, expected_version): current = vectorstore.get_version() if current != expected_version: raise MemoryConflictError(\u0026#34;记忆已被其他 Agent 修改\u0026#34;) vectorstore.add_texts([text]) vectorstore.set_version(current + 1) 八、总结 LLM Agent 的记忆系统设计，本质上是三个问题：\n存什么 → 分层记忆（注意力/语义/程序） 怎么存 → 向量数据库 + 元数据分类 怎么用 → 混合检索 + 重排序 + Context 压缩 一个好的记忆系统，能让 Agent 从「每轮都失忆的金鱼」，变成「理解上下文的专业助手」。\n核心实践经验：\n向量检索是基础，但纯相似度召回不够用，必须配合混合检索和重排序 记忆写入要克制，不是所有对话都需要记住，避免向量数据库膨胀 分层设计是关键，注意力记忆管短期，向量数据库管长期，System Prompt 管本能 遗忘也是一种智慧，定期清理过时记忆，让 Agent 保持「清醒」 延伸阅读 LangChain Memory Documentation Lilian Weng - LLM Agent Memory Pinecone - Vector Database for AI 原文引用：Lilian Weng - LLM Agent Memory Architecture\n欢迎关注收藏我，获取更多硬核技术干货！\n","permalink":"https://blog.lucasma.cc/posts/llm-agent-memory-systems/","summary":"\u003ch2 id=\"痛点为什么你的ai-agent总是失忆\"\u003e痛点：为什么你的AI Agent总是\u0026quot;失忆\u0026quot;？\u003c/h2\u003e\n\u003cp\u003e用过 AI Agent 的同学一定遇到过这种情况：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e上一轮对话你告诉它「我的项目叫 XXX」，下一轮它就忘了。\u003c/p\u003e\n\u003cp\u003e你让它先查文件、再写代码、再测试，它全做完了，但最后问你「测试结果是什么」时，它一脸茫然——因为它不记得自己刚才做了什么。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这不是 Bug，这是\u003cstrong\u003e记忆缺失\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e大模型（LLM）本身是\u003cstrong\u003e无状态的\u003c/strong\u003e（Stateless）：每一次 API 调用都是独立的，模型不会「记住」之前发生的事。\u003c/p\u003e\n\u003cp\u003e那么问题来了——\u003cstrong\u003e如何让 AI Agent 拥有记忆？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e今天我们从原理到实现，系统性地拆解 LLM Agent 的记忆系统设计。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一记忆系统的本质让模型看到历史\"\u003e一、记忆系统的本质：让模型「看到」历史\u003c/h2\u003e\n\u003cp\u003e先厘清一个核心概念：所谓 AI Agent 的「记忆」，本质上就是\u003cstrong\u003e把历史上下文塞进下一轮 Prompt 里\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e模型自己没有记忆，我们要做的是：\u003c/p\u003e\n\u003cpre class=\"mermaid\"\u003egraph LR\n    A[\u0026#34;用户输入\u0026#34;] --\u0026gt; B[\u0026#34;检索记忆\u0026#34;]\n    B --\u0026gt; C[\u0026#34;拼接 Context\u0026#34;]\n    C --\u0026gt; D[\u0026#34;LLM 推理\u0026#34;]\n    D --\u0026gt; E[\u0026#34;模型输出\u0026#34;]\n    E --\u0026gt; F[\u0026#34;写入新记忆\u0026#34;]\n    F --\u0026gt; B\n\u003c/pre\u003e\n\n\u003cp\u003e整个流程闭环：\u003cstrong\u003e检索 → 拼接 → 推理 → 写入 → 下一轮检索\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e所以记忆系统的核心模块只有三个：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e记忆存储\u003c/strong\u003e（Memory Storage）—— 记忆怎么存？\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e记忆检索\u003c/strong\u003e（Memory Retrieval）—— 需要用时怎么找？\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e记忆写入\u003c/strong\u003e（Memory Write）—— 新信息怎么记录？\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch2 id=\"二记忆存储三种主流方案\"\u003e二、记忆存储：三种主流方案\u003c/h2\u003e\n\u003ch3 id=\"21-方案一全量上下文full-context\"\u003e2.1 方案一：全量上下文（Full Context）\u003c/h3\u003e\n\u003cp\u003e最暴力的方案——把历史对话全部塞进 Context Window。\u003c/p\u003e","title":"LLM Agent 记忆系统设计指南：让AI拥有\"记忆\"的技术原理与实战"},{"content":"痛点：AI 工具散落成孤岛，团队研发流程靠\u0026quot;人肉\u0026quot;串 过去两年，AI 辅助编程工具爆发式增长——Code Review 有 AI Review，任务管理有 Jira AI Bot，代码生成有 Copilot……但把它们真正串联成一条可自动化、可复用、可审计的研发链路？\n大多数团队的选择是：写一堆脚本，人肉约定执行顺序，然后祈祷不会出 bug。\ncode-minions 正是为了解决这个问题而生的。它的核心理念只有三句话：\n用 YAML 定义工作流，用 Skill 装配能力，用 **MCP 对接外部系统。\n今天我们从架构设计到源码实现，把它的每一个核心模块讲透。\n项目开源地址：https://github.com/pony-maggie/code_minions\n一、整体架构：五层分层 graph TD subgraph CLI层 CLI[\u0026#34;CLI (code-minions run/start/status/resume)\u0026#34;] end subgraph Workflow Engine Engine[\u0026#34;Engine — 顶层编排器\u0026#34;] DAGRunner[\u0026#34;DAG Runner — 步骤调度器\u0026#34;] WorkflowLoader[\u0026#34;Workflow Loader — YAML 解析\u0026#34;] SkillLoader[\u0026#34;Skill Loader — SKILL.md 解析\u0026#34;] end subgraph Skill Runtime SkillRuntime[\u0026#34;Skill Runtime — 技能执行器\u0026#34;] ToolExecutor[\u0026#34;Tool Executor — 工具执行\u0026#34;] ContextAssembler[\u0026#34;Context Assembler — 上下文注入\u0026#34;] end subgraph 能力基座 LLM[\u0026#34;LLM Backend\\n(Multi-Provider)\u0026#34;] MCP[\u0026#34;MCP Client Pool\u0026#34;] Git[\u0026#34;Git Worktree Manager\u0026#34;] RunStore[\u0026#34;SQLite Run Store\u0026#34;] end CLI --\u0026gt; Engine Engine --\u0026gt; DAGRunner Engine --\u0026gt; WorkflowLoader Engine --\u0026gt; SkillLoader DAGRunner --\u0026gt; SkillRuntime SkillRuntime --\u0026gt; ToolExecutor SkillRuntime --\u0026gt; ContextAssembler SkillRuntime --\u0026gt; LLM SkillRuntime --\u0026gt; MCP DAGRunner --\u0026gt; Git Engine --\u0026gt; RunStore 五大核心环节（对应 PRD → 工单 → 代码 → TDD → AI Code Review）：\nparse-prd — 解析 PRD，提取结构化需求 plan-tasks — 任务拆解，生成工单列表 implement-with-tdd — TDD 循环写代码 + 自愈 self-heal ai-code-review — AI Code Review，发现 blocker/major 问题 compile-report — 生成验收报告 二、DAG Runner：拓扑排序 + for_each 展开 engine/dag_runner.py 是工作流的执行引擎。它的职责：按依赖顺序执行每一步，支持失败后 resume，支持 for_each 并发展开。\n2.1 拓扑排序执行 def _topo_order(self) -\u0026gt; list[WorkflowStep]: by_id = {s.id: s for s in self._wf.steps} visited: set[str] = set() visiting: set[str] = set() order: list[WorkflowStep] = [] def visit(sid: str) -\u0026gt; None: if sid in visited: return if sid in visiting: raise DAGRunnerError(f\u0026#34;cycle detected involving {sid}\u0026#34;) visiting.add(sid) step = by_id[sid] for dep in step.depends_on: visit(dep) visiting.remove(sid) visited.add(sid) order.append(step) for s in self._wf.steps: visit(s.id) return order 实战建议：如果你的 workflow 出现循环依赖，错误会精确到 cycle detected involving \u0026lt;step-id\u0026gt;。排查时先检查 depends_on 是否有环。\n2.2 for_each 展开 for_each 将一个步骤展开为多个并行实例（实际执行顺序是串行的，但每个实例有独立的 step_id）：\ndef _run_for_each(self, step: WorkflowStep, outputs: dict[str, dict[str, Any]]) -\u0026gt; dict[str, Any]: items_value = self._resolve_value(step.for_each, outputs) collected: list[dict[str, Any]] = [] for idx, item in enumerate(items_value): scratch = {\u0026#34;__item__\u0026#34;: item, \u0026#34;__index__\u0026#34;: idx} per_iter = { k: self._resolve_with_scratch(v, outputs, step.as_, scratch) for k, v in step.inputs.items() } sub_id = f\u0026#34;{step.id}[{idx}]\u0026#34; # 支持 resume：跳过已成功的 iteration if sub_id in outputs: result = outputs[sub_id] else: result = self._run_single_step(step, sub_id, per_iter, outputs, detail=...) collected.append(result) return {\u0026#34;items\u0026#34;: collected} 生成的 sub_id 形如 implement[0]、implement[1]……Resume 时，preloaded_outputs 已经包含了每个已成功的 sub-step，所以重启不会重复执行已成功的迭代。\n2.3 变量引用解析 支持 $inputs.xxx 和 $steps.\u0026lt;id\u0026gt;.output.\u0026lt;path\u0026gt; 两种引用：\ndef _resolve_ref(self, ref: str, step_outputs: dict[str, dict[str, Any]]) -\u0026gt; Any: parts = ref[1:].split(\u0026#34;.\u0026#34;) # strip leading $ root = parts[0] if root == \u0026#34;inputs\u0026#34;: return self._walk(self._inputs, parts[1:], ref) if root == \u0026#34;steps\u0026#34;: step_id = parts[1] return self._walk(step_outputs[step_id], parts[3:], ref) 三、Skill Loader 与 SKILL.md 规范 3.1 SKILL.md 的 frontmatter 规范 每个 Skill 是一个目录，必含 SKILL.md，frontmatter 格式如下：\n--- name: parse-prd description: 解析 PRD 并提取结构化交付契约 allowed-tools: # 仅允许使用的内置工具（空=仅 LLM） - Read required-mcps: # 必须启动的 MCP server - github entrypoint-script: # 确定性脚本路径（相对路径） scripts/run.py inputs: prd_file: {type: string, required: true} delivery_stack_id: {type: string} outputs: structured_prd: {type: object} delivery_profile: {type: object} llm: max_iterations: 15 temperature: 0.1 max_tokens: 4096 policies: max_tasks: 50 # 最大输出任务数限制 self_heal_max_rounds: 3 reviewer_max_rounds: 2 hooks: post_run: - lint # skill 返回后执行 lint hook invokes-skills: - ai-code-review # 当前 skill 可调用其他 skill --- # Skill 主体说明（LLM 会看到这部分） 设计亮点：frontmatter 里 entrypoint-script 存在时走确定性脚本路径，不存在时走 LLM Agent 路径。两种模式共存，互不干扰。\n3.2 加载流程 def load_skill(directory: Path) -\u0026gt; Skill: md = d / \u0026#34;SKILL.md\u0026#34; raw, body = _split_frontmatter(md.read_text()) # 支持 kebab-case 别名（invokes-skills -\u0026gt; invokes_skills） raw = _normalize_frontmatter_keys(raw) meta = SkillMeta.model_validate(raw) return Skill(meta=meta, instructions=body.strip(), directory=d) 四、Context Assembler：AGENTS.md 注入机制 LLM 在执行 skill 时，需要同时知道项目约定和skill 指令。ContextAssembler 负责拼装 system prompt：\nBASE_PROMPT = \u0026#34;\u0026#34;\u0026#34;You are a code_minions skill executor. Work inside the given run workspace. Use the provided built-in local tools to read files, edit files, write files, and run local commands. Use MCP tools only for external systems such as Jira or GitHub. When finished, reply with a JSON object matching the skill\u0026#39;s declared outputs. Do not add narration outside the JSON in your final message.\u0026#34;\u0026#34;\u0026#34; def build_system_prompt(self, skill_instructions: str, step_summary: str) -\u0026gt; str: parts = [BASE_PROMPT.strip()] parts.append(\u0026#34;## Project (AGENTS.md)\\n\u0026#34; + agents) parts.append(\u0026#34;## Skill (SKILL.md)\\n\u0026#34; + skill_instructions.strip()) if step_summary.strip(): parts.append(\u0026#34;## Current step\\n\u0026#34; + step_summary.strip()) return \u0026#34;\\n\\n\u0026#34;.join(parts) 拼装后的 system prompt 顺序：Base 规则 → AGENTS.md（项目约定）→ SKILL.md（技能指令）→ 当前 step 上下文。这保证了 LLM 在执行每一步时都能感知项目级别的约定（比如代码风格、禁止的模式等）。\n五、MCP Client Pool：外部系统的即插即用 mcp/client.py + mcp/pool.py 实现了 MCP (Model Context Protocol) 客户端的连接池管理：\nclass MCPClientPool: def __init__(self, config: MCPConfig, allowed_servers: list[str] | None = None): self._config = config self._allowed = set(allowed_servers) if allowed_servers is not None else None self._clients: dict[str, MCPClient] = {} def start(self) -\u0026gt; None: for name, server_cfg in self._config.servers.items(): if self._allowed is not None and name not in self._allowed: continue client = MCPClient(server_cfg) client.start() self._clients[name] = client Skill 中声明 required-mcps: [github]，运行时 MCP Pool 只启动被 skill 需要的 server，避免资源浪费。\n调用工具时，Skill Runtime 自动将 mcp__github__create_issue 这样的 wire name 路由到正确的 server：\ntool_to_server: dict[str, str] = {} for server_name, srv_tools in ctx.mcp_pool.list_tools().items(): for t in srv_tools: wire_name = f\u0026#34;mcp__{server_name}__{t[\u0026#39;name\u0026#39;]}\u0026#34; tools.append(LLMTool(name=wire_name, ...)) tool_to_server[wire_name] = server_name 六、LLM 多 Provider 抽象 llm/base.py 定义了 LLMBackend Protocol：\nclass LLMBackend(Protocol): name: str def chat( self, messages: list[Message], tools: list[Tool] | None = None, model: str | None = None, temperature: float = 0.2, max_tokens: int = 4096, ) -\u0026gt; Response: ... def supports_tool_use(self) -\u0026gt; bool: ... llm/litellm_backend.py 是实际实现，通过 litellm 统一接入 Anthropic、OpenAI、DeepSeek、MiniMax、Ollama 等所有 litellm 支持的 provider：\nclass LiteLLMBackend: name = \u0026#34;litellm\u0026#34; def chat(self, messages, tools=None, model=None, temperature=0.2, max_tokens=4096) -\u0026gt; Response: full_model = f\u0026#34;{self._provider}/{model or self._default_model}\u0026#34; raw = completion(model=full_model, messages=sdk_msgs, tools=[...], ...) return self._from_sdk_response(raw) 实战建议：如果你想接入新 provider，只需要确认 litellm 支持该 provider，然后配置 provider + default_model + api_key 三参数即可。engine.py 的 _llm_display() 能自动从 backend 属性中提取 provider/model 信息。\n七、Git Worktree 隔离执行 每个需要改代码的 workflow，都在一个独立的 git worktree 分支中执行：\nclass WorktreeManager: def create(self, worktree_path: Path, branch: str, base: str = \u0026#34;HEAD\u0026#34;) -\u0026gt; WorktreeInfo: result = subprocess.run( [\u0026#34;git\u0026#34;, \u0026#34;worktree\u0026#34;, \u0026#34;add\u0026#34;, \u0026#34;-b\u0026#34;, branch, str(worktree_path), base], cwd=self._repo, capture_output=True, text=True, ) if result.returncode != 0: raise WorktreeError(f\u0026#34;git worktree add failed: {result.stderr}\u0026#34;) return WorktreeInfo(path=worktree_path, branch=branch) 工作目录约定：.devflow/runs/\u0026lt;run-id\u0026gt;/worktree，每个 run 有独立分支 code-minions/\u0026lt;run-id\u0026gt;。验收完成后手动 merge 回 main，workflow 不负责合并逻辑（避免强制覆盖用户代码）。\n八、Run Store 持久化：SQLite + Resume store/run_store.py + store/schema.py 用 SQLAlchemy Core 管理 SQLite：\nruns = Table(\u0026#34;runs\u0026#34;, metadata, Column(\u0026#34;id\u0026#34;, String, primary_key=True), Column(\u0026#34;workflow\u0026#34;, String, nullable=False), Column(\u0026#34;status\u0026#34;, String, nullable=False), Column(\u0026#34;llm\u0026#34;, String, nullable=True), Column(\u0026#34;started_at\u0026#34;, DateTime, nullable=False), Column(\u0026#34;ended_at\u0026#34;, DateTime, nullable=True), Column(\u0026#34;input_json\u0026#34;, Text, nullable=False), ) steps = Table(\u0026#34;steps\u0026#34;, metadata, Column(\u0026#34;run_id\u0026#34;, String, nullable=False), Column(\u0026#34;step_id\u0026#34;, String, nullable=False), Column(\u0026#34;status\u0026#34;, String, nullable=False), Column(\u0026#34;detail\u0026#34;, Text, nullable=True), Column(\u0026#34;output_json\u0026#34;, Text, nullable=True), Column(\u0026#34;error\u0026#34;, Text, nullable=True), UniqueConstraint(\u0026#34;run_id\u0026#34;, \u0026#34;step_id\u0026#34;, name=\u0026#34;uq_run_step\u0026#34;), ) get_successful_outputs() 支持按 step_id 恢复已成功的步骤输出，实现精准 resume：\ndef get_successful_outputs(self, run_id: str) -\u0026gt; dict[str, dict[str, Any]]: result: dict[str, dict[str, Any]] = {} for s in self.list_steps(run_id): if s[\u0026#34;status\u0026#34;] == \u0026#34;success\u0026#34; and s[\u0026#34;output_json\u0026#34;]: result[s[\u0026#34;step_id\u0026#34;]] = json.loads(s[\u0026#34;output_json\u0026#34;]) return result 注意：for_each 场景下 sub-step id 形如 implement[0]，resume 机制会跳过已成功的单个 iteration，而非整个 step。\n九、Coder ↔ Reviewer 自愈回环 最精彩的设计之一在 implement-with-tdd skill 内部的双层循环：\nsequenceDiagram participant Outer as Reviewer Loop participant Inner as Self-Heal Loop participant LLM as LLM (Coder) participant Shell as Test Runner participant Reviewer as ai-code-review Outer -\u0026gt;\u0026gt; Inner: for each self_heal_max_rounds Inner -\u0026gt;\u0026gt; LLM: write tests + implementation LLM -\u0026gt;\u0026gt; Shell: run tests Shell --\u0026gt;\u0026gt; LLM: test result alt tests green Inner -\u0026gt;\u0026gt; Outer: break else tests red Inner -\u0026gt;\u0026gt; LLM: feed error back, self-heal end end Outer -\u0026gt;\u0026gt; Reviewer: run ai-code-review Reviewer -\u0026gt;\u0026gt; Outer: blocker/major issues? alt no blockers Outer -\u0026gt;\u0026gt; Done: approved else has blockers Outer -\u0026gt;\u0026gt; LLM: feed issues back to coder end implement-with-tdd 的 SKILL.md policy 字段控制行为：\npolicies: self_heal_max_rounds: 3 # 自愈轮数 reviewer_max_rounds: 2 # Reviewer 轮数（0=不需要 review） ai-code-review 输出的结构化 issues 数组，按严重等级分为 blocker（阻止合并）、major（逻辑 bug）、minor（可读性）、nit（格式）。只有 approved: true（无 blocker/major）才会跳出循环。\n十、Event Bus + Hooks engine/event_bus.py 实现了内存 pub/sub，Phase C 会替换为 Redis/WebSocket：\nclass EventBus: def __init__(self) -\u0026gt; None: self._subs: list[Callable[[Event], None]] = [] self._lock = Lock() def publish(self, event: Event) -\u0026gt; None: with self._lock: subs = list(self._subs) for fn in subs: with contextlib.suppress(Exception): fn(event) engine/hooks.py 实现了 skill 级别的 post-run hook。SkillMeta.hooks.post_run 里声明的 hook 在 skill 成功返回后自动触发：\n# skill SKILL.md 中声明： hooks: post_run: - lint # engine.py 中执行： if self._hook_registry is not None: for hook_name in skill.meta.hooks.get(\u0026#34;post_run\u0026#34;, []): self._hook_registry.run(hook_name, HookContext(...)) 内置 lint hook 会检查 ruff 是否在 PATH 上，运行 ruff check。\n十一、Web Dashboard：FastAPI + SSE web/app.py 暴露了 run 列表、状态查询、表单发起 workflow 等接口，并支持 SSE (Server-Sent Events) 实时推送 step 状态：\ngraph LR Browser --SSE--\u0026gt; WebServer WebServer --EventBus publish--\u0026gt; WebServer WebServer --SSE--\u0026gt; Browser CLI --run--\u0026gt; Engine Engine --EventBus publish--\u0026gt; WebServer 当前 Web 限制：仅面向本机无鉴权使用，CLI 发起的 run 不跨进程实时推送。\n十二、实战：一分钟跑通 hello-world # 1. 安装 git clone https://github.com/malu/code-minions.git cd code-minions \u0026amp;\u0026amp; pip install -e . # 2. 初始化（不需要 LLM） cd your-project code-minions init . # 3. 运行 hello-world 验证基础能力 code-minions run hello-world --input name=world # 4. 查看状态 code-minions list-runs code-minions status \u0026lt;run-id\u0026gt; # 5. 完整 PRD → commit 工作流（需要 LLM key） code-minions run prd-to-commit --input prd=./my-prd.md 欢迎关注收藏我，获取更多硬核技术干货！\n","permalink":"https://blog.lucasma.cc/posts/code_minions_arch/","summary":"\u003ch2 id=\"痛点ai-工具散落成孤岛团队研发流程靠人肉串\"\u003e痛点：AI 工具散落成孤岛，团队研发流程靠\u0026quot;人肉\u0026quot;串\u003c/h2\u003e\n\u003cp\u003e过去两年，AI 辅助编程工具爆发式增长——Code Review 有 AI Review，任务管理有 Jira AI Bot，代码生成有 Copilot……但把它们真正串联成一条\u003cstrong\u003e可自动化、可复用、可审计\u003c/strong\u003e的研发链路？\u003c/p\u003e\n\u003cp\u003e大多数团队的选择是：写一堆脚本，人肉约定执行顺序，然后祈祷不会出 bug。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecode-minions\u003c/code\u003e 正是为了解决这个问题而生的。它的核心理念只有三句话：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e用 \u003cstrong\u003eYAML 定义工作流\u003c/strong\u003e，用 \u003cstrong\u003eSkill 装配能力\u003c/strong\u003e，用 **MCP 对接外部系统。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e今天我们从架构设计到源码实现，把它的每一个核心模块讲透。\u003c/p\u003e\n\u003cp\u003e项目开源地址：https://github.com/pony-maggie/code_minions\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"一整体架构五层分层\"\u003e一、整体架构：五层分层\u003c/h2\u003e\n\u003cpre class=\"mermaid\"\u003egraph TD\n    subgraph CLI层\n        CLI[\u0026#34;CLI (code-minions run/start/status/resume)\u0026#34;]\n    end\n\n    subgraph Workflow Engine\n        Engine[\u0026#34;Engine — 顶层编排器\u0026#34;]\n        DAGRunner[\u0026#34;DAG Runner — 步骤调度器\u0026#34;]\n        WorkflowLoader[\u0026#34;Workflow Loader — YAML 解析\u0026#34;]\n        SkillLoader[\u0026#34;Skill Loader — SKILL.md 解析\u0026#34;]\n    end\n\n    subgraph Skill Runtime\n        SkillRuntime[\u0026#34;Skill Runtime — 技能执行器\u0026#34;]\n        ToolExecutor[\u0026#34;Tool Executor — 工具执行\u0026#34;]\n        ContextAssembler[\u0026#34;Context Assembler — 上下文注入\u0026#34;]\n    end\n\n    subgraph 能力基座\n        LLM[\u0026#34;LLM Backend\\n(Multi-Provider)\u0026#34;]\n        MCP[\u0026#34;MCP Client Pool\u0026#34;]\n        Git[\u0026#34;Git Worktree Manager\u0026#34;]\n        RunStore[\u0026#34;SQLite Run Store\u0026#34;]\n    end\n\n    CLI --\u0026gt; Engine\n    Engine --\u0026gt; DAGRunner\n    Engine --\u0026gt; WorkflowLoader\n    Engine --\u0026gt; SkillLoader\n    DAGRunner --\u0026gt; SkillRuntime\n    SkillRuntime --\u0026gt; ToolExecutor\n    SkillRuntime --\u0026gt; ContextAssembler\n    SkillRuntime --\u0026gt; LLM\n    SkillRuntime --\u0026gt; MCP\n    DAGRunner --\u0026gt; Git\n    Engine --\u0026gt; RunStore\n\u003c/pre\u003e\n\n\u003cp\u003e五大核心环节（对应 PRD → 工单 → 代码 → TDD → AI Code Review）：\u003c/p\u003e","title":"code-minions：一个用 YAML 编排 AI 研发流程的工作流引擎"}]