Skip to content

NanoAgent 核心笔记

📅 发表于 2026/03/31
🔄 更新于 2026/03/31
👁️ -- 次访问
📝 0 字
0 分钟
nanoagent
#Tools
#Memory
#Planning
#Plan-As-Tool
#Rules
#Skills
#MCP
#SubAgent
#SubAent-as-Tool
#TeamsAgent
#上下文压缩
#安全权限控制
#Harness
#skill_run
#5种SKILL模式
#纯Prompt注入型
#脚本执行型
#库调用型
#参考文档渐进型
#编排型

参考链接

Harness 总结

核心组件

Harness 组件作用对应系列文章
工具 + 执行循环让模型能执行代码、读写文件第一篇:工具 + 循环
记忆 + 规划让模型能记住过去、分步完成复杂任务第二篇:记忆与规划
System Prompt + Rules + Skills + MCP注入知识、约束行为、扩展工具第三篇:Rules、Skills、MCP
子 Agent 生成把任务委派给专门的子智能体第四篇:SubAgent
多 Agent 编排持久团队、通信通道、生命周期管理第五篇:Teams
上下文压缩 / Compaction对抗 context window 限制第六篇:上下文压缩
安全防线 + 执行钩子(Hook)黑名单、用户确认、输出截断、可插拔管道第七篇:安全与权限

Harness vs Model

bash
┌─────────────────────────────────────────────────────┐
                    Harness

  ┌─────────────┐  ┌──────────┐  ┌────────────────┐
 Rules Skills MCP Tools
 (第三篇)    │  │ (第三篇)  │  │ (第三篇)       │  │
  └──────┬──────┘  └────┬─────┘  └───────┬────────┘
         └──────────────┼────────────────┘

  ┌──── System Prompt + 工具列表 ────┐

   Memory (第二篇)                 │                │
   Compaction (第六篇)             │                │

  └────────────┬──────────────────────┘

  ┌─────────────────────────┐
      ┌─────────┐
  Model 模型只管思考和决策
 (裸模型)  │        │                         │
      └─────────┘
  └────────────┬────────────┘

  ┌──── Hook 管道 (第七篇) ────┐                      │
  黑名单 用户确认 执行
  └────────────┬───────────────┘

  ┌──── 工具执行层 (第一篇) ────┐                     │
  bash / read / write / edit
  └────────────┬─────────────────┘

  ┌──── 协作层 (第四、五篇) ────┐                     │
  SubAgent / Teams / 通信
  └──────────────────────────────┘

└─────────────────────────────────────────────────────┘

工具调用

Agent
  • Agent = LLM + 工具 + 循环

工具定义

定义

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "execute_bash",  
            "description": "Execute a bash command on the system",  
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": "The bash command to execute"}  
                },
                "required": ["command"]
            }
        }
    },
    # ... read_file, write_file 类似
]

工具具体实现

python
def execute_bash(command):
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)  
        return result.stdout + result.stderr
    except Exception as e:
        returnf"Error: {str(e)}"

def read_file(path):
    try:
        with open(path, 'r') as f:
            return f.read()
    except Exception as e:
        returnf"Error: {str(e)}"

def write_file(path, content):
    try:
        with open(path, 'w') as f:
            f.write(content)
        returnf"Successfully wrote to {path}"
    except Exception as e:
        returnf"Error: {str(e)}"

函数映射

python
available_functions = {
    "execute_bash": execute_bash,  
    "read_file": read_file,
    "write_file": write_file
}

Base Tools

对应Claude Code的核心工具集。

python
base_tools = [
    {"name": "read",  "description": "Read file with line numbers", ...},
    {"name": "write", "description": "Write content to file", ...},
    {"name": "edit",  "description": "Replace string in file", ...},   # 新增
    {"name": "glob",  "description": "Find files by pattern", ...},    # 新增
    {"name": "grep",  "description": "Search files for pattern", ...}, # 新增
    {"name": "bash",  "description": "Run shell command", ...},
    {"name": "plan",  "description": "Break down complex task", ...}   # 新增
]

edit: 旧串必须在文件中出现1次。

python
def edit(path, old_string, new_string):
    try:
        with open(path, 'r') as f:
            content = f.read()
        if content.count(old_string) != 1:  
            return f"Error: old_string must appear exactly once"
        new_content = content.replace(old_string, new_string)
        with open(path, 'w') as f:
            f.write(new_content)
        return f"Successfully edited {path}"
    except Exception as e:
        return f"Error: {str(e)}"

read:行号 + 分页

python
def read(path, offset=None, limit=None):
    try:
        with open(path, 'r') as f:
            lines = f.readlines()
        start = offset if offset else0
        end = start + limit if limit else len(lines)
        numbered = [f"{i+1:4d}{line}"for i, line in enumerate(lines[start:end], start)]
        return ''.join(numbered)
    except Exception as e:
        return f"Error: {str(e)}"

工具调用流程

流程图AgentLoop -> 自主调用工具

bash
用户任务


┌──────────────────────────────────────────────────┐
                  Agent Loop

  ┌─────────┐    ┌──────────┐    ┌──────────────┐
 发送给   │───▶│ LLM 决策  │───▶│ 有tool_call?
 LLM    └──────┬───────┘
  └─────────┘    └──────────┘
                          Yes   No
                          ┌─────┴─────┐

  ┌────┴────────┐          ┌──────────┐  返回文本
 结果追加到   │◀─────────│ 执行工具  ──────▶
 messages          └──────────┘   结束
  └─────────────┘
└──────────────────────────────────────────────────┘

核心代码messages短期记忆

python
def run_agent(user_message, max_iterations=5):
    messages = [
        {"role": "system", "content": "You are a helpful assistant that can interact with the system. Be concise."},
        {"role": "user", "content": user_message}
    ]

    for _ in range(max_iterations):  
        # Step 1: 把完整对话历史 + 工具列表发给 LLM
        response = client.chat.completions.create(  
            model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
            messages=messages,
            tools=tools  
        )

        message = response.choices[0].message
        messages.append(message)

        # Step 2: 如果 LLM 没有调用工具 → 任务完成,返回文本回答
        if not message.tool_calls:
            return message.content

        # Step 3: 如果 LLM 要调用工具 → 逐个执行,把结果追加到对话历史
        for tool_call in message.tool_calls:  
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            print(f"[Tool] {function_name}({function_args})")
            function_response = available_functions[function_name](**function_args "function_name")  
            messages.append({  
                "role": "tool",   
                "tool_call_id": tool_call.id,  
                "content": function_response  
            })

    return"Max iterations reached"

记忆

Agent
  • Agent = LLM + 工具 + 循环 + 记忆
记忆
  • LLM本身无持久记忆
  • 记忆实现:在prompt注入历史信息

记忆存储/加载/使用

记忆的存储加载和使用

存储

  • 存储介质:1个Markdown文件
  • 存储格式:时间、任务、结果

加载

  • 滑动窗口:仅读取最近x行的记忆。

记忆注入

  • 把记忆 塞进System Prompt

记忆存储

python
MEMORY_FILE = "agent_memory.md"

def save_memory(task, result):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entry = f"\n## {timestamp}\n**Task:** {task}\n**Result:** {result}\n"
    try:
        with open(MEMORY_FILE, 'a') as f:
            f.write(entry)
    except:
        pass

示例:

markdown
## 2026-03-12 14:30:00
**Task:** 统计当前目录下的 Python 文件数量
**Result:** 当前目录下共有 42 个 Python 文件。

## 2026-03-12 15:00:00
**Task:** 创建一个 hello.py
**Result:** 已创建 hello.py,内容为打印 Hello World。

记忆加载:滑动加载

python
def load_memory():
    if not os.path.exists(MEMORY_FILE):
        return""
    try:
        with open(MEMORY_FILE, 'r') as f:
            content = f.read()
        lines = content.split('\n')
        return'\n'.join(lines[-50:]) if len(lines) > 50 else content  
    except:
        return ""

记忆注入:System Prompt

python
def run_agent_plus(task, use_plan=False):
    memory = load_memory()  
    system_prompt = "You are a helpful assistant that can interact with the system. Be concise."
    if memory:
        system_prompt += f"\n\nPrevious context:\n{memory}"
    messages = [{"role": "system", "content": system_prompt}]

记忆使用流程

bash
 1 次运行 2 次运行
───────────                        ───────────
用户: "创建 hello.py"               用户: "读取 hello.py 并加上注释"


  system prompt:                    system prompt:
  "You are a helpful               "You are a helpful
   assistant..."                    assistant...
                                    
                                    Previous context:
                                    ## 2026-03-12 14:30
                                    Task: 创建 hello.py
                                    Result: 已创建..."


  Agent 执行任务                     Agent 执行任务
                           (知道之前创建过 hello.py)  

  save_memory() ──写入──▶ agent_memory.md ◀─── save_memory()

规划

Agent
  • Agent = LLM + 工具 + 循环 + 记忆 + 规划
规划
  • 规划:让Agent学会先想再做
  • 实现:让LLM规划输出多个子步骤,再单独去执行子步骤

LLM拆解步骤

核心

Break down the task into 3-5 simple, actionable steps. Return as JSON array of strings

python
def create_plan(task):
    print("[Planning] Breaking down task...")
    response = client.chat.completions.create(
        model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
        messages=[
            {"role": "system", "content": "Break down the task into 3-5 simple, actionable steps. Return as JSON array of strings."},  
            {"role": "user", "content": f"Task: {task}"}
        ],
        response_format={"type": "json_object"}
    )
    try:
        plan_data = json.loads(response.choices[0].message.content)
        steps = plan_data.get("steps", [task])
        print(f"[Plan] {len(steps)} steps created")
        for i, step in enumerate(steps, 1):
            print(f"  {i}. {step}")
        return steps  
    except:
      	# plan失败
        return [task]  

Plan流程

bash
agent.py (ReAct)                  agent-plus.py (Plan-then-Execute)

思考 行动 观察                规划(全局思考)

  └─────────┘                      步骤1 步骤2 步骤3
                                   (每步内部仍是 ReAct)

Plan示例

bash
[Planning] Breaking down task...
[Plan] 3 steps created
  1. 使用 grep 递归搜索所有 TODO 注释
  2. 整理搜索结果为 Markdown 清单格式
  3. 将清单写入 todo.md

[Step 1/3] 使用 grep 递归搜索所有 TODO 注释
[Tool] execute_bash({"command": "grep -rn 'TODO' --include='*.py' ."})
 ./app.py:23: # TODO: add error handling
 ./utils.py:7: # TODO: refactor this function
 ./main.py:45: # TODO: add logging

找到 3 TODO 注释。

[Step 2/3] 整理搜索结果为 Markdown 清单格式
(LLM 看到步骤 1 grep 结果,直接整理,无需再次搜索)

已整理为以下清单:
- app.py:23 - add error handling
- utils.py:7 - refactor this function
- main.py:45 - add logging

[Step 3/3] 将清单写入 todo.md
[Tool] write_file({"path": "todo.md", "content": "# TODO List\n\n..."})

已将 TODO 清单写入 todo.md。

多步执行-上下文传递

多步骤共享messages
  • messages 作为上下文由外部传入
  • 返回messages,给到下一个步骤
  • 整体和run_agent一致
python
def run_agent_step(task, messages, max_iterations=5):
    messages.append({"role": "user", "content": task})  
    actions = []

    for _ in range(max_iterations):  
        response = client.chat.completions.create(
            model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
            messages=messages,
            tools=tools
        )
        message = response.choices[0].message
        messages.append(message)

        ifnot message.tool_calls:
            return message.content, actions, messages

        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            print(f"[Tool] {function_name}({function_args})")
            function_response = available_functions[function_name](**function_args "function_name")
            actions.append({"tool": function_name, "args": function_args})
            messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": function_response})

    return "Max iterations reached", actions, messages  

Plan AS Tool

核心思想

Plan AS Tool
  • 前文:Plan为被动的,写死在代码里的。
  • 现在:Plan作为Tool,Agent可选择性的调用。
  • 优点:遇到复杂问题,LLM可主动调用Plan进行拆解,无需用户干预。
json
{
  "name": "plan",
  "description": "Break down complex task into steps and execute sequentially"
}

实现:递归执行和防无限循环

实现
  • 递归调用 run_agent_step
  • 排除plan工具本身:防止在规划中再次规划,陷入死循环了。
  • 上下文跨步共享
python
if function_name == "plan":
    plan_mode = True
    function_response = available_functions[function_name](**function_args "function_name")
    messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": function_response})

    if current_plan:
        results = []
        for i, step in enumerate(current_plan, 1):
            messages.append({"role": "user", "content": step})
            # 关键:排除 plan
            result, messages = run_agent_step(
                messages,
                [t for t in tools if t["function"]["name"] != "plan"]  
            )
            results.append(result)
        plan_mode = False
        current_plan = []
        return "\n".join(results), messages

示例

bash
用户: "重构项目的测试框架"


LLM 判断任务复杂 主动调用 plan 工具


plan() 返回 4 个步骤

  ├── Step 1: 分析当前测试结构
     └── run_agent_step()  [glob, read, grep]

  ├── Step 2: 创建新的测试目录
     └── run_agent_step()  [bash, write]

  ├── Step 3: 迁移现有测试文件
     └── run_agent_step()  [read, edit, write]

  └── Step 4: 验证所有测试通过
        └── run_agent_step()  [bash]

Rules/Skills/MCP

System Prompt

system prompt = 基础指令 + Rules(项目规则) + Skills(技能描述) + Memory(历史记忆)

痛点

未解问题解决方案新概念
工具是硬编码的外部配置文件动态加载工具MCP(Model Context Protocol)
没有行为约束声明式规则文件注入 promptRules + Skills
规划是被动的规划注册为 Agent 可自主调用的工具Plan-as-Tool

Rules (扩展SystemPrompt,定规矩)

Rules 概念

Rules 概念

背景

  • 不同项目/团队/场景对 Agent 的要求不同
  • 不用在对话中反复叮嘱,而是写一次规则文件永久生效

本质

  • 项目级SystemPrompt 扩展
  • 声明式文件,定制Agent的行为边界

Rules 实现

Rules 实现

nanoAgent Rules实现

  • .agent/rules/文件夹下的markdown文件
  • Agent启动时,全部加载,注入到SytemPrompt。

其他实现

  • ClaudeCode:CLAUDE.md
  • Cursor:.cursorrules
  • Github Copilot:.github/copilot-instructions.md

Rules加载

python
RULES_DIR = ".agent/rules"

def load_rules():
    rules = []
    ifnot os.path.exists(RULES_DIR):
        return ""
    try:
        for rule_file in Path(RULES_DIR).glob("*.md"):  
            with open(rule_file, 'r') as f: 
                rules.append(f"# {rule_file.stem}\n{f.read()}")  
        return "\n\n".join(rules) if rules else ""
    except:
        return ""

Rules注入

python
if rules:
    context_parts.append(f"\n# Rules\n{rules}")  

Rules 示例

markdown
<!-- .agent/rules/code-style.md -->
- 使用 Python 3.10+ 语法
- 所有函数必须有 docstring
- 变量命名使用 snake_case
- 不要使用 print 调试,使用 logging 模块
markdown
<!-- .agent/rules/safety.md -->
- 永远不要执行 rm -rf / 或类似的危险命令
- 修改文件前先备份
- 不要修改 .env 文件中的密钥

Skills (告诉Agent如何做事)

Skills 概念和实现

Skills 概念

核心作用

  • 定义做某件事情的核心方法:执行步骤、最佳实践、示例代码等。

存储格式

  • 文件夹
  • SKILL.md Markdown文件

Skills 加载

python
SKILLS_DIR = ".agent/skills"

def load_skills():
    skills = []
    ifnot os.path.exists(SKILLS_DIR):
        return []
    try:
        for skill_file in Path(SKILLS_DIR).glob("*.json"):  
            with open(skill_file, 'r') as f:  
                skills.append(json.load(f))  
        return skills
    except:
        return []

Skill 注入

python
if skills:
    context_parts.append(
        f"\n# Skills\n" + "\n".join(
            [f"- {s['name']}: {s.get('description', '')}"for s in skills]
        )
    )

Skills vs Rules

Skills vs Rules
  • Rules: 管约束,告诉 Agent "做人的底线"。
  • Skills: 管能力,告诉 Agent "做事的方法"。
维度RulesSkills
文件格式MarkdownJSON
作用约束行为("不要做什么")提供能力("可以怎么做")
类比公司规章制度员工培训手册
注入方式全文注入名称 + 描述摘要

ClaudeCode 16个Skill 及其分类

没有一个使用Tools声明来注册funtion calling,与其注册新函数,不如教LLM如何写代码。

Skill有 scripts/ ?有参考文档?核心执行方式
pdf脚本执行 + 参考文档
pptx脚本执行 + 参考文档
xlsx脚本执行
docx脚本执行
webapp-testing脚本执行
frontend-design纯 Prompt 注入
brand-guidelines纯 Prompt 注入
algorithmic-art纯 Prompt 注入
doc-coauthoring纯 Prompt 注入
internal-comms纯 Prompt 注入
web-artifacts-builder纯 Prompt 注入
canvas-design纯 Prompt + 资源
theme-factory纯 Prompt + 资源
slack-gif-creator❌ (有 core/)库调用型
mcp-builder参考文档 + 编排
skill-creator编排型(含子 Agent)

ClaudeCode SKill 调用流程

核心组件

javascript
SKILL.mdFsSkillRepository扫描解析
Skill 对象name, description, body, tools, resources
SkillToolSet6 个管理工具 + skill_run
DynamicSkillToolSet按需加载业务工具
SkillsRequestProcessor注入 system prompt
LLM 使用 Skill 过程
  • 查看所有技能name + desc
  • 加载某个具体的skill,把SKILL.md的body 注入system prompt
  • 按需加载详细文档
  • 在沙箱中执行命令
javascript
1. LLM 调用 skill_list()  
看到所有技能的 name + description~30 Token/
2. LLM 调用 skill_load("pdf") 
触发 state_delta 写入SKILL.md body 注入 system prompt
3. LLM 调用 skill_select_docs(docs=["forms.md"]) 
按需加载详细文档
4. LLM 调用 skill_run(command="python3 scripts/xxx.py") 
在沙箱中执行命令

Skill run的沙箱执行

bash
1. 创建隔离工作空间 /tmp/ws_xxx/
2. 将技能目录复制到工作空间(增量哈希优化,不重复拷贝)
3. 自动注入环境变量:$WORK_DIR$OUTPUT_DIR$SKILLS_DIR
4. bash -lc "python3 scripts/xxx.py" 在隔离目录中执行
5. 收集 stdout + 指定的输出文件,返回给 LLM

工作空间布局

javascript
/tmp/ws_session123/
├── skills/pdf/技能目录只读保护
│   ├── SKILL.md
│   ├── scripts/
│   ├── out/  → ../../out符号链接
│   └── work/ → ../../work符号链接
├── out/$OUTPUT_DIR
├── work/$WORK_DIR
└── runs/执行记录

Skill Run 核心流程

SKill Run

输入输出

  • 输入:一个技能名 + 一条 shell 命令
  • 输出:stdout + stderr + exit_code + 输出文件

注入

  • 第1层 工具 Schema → LLM 知道:有skill_run 这个工具
  • 第2层 System Prompt → LLM 知道:加载技能用skill_run执行
  • 第3层 SKILL.md body → LLM 知道:command参数如何写 "python3 scripts/xxx.py ..."
skill_run工具定义和Schema 注入

工具定义

  • name, desc
  • 参数
    • skill:skill名称
    • command:要执行的shell命令
    • output_files:输出文件的glob模式

System Prompt 注入

  • 告知何时可用skill_run
json
{
  "name": "skill_run",
  "description": "Run a command inside a skill workspace. Stages the entire skill directory and runs a single command.",
  "parameters": {
    "skill": {"type": "string", "description": "技能名称"},
    "command": {"type": "string", "description": "要执行的 shell 命令"},
    "output_files": {"type": "array", "description": "输出文件的 glob 模式"},
    ...
  }
}
python
instruction = f"""
Available skills:
{skill_instructions}
Tooling and workspace guidance:
- Skills run inside an isolated workspace...
- Prefer $SKILLS_DIR, $WORK_DIR, $OUTPUT_DIR... over hard-coded paths
- If a skill is not loaded, call skill_load
- When body and needed docs/tools are present, call skill_run or use tools directly
"""
加载某个Skill,SKILL.md body注入
  • 加载SKILL
    • LLM调用skill_load("pdf"):把SKILL.md的body文本完整注入到SystemPrompt
  • 作用
    • 告知LLM command 具体怎么填写
bash
## 可用脚本

- `scripts/extract_form_structure.py` - 提取 PDF 表单结构
  用法:python3 scripts/extract_form_structure.py <input_pdf>

- `scripts/fill_fillable_fields.py` - 填充 PDF 表单字段
  用法:python3 scripts/fill_fillable_fields.py <input_pdf> <data_json>  
  输出到 $OUTPUT_DIR/filled_output.pdf

### 依赖安装
首次使用时运行:pip install -r scripts/requirements.txt
具体SKILL Run的调用示例

SKILL.md 的 body

  • Body是给LLM看的使用手册body质量整个系统的关键

框架功能

  • 在正确的时机把手册加入SystemPrompt
  • 提供skill_run这个万能执行器

后期考LLM

  • LLM读完后,怎么组装命令、什么顺序调用,全靠LLM自己的理解能力
bash
1. system prompt 里的技能列表
 发现 "pdf" 技能匹配
2. skill_load("pdf")
 SKILL.md body 注入 system prompt
3. 阅读 body
 发现 scripts/extract_form_structure.py 可以分析表单结构
4. 拼装 command:
	"python3 scripts/extract_form_structure.py input.pdf"
5. skill_run(skill="pdf", command="python3 scripts/extract_form_structure.py input.pdf")
6. 读返回的 stdout
 知道表单有哪些字段
7. 再调 skill_run 执行填充脚本

Skill 示例

nanoAgent Skill 示例,和标准的claude code /openclaw 示例不一样,后续补充示例

json
{
  "name": "docker-deploy",
  "description": "Deploy application using Docker Compose. Steps: 1) Check Dockerfile exists, 2) Run docker-compose build, 3) Run docker-compose up -d, 4) Verify containers are running.",
  "triggers": ["deploy", "docker", "container"]
}

模式1:纯Prompt注入型

纯Prompt注入型

核心

  • Skill :一段精心编写的 system prompt
  • 没有任何外部执行。

价值

  • 提供领域专业知识(设计原则、色彩理论)
  • 约束 LLM 行为("永远不要用通用 AI 美学")
  • 引导思维流程("编码前先理解上下文")

代表

  • frontend-design、brand-guidelines、algorithmic-art

示例

bash
frontend-design/
└── SKILL.md 唯一的内容文件

SKILL.md

markdown
# 设计思考
在编码之前,先理解上下文并致力于一个 BOLD 美学方向:
- **目的**: 此界面解决什么问题?谁在使用它?
- **基调**: 选择一个极端:粗犷极简、复古未来主义、奢华精致...
- **差异化**: 什么让这个令人难忘?

## 前端美学指南
- 选择美观独特的字体,避免 Arial/Inter...
- 使用 CSS 变量保持一致性...
- **永远不要** 使用通用 AI 生成的美学...

执行流程

javascript
用户: "帮我做一个科技感的落地页"

LLM 匹配到 frontend-design加载 SKILL.md
SKILL.md body 注入 system message
LLM 根据注入的美学指南直接写 HTML/CSS/React
通过 write_to_file 输出代码

用户得到一个有设计感的落地页

模式2:脚本执行型

脚本执行型

核心

  • SKILL.md教程scripts/工具箱

价值(PDF示例)

  • SKILL.md,本质也是Prompt
    • 一份Python PDF的完整教程,用什么库、怎么用、示例代码如何。
    • 不是给机器跑,是教LLM怎么写代码的。
  • scripts/
    • 给skill_run执行的预制工具

代表

  • pdf、pptx、xlsx、webapp-testing

PDF 示例

bash
pdf/
├── SKILL.md 使用手册 + 代码示例
├── forms.md 表单填写指南
├── reference.md 高级参考
└── scripts/
    ├── extract_form_structure.py
    ├── fill_fillable_fields.py
    ├── convert_pdf_to_images.py
    └── ... (共 8 个脚本)

执行流程

javascript
用户: "帮我填写这个 PDF 表单"

LLM 加载 SKILL.md了解 PDF 操作全景
发现需要填表按需加载 forms.md
按照文档指引调用预制脚本

skill_run("python3 scripts/extract_form_structure.py input.pdf")
  │   → 返回: "Found 12 fields..."

skill_run("python3 scripts/fill_fillable_fields.py input.pdf data.json")
  │   → 返回: 填好的 PDF

用户得到填好的 PDF 表单

SKILL.md代码示例和预制脚本的对比:

SKILL.md 中的代码示例scripts/ 预制脚本
用途教 LLM 怎么写代码直接执行的黑盒工具
谁执行LLM 自己写 + Agent 基础工具skill_run 在沙箱中执行
适合简单一次性操作复杂、需要验证的工作流

模式3:库调用型

库调用型

核心

  • SKILL 自带python库,LLM 写代码可以 import 它
  • LLM不执行预制脚本,而是现场编写自定义脚本,组合SKILL提供的库函数完成任务。

价值

  • SKILL.md:API文档,教LLM如何使用函数。
  • 库函数:提供可调用的SDK

代表

  • slack-gif-creator

示例

core是一个python库

bash
slack-gif-creator/
├── SKILL.md API 文档 + 使用示例
├── requirements.txt 依赖声明
└── core/ Python 库(不是 scripts/!)
    ├── gif_builder.py GIFBuilder
    ├── validators.py validate_gif, is_slack_ready
    ├── easing.py 缓动函数
    └── frame_composer.py 帧生成辅助

执行流程

javascript
用户: "做一个心跳动画的 Slack emoji GIF"

LLM 加载 SKILL.md理解 API
LLM 自己编写一个完整脚本

from core.gif_builder import GIFBuilder
from PIL import Image, ImageDraw
import math

builder = GIFBuilder(width=128, height=128, fps=10)
for i in range(20):
scale = 0.8 + 0.4 * abs(math.sin(i/19 * math.pi * 2))
frame = Image.new('RGB', (128, 128), (255, 240, 240))
  │       # ... 绘制心形 + 缩放 ...
builder.add_frame(frame)
builder.save('heartbeat.gif', optimize_for_emoji=True)

skill_run 执行这个 LLM 写的脚本
  │   (core/ 已被复制到工作空间可直接 import

用户得到优化过的 Slack emoji GIF

与脚本执行型的关键区别

脚本执行型(pdf)库调用型(slack-gif-creator)
文件结构scripts/xxx.py(独立可执行)core/xxx.py(Python 模块)
调用方式skill_run("python3 scripts/xxx.py")LLM 自己写脚本 import core.xxx
LLM 角色调用者(跑预制脚本)开发者(组合库函数写新代码)

模式4:参考文档渐进型

参考文档渐进型

核心

  • SKILL.md路由表详细文档按需加载

价值

  • SKILL.md:本身不包含详细操作步骤,它只告诉 LLM "你要做的事情,应该去读哪份文档"
  • 定义多个详细文档不用一次性全部加载,大量节省token。

代表

  • pptx、mcp-builder

示例

bash
pptx/
├── SKILL.md 路由表(Quick Reference)
├── editing.md 编辑工作流(6.9KB)
├── pptxgenjs.md 从零创建(12.8KB)
└── scripts/
    ├── thumbnail.py
    └── office/
        ├── unpack.py
        └── soffice.py

路由表

markdown
## Quick Reference

| Task | Guide |
|------|-------|
| Read/analyze content | `python -m markdown presentation.pptx` |
| Edit or create from template | Read [editing.md](editing.md) |
| Create from scratch | Read [pptxgenjs.md](pptxgenjs.md) |

三层信息模型

提示

第1层 description(~50词)

  • "Presentation creation, editing, and analysis..."
  • 始终在可用技能列表中,LLM 判断是否需要加载

第2层 SKILL.md body(~200行)

  • Quick Reference 路由表 + 设计指南 + QA 流程
  • 加载后注入 system message,LLM 知道大方向

第3层 editing.md / pptxgenjs.md(按需)

  • 详细操作步骤 + 代码示例
  • LLM 通过 skill_select_docs 按需加载
javascript
第1层 description~50
  "Presentation creation, editing, and analysis..."
始终在可用技能列表中LLM 判断是否需要加载
第2层 SKILL.md body~200
  Quick Reference 路由表 + 设计指南 + QA 流程
加载后注入 system messageLLM 知道大方向
第3层 editing.md / pptxgenjs.md按需
  详细操作步骤 + 代码示例
LLM 通过 skill_select_docs 按需加载

Token 节省效果

渐进按需加载节省Token

一次性全部加载

  • SKILL.md + editing.md + pptxgenjs.md ≈ 28.7KB ≈ ~7000 tokens

渐进加载(编辑任务)

  • description → ~50 tokens
  • SKILL.md body → ~2000 tokens
  • editing.md → ~1700 tokens
  • 总计 ≈ 3750 tokens(节省 ~46%)

核心原则

  • 先粗筛再精选:面对海量数据/复杂问题,不要一步到位,应采用分层策略
    • 先用轻量信息粗筛:需要哪个 Skill?
    • 再用中等信息定方向:该读哪份文档?
    • 最后用详细信息执行:具体怎么操作?
注入层级触发条件典型 Token 消耗
L0: 概览始终注入~30 Token/技能
L1: SKILL.md bodyskill_load 后~500-2000 Token
L2: 详细文档skill_select_docs 后~1000-5000 Token

模式5:编排型

参考文档渐进型

核心

  • SKILL.md 不是工具说明,而是一个完整的多阶段工作流编排方案

价值

  • SKILL.md:定义了一条完整的多阶段流水线
  • Capture Intent → Interview → Write SKILL.mdRun TestsEvaluate → Improve → Repeat → Package

代表

  • skill-creator

示例

bash
skill-creator/
├── SKILL.md 32KB 的超详细编排指南
├── agents/ Agent 指令
   ├── grader.md 评分 Agent
   ├── comparator.md A/B 对比 Agent
   └── analyzer.md 分析 Agent
├── scripts/ 自动化脚本
   ├── aggregate_benchmark.py
   ├── run_loop.py
   ├── run_eval.py
   └── package_skill.py
├── eval-viewer/ 评估结果查看器
└── references/ 参考文档

SKILL.md 流水线

javascript
Capture IntentInterviewWrite SKILL.mdRun Tests
EvaluateImproveRepeatPackage
执行时 LLM 会
  1. 与用户对话确定技能范围(纯对话,无工具)
  2. 写出新 SkillSKILL.md(用 write_to_file)
  3. 并行 spawn 子 Agent 做 A/B 测试(with-skill vs baseline)
  4. 执行评估脚本 聚合测试数据、生成可视化报告
  5. 收集用户反馈 → 修改 → 回到第 3 步循环迭代
  6. 打包输出 最终的.skill 文件

5种SKILL 模式对比和选择

选择

写好 SKILL.md 是一切的基础。

你的场景推荐模式你需要做的
教 LLM 遵循某种规范/风格纯 Prompt 注入写好 SKILL.md,全靠文本质量
需要 LLM 操作特定文件格式脚本执行型写预制脚本放 scripts/,SKILL.md 写使用说明
需要 LLM 灵活组合 API库调用型写 Python 库放 core/,SKILL.md 当 API 文档
知识量大,不同任务需要不同文档渐进加载型SKILL.md 做路由表,详细文档独立存放
需要 LLM 执行复杂多步骤工作流编排型SKILL.md 定义流水线 + 脚本工具链 + 子 Agent

对比

bash
                    Skill 执行模式光谱
  ◄── 轻量 ────────────────────────────────── 重量 ──►
 Prompt      参考文档       库调用       脚本执行      编排
  注入型         渐进加载型
  ──────────    ──────────    ──────────   ──────────   ──────────
  frontend-     pptx          slack-gif-   pdf          skill-
  design        mcp-builder   creator      xlsx         creator
  ──────────    ──────────    ──────────   ──────────   ──────────
  仅注入 body    注入 +         注入 +       注入 +        注入 +
 system      按需加载       LLM 写代码   预制脚本      多步骤
  message        docs          import    skill_run    工作流
框架机制模式一模式二模式三模式四模式五
skill_load → body 注入
skill_select_docs
skill_run (预制脚本)
skill_run (LLM 写的脚本)
Tools: 声明 (function calling)
子 Agent 编排

SKILL 实现思考

SKILL vs Funtion Call

SKILL vs Funtion Call

  • funtion call:确定性操作设计
  • SKILL:面对的是不确定性的内容

无需注册funtion calling,仅需写好SKILL.md

  • LLM 本身就是最好的代码执行器
    • LLM 看一眼SKILL.md,就能写出正确代码
    • FC 只能只能调用预定义的函数,不能灵活组合
  • skill_run是万能兜底
    • 任何语言、任何命令,只要能在 shell 里跑skill_run 就能执行
    • 不需要为每个操作都定义工具 Schema
    • Token 节省
      • Skill_run的schame:20 Token
      • 8个独立funtion call schema:1600-4000 Token
  • Tools的价值场景很窄
    • 只有不能通过写代码/Shell完成的操作,才需要注册funtion calling。
    • 但Anthropic的官方Skill都可以通过代码+脚本完成
SKILL 核心执行力

执行力

  • Skill 执行力 = SKILL.md body 质量 × (Agent 基础工具 + skill_run 沙箱能力)

组件

  • SKILL.md 的 body 文本灵魂,它决定LLM能否理解任务选对方法写对代码
  • skill_run: 是手脚,它提供了在隔离沙箱中 执行任意命令的能力
  • Tools: 声明是可选配件。框架支持,但大多数场景不需要。

MCP

MCP 概念

MCP Tools

all_tools = base_tools + mcp_tools

MCP

背景

  • 避免硬编码Tools
  • 通过MCP协议,配置可用的Tools即可。
  • 分离工具定义工具实现

MCP

  • 配置文件,配置tool/mcpservers。

示例

  • GitHub MCP Server:Agent可以操作PR和Issue。
  • 数据库 MCP Server:Agent可执行SQL查询。

MCP 解决的根本问题

笔记

通用标准协议

  • 工具方(Server):只要按MCP标准写1次接口,所有Agent都能用。
  • Agent方(Client):只需要实现一个MCP Client,就能接入市面上所有现成的MCP Server。
  • 只需要写一次,避免每种Agent框架都去实现一套工具实现。
bash
没有 MCP 的世界: MCP 的世界:

Agent A         Agent B             MCP Server: Slack  MCP Server: GitHub
├── Slack (自写) ├── Slack (自写)         │                │
├── GitHub(自写) ├── Jira (自写)          └───── 标准协议 ──┘
└── DB (自写)    └── DB (自写)                    │
                                         ┌───────┼───────┐
每个 Agent 各写各的                      Agent A  Agent B  Agent C
N × M 的工作量                          工具实现一次,全部共享
                                         N + M 的工作量

MCP 实现

配置文件:.agent/mcp.json

json
{
  "mcpServers": {
    "filesystem": {
      "disabled": false,
      "tools": [{
        "name": "list_directory",  
        "description": "List contents of a directory with metadata",
        "parameters": {
          "type": "object",
          "properties": {"path": {"type": "string"}},  
          "required": ["path"]  
        }
      }]
    },
    "database": {
      "disabled": true,
      "tools": [...]
    }
  }
}

MCP 配置读取

python
MCP_CONFIG = ".agent/mcp.json"

def load_mcp_tools():
    ifnot os.path.exists(MCP_CONFIG):
        return []
    try:
        with open(MCP_CONFIG, 'r') as f:
            config = json.load(f)
        mcp_tools = []
        for server_name, server_config in config.get("mcpServers", {}).items():
            if server_config.get("disabled", False):
                continue
            for tool in server_config.get("tools", []):  
                mcp_tools.append({"type": "function", "function": tool})  
        return mcp_tools
    except:
        return []

各模块组合(记忆+Rules+Skills+MCP)

核心代码

python
def run_agent_claudecode(task, use_plan=False):
    print("[Init] Loading ClaudeCode features...")

    # 1. 从文件系统加载所有外部配置
    memory = load_memory()        # 历史记忆  
    rules = load_rules()          # 行为规则 
    skills = load_skills()        # 技能注册
    mcp_tools = load_mcp_tools()  # MCP 外部工具

    # 2. 合并工具列表(基础工具 + MCP 工具)
    all_tools = base_tools + mcp_tools  

    # 3. 构建 system prompt(基础指令 + Rules + Skills + Memory)
    context_parts = ["You are a helpful assistant..."]
    if rules:
      	context_parts.append(f"\n# Rules\n{rules}")  
    if skills:
      	context_parts.append(f"\n# Skills\n...")  
    if memory:
      	context_parts.append(f"\n# Previous Context\n{memory}")  

    messages = [{"role": "system", "content": "\n".join(context_parts)}]  
    # 4. 执行 ...

关键示例

bash
┌─────────────────────── 文件系统 ───────────────────────┐

  .agent/rules/*.md load_rules()     system prompt
  .agent/skills/*.json load_skills()    system prompt
  .agent/mcp.json load_mcp_tools() tools 列表
  agent_memory.md load_memory()    system prompt

└─────────────────────────────────────────────────────────┘


              ┌──── Agent 运行时 ────┐

  system prompt =
    基础指令
    + Rules
    + Skills
    + Memory

  tools =
    base_tools (7个)   │
    + mcp_tools (N个)  │

              └──────────────────────┘

关键架构

bash
┌───────────────────────────────────────────────────────┐
                    Agent 架构全景

  ┌──────────────┐  第三篇:agent-claudecode.py
  Rules  行为约束层 ──── .agent/rules/
  Skills  技能知识层 ──── .agent/skills/
  MCP  工具扩展层 ──── .agent/mcp.json
  Plan Tool  自主规划层 ──── plan() 作为工具
  ├──────────────┤  第二篇:agent-plus.py
  Memory  持久记忆层 ──── agent_memory.md
  Planning  任务分解层 ──── create_plan()       
  Multi-step  多步编排层 ──── 步骤间上下文共享
  ├──────────────┤  第一篇:agent.py
  LLM  推理决策层 ──── OpenAI API
  Tools  工具执行层 ──── bash/read/write
  Loop  核心循环层 ──── for + tool_calls
  └──────────────┘
└───────────────────────────────────────────────────────┘

每一层都在回答一个关键问题:

Agent Core

Loop

  • Agent 如何自主运行

Tools

  • Agent 如何作用于世界

Memory

  • Agent 如何记住过去

Planning

  • Agent 如何应对复杂任务

Rules

  • Agent 如何遵守约束

Skills

  • Agent 如何获得领域知识

MCP

  • Agent 如何获得新工具

SubAgent (临时工)

SubAgent概念 (给Agent找帮手,用完就弃)

信息

背景

  • 用于只有1个Agent在干活。
  • 希望找帮手、分工合作

主Agent

  • 项目经理,把子任务委派给有不同专业身份SubAgent。各管一块,互不干扰。

SubAgent

  • 特性
    • 拥有不同专业身份,执行子任务。
    • 但没有名称、没有工位、没有记忆。
    • 一次性的多次独立的用完就扔临时工
  • 价值
    • 单次任务内的分工问题,不是长期协作问题。
    • 给子任务一个干净的上下文专注的角色,而不是构建一个持久的团队。
python
# 主 Agent: 协调者
"You are an orchestrator agent. You can delegate to sub-agents..."

# SubAgent: 专家
f"You are a {role}. Be concise and focused. Only do what is asked."

SubAgent 生命周期和示例

生命周期

bash
生成 接收任务 干活(可以调用工具)→ 返回结果摘要 消亡

类比:但是没有人物名称等内容,有名称持久化记忆则为Teams。

bash
之前(一个人干所有活):

  老板 "小张,你把前端后端数据库全搞定"
         小张(一个人扛所有)
         - 写后端 API...
         - 写前端页面...(等等,后端那个接口叫啥来着?)
         - 建数据库表...(前端那个字段是什么格式?)


现在(项目经理 + 专人):

  老板 项目经理(主 Agent)

              ├── "后端用 FastAPI" 后端工程师(SubAgent A)
              ├── "前端用 React" 前端工程师(SubAgent B)
              └── "验证能跑通" 测试工程师(SubAgent C)

  每个人只管自己的事,干完把结果交给项目经理汇总。

SubAgent 调用链路

bash
用户: "创建一个 TODO 应用,包含 Python 后端和 HTML 前端"


 Agent run_agent() 循环启动


(1) 代码把 messages + tools 列表发送给 LLM
    tools 列表里包含: [read, write, edit, glob, grep, bash, subagent]
                                                            ^^^^^^^^
                                                       LLM 看到了这个工具


(2) LLM 分析任务,决定委派,返回:
    {"tool_calls": [{"function": {"name": "subagent",
                                  "arguments": {"role": "Python backend developer",
                                                "task": "用 FastAPI 创建..."}}}]}


(3) 核心循环中的通用调度代码执行:
    fn = "subagent"
    args = {"role": "Python backend developer", "task": "..."}
    result = available_functions["subagent"](**args ""subagent"")
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
             走到了我们写的 subagent() 函数!


(4) subagent() 内部启动一个全新的 Agent 循环
    - 独立的 system prompt: "You are a Python backend developer."
    - 独立的 messages 列表
    - 可以使用 read/write/edit/bash 等工具
    - 循环结束后,返回结果文本


(5) 结果返回给主 Agent,主 Agent 可能继续派出前端 SubAgent...

SubAgent-as-Tool

SubAgent Tool 定义

json
{
    "name": "subagent",
    "description": "Delegate a task to a specialized sub-agent with its own role and independent context.",
    "parameters": {
        "type": "object",
        "properties": {
            "role": {"type": "string", "description": "The sub-agent's specialty, e.g. 'Python backend developer'"},  
            "task": {"type": "string", "description": "The specific task to delegate"}  
        },
        "required": ["role", "task"]  
    }
}

SubAgent Tool 实现

python
def subagent(role, task):
    """启动一个独立的 Agent 循环,拥有专属角色和独立上下文"""
    print(f"\n[SubAgent:{role}] 开始: {task}")

    # 关键 1:独立的 messages,独立的 system prompt
    sub_messages = [
        {"role": "system", "content": f"You are a {role}. Be concise and focused. Only do what is asked."},  
        {"role": "user", "content": task}  
    ]

    # 关键 2:排除 subagent 自身,防止无限递归
    sub_tools = [t for t in tools if t["function"]["name"] != "subagent"]

    # 关键 3:一个完整的 Agent 循环(和第一篇的核心循环一模一样)
    for _ in range(10):
        response = client.chat.completions.create(
            model=MODEL, messages=sub_messages, tools=sub_tools
        )
        message = response.choices[0].message
        sub_messages.append(message)

        if not message.tool_calls:
            print(f"[SubAgent:{role}] 完成")
            return message.content

        for tc in message.tool_calls:
            fn = tc.function.name
            args = json.loads(tc.function.arguments)
            print(f"  [SubAgent:{role}] {fn}({args})")
            result = available_functions[fn](**args "fn")  
            sub_messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})  

    return"SubAgent: max iterations reached"

注册到路由表

python
available_functions["subagent"] = subagent

SubAgent vs Plan

维度PlanSubAgent(本文)
上下文所有步骤共享 messages每个 SubAgent 独立 messages
身份同一个 Agent,同一个角色每个 SubAgent 不同的专业角色
生命周期步骤间 Agent 持续存在生成 → 干活 → 返回摘要 → 消亡(一次性)
跨次记忆步骤 2 能看到 步骤 1的全部细节SubAgent B 看不到 SubAgent A 做了什么
适合步骤之间有依赖子任务之间相对独立
类比一个人按步骤做事叫了个跑腿临时工,干完就走

最新Agent 架构

bash
┌───────────────────────────────────────────────────────┐
                    Agent 架构全景

  ┌──────────────┐  第四篇 (本文)                       │
  SubAgent  多智能体协作 ── subagent() 工具
  ├──────────────┤  第三篇
  Rules  行为约束层 ──── .agent/rules/
  Skills  技能知识层 ──── .agent/skills/
  MCP  工具扩展层 ──── .agent/mcp.json
  ├──────────────┤  第二篇
  Memory  持久记忆层 ──── agent_memory.md
  Planning  任务分解层 ──── create_plan()       
  ├──────────────┤  第一篇
  LLM  推理决策层 ──── OpenAI API
  Tools  工具执行层 ──── bash/read/write
  Loop  核心循环层 ──── for + tool_calls
  └──────────────┘
└───────────────────────────────────────────────────────┘
文件核心主题一句话总结
agent.py (115行)工具 + 循环Agent 的最小本质——LLM 是大脑,代码是手脚
agent-plus.py (182行)记忆 + 规划时间维度——记住过去、规划未来
agent-claudecode.py (265行)Rules + Skills + MCP空间维度——扩展知识与工具
agent-subagent.py (192行) ⭐新SubAgent协作维度——给 Agent 找帮手

Teams Agent(正式工)

Teams Agent 概念

Teams Agent

背景

  • 后端写完API -> 前端需知接口;测试发现Bug后 -> 告诉开发去修;开发修完 -> 测试再测一遍
  • 同一个人被多次找到,而且他还记得上次做了什么

SubAgent

  • 没有持久记忆、没法交互,做不到。

Teams Agent

  • 能跨多轮对话、持久记忆
  • 有身份名字角色,有生命周期。创建、干活、解散。而非用完即弃。
  • Agent之间可以互相发消息,不是隔离看不到的。
问题SubAgent(临时工)Teams Agent(正式员工)
有名字吗?❌ 只有一个临时角色描述✅ 有名字(alice)、有固定角色
记得上次做了什么吗?❌ 每次调用都失忆✅ 多次交互之间记忆持续累积
能收到同事消息吗?❌ 互相看不到✅ 有收件箱,能收消息
什么时候消失?函数返回就没了团队解散才消失

TeamsAgent 类定义

TeamsAgent

TeamsAgent

name/role

  • 有名字和角色

inbox

  • 收件箱

messages

  • 对象属性,只要对象活着,messages就一直存在,记得之前做过什么
python
class Agent:
    def __init__(self, name, role):
      	# 身份:有名字,有角色
        self.name = name             
        self.role = role
        # 通信:收件箱
        self.inbox = []  
        # 记忆:持久保持
        self.messages = [        
            {"role": "system", "content": f"You are {name}, a {role}. Be concise and focused."}  
        ]  

对比SubAgent

python
# SubAgent(上文)—— 一个函数,用完就没
def subagent(role, task):
    sub_messages = [...]  # 局部变量,函数返回即消亡
    for _ in range(10):
        ...
    return result  # 返回后 sub_messages 被垃圾回收,一切归零

Chat和Receive收发消息

Chat:发消息

python
def chat(self, task):
    # 第 1 步:如果 inbox 有新消息,先读取并消化
    if self.inbox:
        mail = "\n".join(f"[来自 {m['from']}]: {m['content']}"for m in self.inbox)  
        self.messages.append({"role": "user", "content": f"你收到了团队成员的消息:\n{mail}"})  
        resp = client.chat.completions.create(model=MODEL, messages=self.messages)
        self.messages.append(resp.choices[0].message)  
        self.inbox.clear()

    # 第 2 步:执行本次任务(和之前的 Agent 循环一样)
    self.messages.append({"role": "user", "content": task})  
    for _ in range(10):
        response = client.chat.completions.create(model=MODEL, messages=self.messages, tools=tools)
        message = response.choices[0].message
        self.messages.append(message)
        ifnot message.tool_calls:
            return message.content
        for tc in message.tool_calls:
            # ... 执行工具,追加结果(和第一篇完全一样)

收消息

python
def receive(self, sender, message):
    self.inbox.append({"from": sender, "content": message})  

Team类 生命周期管理和通信编排

python
class Team:
    def __init__(self):
        self.agents = {}  # name → Agent

    def hire(self, name, role):
        """招募:创建一个持久 Agent"""
        agent = Agent(name, role)  
        self.agents[name] = agent  
        return agent

    def send(self, from_name, to_name, message):
        """点对点通信"""
        self.agents[to_name].receive(from_name, message)  

    def broadcast(self, from_name, message):
        """广播:给团队所有其他人发消息"""
        for name, agent in self.agents.items():  
            if name != from_name:   
                agent.receive(from_name, message)  

    def disband(self):
        """解散:所有 Agent 生命周期结束"""
        self.agents.clear()  

协作流程

python
def run_team(task):
    team = Team()

    # 第 1 阶段:组建团队
    # LLM 自动拆分角色
    members = plan_team(task)  
    for m in members:
        team.hire(m["name"], m["role"])  

    # 第 2 阶段:逐个执行,每人干完广播成果
    for m in members:
        agent = team.agents[m["name"]]  
        result = agent.chat(m["task"])  
        team.broadcast(m["name"], f"我完成了任务。摘要: {result[:200]}")  

    # 第 3 阶段:最后一个成员做二次审查
    reviewer = team.agents[members[-1]["name"]]
    review = reviewer.chat("请根据团队成果做最终审查")

    # 第 4 阶段:解散
    team.disband()

交互示例

输入: 创建一个 TODO 应用,包含 Python 后端和 HTML 前端

bash
[PM] 分析任务,组建团队...
[团队] 3 人:
  1. alice backend developer FastAPI 创建 TODO 后端 API
  2. bob frontend developer 创建 HTML 前端页面
  3. carol test engineer 验证前后端能正常工作

============================================================
 1 阶段: 招募团队
============================================================
  [创建] alice (backend developer)
  [创建] bob (frontend developer)
  [创建] carol (test engineer)

============================================================
 2 阶段: 协作开发
============================================================

── [1/3] alice 开始工作 ──
  [alice] write({"path": "app.py", ...})
  [alice] → 已创建 app.py,包含 GET/POST/DELETE 三个接口...
  [广播] alice → 全体: 我完成了任务。摘要: 已创建 app.py...

── [2/3] bob 开始工作 ──
  (bob 的 inbox 里有 alice 的广播,他知道后端接口长什么样)
  [bob] write({"path": "index.html", ...})
  [bob] → 已创建 index.html,调用了 alice 定义的 API 接口...
  [广播] bob → 全体: 我完成了任务。摘要: 已创建 index.html...

── [3/3] carol 开始工作 ──
  (carol 的 inbox 里有 alice 和 bob 的广播)
  [carol] read({"path": "app.py"})
  [carol] read({"path": "index.html"})
  [carol] bash({"command": "python -c 'import app; print(\"OK\")'"})
  [carol] → 后端代码语法正确,前端页面已创建,接口调用地址匹配...
  [广播] carol → 全体: 我完成了任务。摘要: 验证通过...

============================================================
  第 3 阶段: carol 做最终审查
============================================================
  (carol 被第二次调用 chat(),她还记得第一次测试的结果)
  [carol] → 最终审查:后端 app.py 包含 3 个接口(GET/POST/DELETE),
            前端 index.html 已正确引用后端地址,代码验证通过,可以交付。

Teams vs SubAgent

如何选择
  • 任务简单互不相关SubAgent
  • 需要协作需要记忆Teams
场景选 SubAgent选 Teams
子任务之间完全独立✅ 互不干扰,简单直接没必要,杀鸡用牛刀
后续任务依赖前面的结果❌ 看不到别人做了什么✅ 通过通信通道传递信息
需要同一个人多次返工❌ 每次都是新人,不记得✅ 持久记忆,记得上次做了什么
需要测试 → 修 bug → 再测试❌ 做不到✅ 测试人员和开发都能被多次调用

最新Agent 架构

核心新增一句话
工具 + 循环Agent 的最小本质
记忆 + 规划记住过去,规划未来
Rules + Skills + MCP扩展知识与工具
SubAgent一次性临时工
Agent 类 + Team 类有记忆、有身份、能通信的正式团队

上下文压缩

主要压缩方法

不断交互,迟早会爆。

python
1 轮: messages += [LLM的回复, 工具的返回结果]
2 轮: messages += [LLM的回复, 工具的返回结果]
3 轮: messages += [LLM的回复, 工具的返回结果]
...
方法

方法1:更大上下文模型

  • 迟早会爆

方法2:限制最大循环次数

  • 任务做不完

方法3:截断工具返回结果

  • 能减缓增长速度,但是可能丢失关键信息,且治标不治本

方法4:压缩旧的对话历史

  • 早期详细对话压缩成摘要,只保留要点。
  • LLM自己总结历史,然后轻装上阵,继续干活。
  • 记住要点忘掉细节保留现场

nanoAgent 的压缩是最朴素的实现。业界的方案更加精细。似乎ClaudeCode就很简单一个压缩Prompt

维度agent-compact.pyOpenClaw / Claude Code 等生产级实现
触发条件消息条数超过固定阈值基于 token 数精确计算,考虑模型的实际窗口大小
压缩方式一次性把所有旧消息压缩成一段摘要分层压缩:最近的保留原文,稍远的做摘要,更远的只保留关键事实
保留策略固定保留最近 N 条智能选择:保留包含文件路径、错误信息等关键消息
摘要质量通用摘要 prompt针对 coding 场景优化的摘要 prompt,确保保留文件路径、代码片段、决策原因

压缩示例

bash

压缩前的 messages(30 条,快爆了):
┌────────┐
 system 永远保留
├────────┤
 user 最初的任务
 assist LLM 调用了 bash
 tool bash 输出了 200 行文件列表
 assist LLM 调用了 read_file
 tool 文件内容 500            ─┐
 assist LLM 决定统计行数
 tool 统计结果 这些旧消息
 assist LLM 调用了 grep 交给 LLM 做摘要
 tool grep 结果 300
 ... 更多历史                   ─┘
 assist LLM 准备写文件             ─┐
 tool 写入成功 最近 6
 assist LLM 调用 read 验证 保留原样
 tool 文件内容 (不压缩)
 assist LLM 准备做最后总结
 user 当前操作                   ─┘
└────────┘

 compact_messages() 

压缩后的 messages(9 条,清爽了):
┌────────┐
 system 永远保留(不动)
├────────┤
 user "之前的对话摘要:找到了 42 个 Python 文件,
│        │    统计了行数,最长的是 utils.py (350行)..."
 assist "明白了,我继续。"
├────────┤
 assist LLM 准备写文件             ─┐
 tool 写入成功 最近 6
 assist LLM 调用 read 验证 完整保留
 tool 文件内容
 assist LLM 准备做最后总结
 user 当前操作                   ─┘
└────────┘

关键代码实现

核心Prompt

Summarize the following conversation history. Keep all important facts, file paths, command results, and decisions. Be concise but don't lose critical details.

关键代码

python
COMPACT_THRESHOLD = 20  # 超过 20 条就压缩
KEEP_RECENT = 6         # 保留最近 6 条不压缩

def compact_messages(messages):
    if len(messages) <= COMPACT_THRESHOLD:
        return messages  # 没超阈值,不压缩
		
    # system prompt 永远保留
    system_msg = messages[0] 
    # 旧消息 → 要被压缩
    old_messages = messages[1:-KEEP_RECENT]  
    # 最近的消息 → 保留原样
    recent_messages = messages[-KEEP_RECENT:]  

    # 把旧消息拼成文本
    old_text = ""
    for msg in old_messages:
        role = msg.get("role", "unknown") if isinstance(msg, dict) else getattr(msg, "role", "unknown")
        content = msg.get("content", "") if isinstance(msg, dict) else getattr(msg, "content", "")
        if content:
            old_text += f"[{role}]: {content}\n"

    # 调用 LLM 生成摘要
    summary_response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": "Summarize the following conversation history. Keep all important facts, file paths, command results, and decisions. Be concise but don't lose critical details."},  
            {"role": "user", "content": old_text}  
        ]
    )
    summary = summary_response.choices[0].message.content  

    # 重新组装
    return [
        system_msg,
        {"role": "user", "content": f"[Previous conversation summary]: {summary}"}, 
        {"role": "assistant", "content": "Understood. I have the context from our previous conversation. Let me continue."},  
        *recent_messages  
    ]

最新Agent架构

核心主题解决什么问题
工具 + 循环Agent 如何自主工作
记忆 + 规划Agent 如何记住过去、规划未来
Rules + Skills + MCPAgent 如何扩展知识和工具
SubAgentAgent 如何临时找帮手
TeamsAgent 如何组建持久团队
上下文压缩Agent 如何在有限窗口内持续工作
如果把 Agent 比作一个人
  • Tools:手脚(工具)
  • Memory&Plan:笔记本(记忆)和地图(规划)
  • Rules/Skills/MCP:规章制度工具箱
  • SubAgent:能叫临时工帮忙
  • Teams:组建正式团队
  • 上下文压缩:学会"抓大放小"——记住要点、忘掉细节、轻装上阵

安全和权限控制

LLM 可能生成 rm -rf /等命令。

三道防线

串联示例

bash
LLM 输出一条命令


防线 1: 命令黑名单
 "rm -rf /" �� 直接拦截,不问用户
 "ls -la" 通过

防线 2: 用户确认
 "find . -name '*.py'" 用户看到后按 Y 放行
 用户按 N 跳过
 用户按 Q 终止 Agent

防线 3: 输出截断
 命令输出 10000 截断为首尾各 2500 字符
 命令输出 10 原样返回

结果返回给 LLM

命令黑名单

核心是正则匹配,只能拦阻已知危险,拦不住所有的危险。

python
DANGEROUS_PATTERNS = [
    r'\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*--no-preserve-root)',  # rm -rf
    r'\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?/',                     # rm /
    r'\bmkfs\b',                    # 格式化磁盘
    r'\bdd\s+.*of\s*=\s*/dev/',     # 覆写磁盘
    r'>\s*/dev/sd[a-z]',            # 重定向到磁盘设备
    r'\bchmod\s+(-R\s+)?777\s+/',   # chmod 777 /
    r':\(\)\s*\{',                  # fork bomb
    r'\bcurl\b.*\|\s*(ba)?sh',      # curl | bash
    r'\bwget\b.*\|\s*(ba)?sh',      # wget | bash
    r'\bshutdown\b',                # 关机
    r'\breboot\b',                  # 重启
]

def is_dangerous(command):
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            return True, pattern
    return False, None

用户确认

python
def ask_user_confirmation(tool_name, args):
    if AUTO_APPROVE:
        return True

    print(f"\n┌─ 确认执行 ─────────────────────────────")
    print(f"│ 工具: {tool_name}")
    for key, value in args.items():
        print(f"│ {key}: {str(value)[:200]}")
    print(f"└────────────────────────────────────────")

    while True:
        answer = input("[Y]执行 / [N]跳过 / [Q]终止 Agent > ").strip().lower()
        if answer in ('y', 'yes', ''):
            return True
        elif answer in ('n', 'no'):
            return False
        elif answer in ('q', 'quit'):
            sys.exit(0)

并非所有动作都需要确认

笔记
  • agent-safe.py 中,三个工具(bash、read_file、write_file)都会触发确认。

  • 但在实际产品中,确认策略可以更精细:

    • read_file 通常是安全的——只读不写,可以默认放行

    • write_file 要看路径——写入项目目录内的放行,写入 /etc/ 的要确认

    • bash 最危险——每次都确认,或者用白名单模式(只允许 lsgrepcat 等安全命令免确认)

  • --auto 参数可以跳过所有确认,用于信任场景(比如在 Docker 容器里运行)。

输出截断

输出截断

解决问题

  • 解决返回结果太大的问题, 导致下一轮调用llm失败,而非命令安全问题。
  • 比如LLM 执行了 cat /var/log/syslog,返回了 10MB 的日志。

截断策略

  • 首尾各一半
python
MAX_OUTPUT_LENGTH = 5000

def truncate_output(text):
    if len(text) <= MAX_OUTPUT_LENGTH:
        return text
    half = MAX_OUTPUT_LENGTH // 2
    return (
        text[:half]
        + f"\n\n... [输出过长,已截断。原始 {len(text)} 字符,保留首尾各 {half} 字符] ...\n\n"
        + text[-half:]
    )

新的 execute_bash

硬编码三道防线

python
def execute_bash(command):
    # 防线 1: 黑名单
    dangerous, pattern = is_dangerous(command)
    if dangerous:
        return f"�� 命令被拦截: {command}"

    # 防线 2: 用户确认
    ifnot ask_user_confirmation("execute_bash", {"command": command}):
        return "用户跳过了此命令。"

    # 执行
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
        output = result.stdout + result.stderr
    except subprocess.TimeoutExpired:
        output = "Error: 命令执行超时(30秒)"
    except Exception as e:
        output = f"Error: {str(e)}"

    # 防线 3: 输出截断
    return truncate_output(output)

加钩子新增三道防线

Hook 管道的execute_bash

python
def execute_bash(command):
    is_dangerous(command)           # 检查 1:黑名单
    ask_user_confirmation(...)      # 检查 2:用户确认
    result = subprocess.run(...)    # 实际执行
    truncate_output(result)         # 后处理:截断
python
# 定义 Hook 管道
before_hooks = [check_blacklist, ask_confirmation, log_command]
after_hooks  = [truncate_output, log_result]

# 通用的工具执行函数
def execute_tool(name, args):
    # 执行前:依次过所有 before hook
    for hook in before_hooks:
        blocked, msg = hook(name, args)
        if blocked:
            return msg              # 任何一个 hook 可以拦截

    # 实际执行
    result = available_functions[name](**args "name")

    # 执行后:依次过所有 after hook
    for hook in after_hooks:
        result = hook(name, result)

    return result
总访客数:   ·   总访问量:
PLM's Blog @ 2016 - 2026