从nanoAgent看OpenClaw/Trae/Claude Code底层原理
OpenClaw/Trae/Claude Code的Rules、Skills与MCP机制
经过前两个版本的迭代后,agent-plus继续进化成像 OpenClaw等一样的agent——agent-claudecode,新增了80行左右的代码,为了解决 agent-plus遗留的问题。
如果不知道讲什么请看上一篇:Agent进化:nanoAgent记忆与规划双突破
先回顾下 agent-plus未解决的问题以及新的解决方式:
| 未解问题 | 解决方案 | 新概念 |
|---|---|---|
| 工具是硬编码的 | 外部配置文件动态加载工具 | MCP(Model Context Protocol) |
| 没有行为约束 | 声明式规则文件注入prompt | Rules |
| 规划是被动的 | 把规划注册为 Agent 可自主调用的工具 “规划即工具 Plan as Tool” |
工具集的扩充
agent-claudecode 在之前的基础工具上扩充了一些更常用的工具,成自己的核心工具集;这些新增的工具也不是随意选取的,它能让自己的能力大增。
base_tools = [
{"type": "function", "function": {"name": "read", "description": "Read file with line numbers", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "offset": {"type": "integer"}, "limit": {"type": "integer"}}, "required": ["path"]}}},
{"type": "function", "function": {"name": "write", "description": "Write content to file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
{"type": "function", "function": {"name": "edit", "description": "Replace string in file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}},
{"type": "function", "function": {"name": "glob", "description": "Find files by pattern", "parameters": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}}},
{"type": "function", "function": {"name": "grep", "description": "Search files for pattern", "parameters": {"type": "object", "properties": {"pattern": {"type": "string"}, "path": {"type": "string"}}, "required": ["pattern"]}}},
{"type": "function", "function": {"name": "bash", "description": "Run shell command", "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
{"type": "function", "function": {"name": "plan", "description": "Break down complex task into steps and execute sequentially", "parameters": {"type": "object", "properties": {"task": {"type": "string"}}, "required": ["task"]}}}
]
其中重点说下 edit和改进后的read 。
edit:用约束引导LLM行为
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)}"
在替换字符串时,old_string 必须在文件中仅出现一次,因为出现多次不知道应该替换哪一处。使用这种防御性编程防止LLM替换了不该替换的子串,大大降低了误编辑的概率。
设计工具的启示:用工具的约束来引导 LLM 的行为,比在 prompt 中告诫"小心编辑"可靠得多。约束是硬性的,prompt 是软性的。
read:行号+分页
def read(path, offset=None, limit=None):
try:
with open(path, 'r') as f:
lines = f.readlines()
start = offset if offset else 0
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)}"
该版本升级了read函数,这个版本支持offset和limit分页读取,并且每行加上了行号,行号可以让LLM更靳准的定位。
Rules:给agent立人设、立规矩
Rules放在 .agent/rules/目录下,agent启动初始化时加载并注入到 system prompt中。
RULES_DIR = ".agent/rules"
def load_rules():
rules = []
if not 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 ""
# agent启动时加载
rules = load_rules()
if rules:
context_parts.append(f"\n# Rules\n{rules}")
print(f"[Rules] Loaded {len(rules.split('# '))-1} rule files")
rules的本质
roles是项目级的 system prompt扩展。它解决了一个关键问题:不同项目、不同团队、不同场景对 Agent 的要求不同。与其每次在对话中反复叮嘱"记得遵守 pep8规范",不如写一次规则文件,永久生效。用声明式文件定制 Agent 的行为边界。
所以现在的 agent-claudecode 由之前的 agent-plus只有 基础指令+记忆 的两层 system prompt拼接 升级到了四层拼接。
agent-claudecode system prompt = 基础指令 + Rules(项目规则) + Skills(技能描述) + Memory(历史记忆)
Skills:可插拔的技能注册
Skills 是 .agent/skills/ 目录下的 JSON 文件,以列表摘要的形式注入 system prompt
SKILLS_DIR = ".agent/skills"
def load_skills():
skills = []
if not 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 []
if skills:
context_parts.append(f"\n# Skills\n" + "\n".join([f"- {s['name']}: {s.get('description', '')}" for s in skills]))
print(f"[Skills] Loaded {len(skills)} skills")
关于 Skill 的文件格式: 在 OpenClaw / Claude Code 的实际实现中,Skill 的标准格式是 Markdown(每个 Skill 目录下有一个 SKILL.md,里面详细描述执行步骤、最佳实践、示例代码等)。但 nanoAgent 原始仓库中采用的是 JSON 格式,所以代码里用 json.load() 来解析。这不影响理解核心思路——不管是 Markdown 还是 JSON,本质都是"把技能描述加载出来注入到 system prompt"。格式只是载体,思想是一样的。
MCP:让agent拥有无限工具的协议
MCP(Model Context Protocol)是一个开放标准,它定义了 LLM 与外部工具之间的通信协议,任何遵循这个协议的工具服务都可以即插即用地接入Agent。
agent-claudecode中的MCP实现
MCP_CONFIG = ".agent/mcp.json"
def load_mcp_tools():
if not 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:
all_tools = base_tools + mcp_tools; agent的本地基础工具集加上 mcp服务提供的工具集,组成agent所能使用到的所有工具集合。
不过在 nanoAgent的MCP实现中,实际是简化版的MCP,它只是实现了“工具注册”的demo,没有实现mcp的工具执行(通过网络调用远程mcp服务的能力),实际调用时会走到 else分支语句:Error: Unknown tool,agent-claudecode的实现虽然不完整,但它展示了 MCP 集成的核心思路:工具定义与工具实现的分离。在完整实现中,那个 else 分支会变成一个 MCP 客户端调用。
规划即工具:规划从手动触发到自主决策,让agent成为真正的六边形战士
agent-claudecode中将 plan注册为工具,这意味着LLM遇到复杂问题的时候可以主动调用plan工具进行任务拆解,化整为零,各个击破,无需用户干预。与算法中的分治算法思想类似。
agent-claudecode总结
- loop:agent如何自主运行?
- tools: agent如何作用于世界
- memory: agent如何回忆过去
- planning: agent如何规划未来(应对复杂任务)
- rules: agent如何遵守约束?
- skills: agent如何掌握做事的方法
- mcp:agent如何获得新工具
这7层架构是当今主流Agent框架的共同架构,nanoAgent用不到300行代码将这个架构完成的展现了出来。
更多推荐




所有评论(0)