https://arthurchiao.art/blog/openclaw-technical-notes-zh/
OpenClaw:技术解读和给 AI 应用开发的启示(2026)
作者用不到 632 行的核心 Python 代码,从零写一个极简版,取名 ToyClaw。
整个项目只有一个文件:toyclaw.py,全部依赖 Python 标准库,没有任何第三方包。但它却实现了:
代码地址:
https://github.com/ArthurChiao/arthurchiao.github.io/blob/master/assets/code/openclaw-technical-notes/toyclaw.py
一、代码结构总览:麻雀虽小,五脏俱全
toyclaw.py,从上到下可以分为七个核心模块。这是简化版的结构图:
texttoyclaw.py├── 1. 全局配置与常量├── 2. 工作区初始化├── 3. 上下文文件管理├── 4. 技能安装与加载├── 5. 系统提示词构建├── 6. Shell 命令执行(含安全拦截)├── 7. Agent 主循环(ReAct)└── 8. REPL 入口
二、模块 1:全局配置与默认文件内容
首先定义工作目录和几个固定的 Markdown 文件名。
这里用一个字典 DEFAULT_FILE_CONTENTS 存放每个文件的初始模板,方便第一次运行时自动创建。
DEFAULT_FILE_CONTENTS = { "USER.md": textwrap.dedent( """\ # USER.md Describe the human you are helping here. Examples: - name / nickname - language preference - working style - constraints to remember """ ), "SOUL.md": textwrap.dedent( """\ # SOUL.md Define the assistant's values, personality, and tone here. """ ), "IDENTITY.md": textwrap.dedent( """\ # IDENTITY.md Define the assistant's public identity here. Example: - name - vibe - style """ ), "AGENT.md": textwrap.dedent( """\ # AGENT.md Operating notes: - help the user directly - keep answers concise - use shell only when it materially helps - avoid destructive commands """ ),}
这些文件就是 AI 的“外挂记忆”和“人设配置”,后续会被动态读取并注入到系统提示词中。
工作目录直接定在 /tmp/toyclaw/
在这个文件夹下,它维护了几个熟悉的 Markdown 文件来控制 AI 的行为:
USER.md:你是谁?有什么习惯?
SOUL.md:AI 的性格和说话语气。
IDENTITY.md:AI 的公开身份。
AGENT.md:运行时的操作守则。
三、模块 2:工作区初始化
这个函数负责检查 /tmp/toyclaw 目录是否存在,如果不存在就创建,并把上面的默认文件全部写入。
它还会创建 skills/ 子目录,用来存放安装的技能包。
def ensure_workspace(): """创建工作目录并写入默认上下文文件""" WORKSPACE.mkdir(parents=True, exist_ok=True) SKILLS_DIR.mkdir(exist_ok=True) for filename, content in DEFAULT_FILE_CONTENTS.items(): file_path = WORKSPACE / filename if not file_path.exists(): file_path.write_text(content, encoding="utf-8")
运行 python toyclaw.py 后,你的 /tmp/toyclaw 下就会自动生成这些文件。
四、模块 3:技能安装——让 AI 学会新把戏
技能包本质上就是一个 Markdown 文件,里面包含了如何使用某个特定 API 或工具的说明。install_skill 函数支持两种来源:
远程 URL:用 urllib 下载。
本地路径:直接读取。
下载后,它会把文件保存到 skills/ 目录下。
def install_skill(source: str) -> Path: parsed = urlparse(source) if parsed.scheme in {"http", "https"}: # 下载远程技能文件 request = Request(source, headers={"User-Agent": "ToyClaw/0.1"}) with urlopen(request, timeout=20) as response: content = response.read().decode("utf-8") stem = Path(parsed.path or "skill.md").name else: # 读取本地文件 local_path = Path(source).expanduser() content = local_path.read_text(encoding="utf-8") stem = local_path.name # 保存到 skills/ 目录 target = SKILLS_DIR / stem target.write_text(content, encoding="utf-8") return target
安装完成后,后续 build_system_prompt() 会遍历 skills/ 目录,把所有 .md 文件的内容全部塞进系统提示词里,这样模型就知道怎么调用这个技能了。
五、模块 4:系统提示词构建——注入灵魂和技能
这是整个程序的“大脑说明书”。build_system_prompt() 会做三件事:
读取 USER.md、SOUL.md、IDENTITY.md、AGENT.md,拼接成上下文块。
遍历 skills/ 目录,把每个技能的内容也拼进去。
用一段严格的 JSON 格式指令,告诉模型:你每次只能输出一个 JSON 对象,要么 answer,要么 shell。
def build_system_prompt() -> str: # 1. 读取上下文文件 context_blocks = [] for name in ["USER.md", "SOUL.md", "IDENTITY.md", "AGENT.md"]: path = WORKSPACE / name if path.exists(): content = path.read_text(encoding="utf-8") context_blocks.append(f"## {name}\n{content}") # 2. 读取已安装技能 skills_text = "" for skill_file in SKILLS_DIR.glob("*.md"): content = skill_file.read_text(encoding="utf-8") skills_text += f"\n## Skill: {skill_file.name}\n{content}\n" # 3. 构建最终 prompt return textwrap.dedent(f"""\ You are ToyClaw, a tiny OpenClaw-like assistant. Workspace root: {WORKSPACE} Respond with exactly one JSON object: - If answering directly: {{"type": "answer", "content": "..."}} - If needing a shell command: {{"type": "shell", "command": "..."}} Context files: {''.join(context_blocks) if context_blocks else'(none)'} Installed skills: {skills_text if skills_text else'(none)'} """)
正是这段 prompt,把模型约束成了一个严格遵守 JSON 协议的“工具调用者”。
六、模块 5:Shell 命令执行与安全拦截
AI 可以执行 Shell 命令,但必须有限制。run_shell 函数在执行前会先过一道安全检查:如果命令中包含 sudo、rm -rf 等危险模式,直接拒绝。
def is_dangerous_shell(command: str) -> str | None: """返回拦截原因,None 表示安全""" dangerous_patterns = [ (r"(^|[;&|])\s*sudo\b", "sudo is blocked"), (r"\brm\s+-rf\b", "rm -rf is blocked"), (r"\bcurl.*\|\s*bash\b", "curl pipe bash is blocked"), ] import re for pattern, reason in dangerous_patterns: if re.search(pattern, command, re.IGNORECASE): return reason return Nonedef run_shell(command: str) -> str: blocked_reason = is_dangerous_shell(command) if blocked_reason: return f"COMMAND BLOCKED\nReason: {blocked_reason}\nCommand: {command}" # 使用 subprocess 执行,设置超时和工作目录 result = subprocess.run( ["bash", "-lc", command], cwd=WORKSPACE, capture_output=True, text=True, timeout=30, ) return textwrap.dedent(f"""\ Command: {command} Exit code: {result.returncode} Stdout: {result.stdout or'(empty)'} Stderr: {result.stderr or'(empty)'} """)
这里用了 bash -lc 是为了让命令能加载用户环境变量(比如 TRIPGENIE_API_KEY),方便技能调用。
七、模块 6:Agent 主循环——ReAct 推理
这是整个程序最核心的逻辑。run_agent_turn 实现了经典的 ReAct(Reasoning + Acting)循环:
把用户输入发给模型。
解析模型返回的 JSON。
如果是 answer,直接返回给用户。
如果是 shell,执行命令,把输出作为新的“用户消息”追加到对话历史,然后继续循环。
MAX_TOOL_STEPS = 5def run_agent_turn(client, history, user_input): system_prompt = build_system_prompt() messages = [{"role": "system", "content": system_prompt}] + history messages.append({"role": "user", "content": user_input}) for _ in range(MAX_TOOL_STEPS): response = client.complete(messages) # 调用 LLM API action = json.loads(response) # 解析 JSON if action["type"] == "answer": history.append({"role": "user", "content": user_input}) history.append({"role": "assistant", "content": action["content"]}) return action["content"] elif action["type"] == "shell": command = action["command"] result = run_shell(command) print(f"[shell] {command}\n{result}") # 回显命令结果 # 把执行结果作为 user 消息追加 messages.append({"role": "assistant", "content": json.dumps(action)}) messages.append({"role": "user", "content": f"Shell result:\n{result}"}) continue return "Reached maximum tool steps."
注意,这里的 client.complete 需要你自己实现,对接 OpenAI 兼容的 API。
八、模块 7:REPL 交互入口
最后是主循环,一个简单的 while True 读取用户输入,调用上面的 Agent 逻辑。
def repl(client): history = [] print("ToyClaw ready. Type 'exit' to quit.") while True: user_input = input("\nyou> ").strip() if user_input.lower() == "exit": break reply = run_agent_turn(client, history, user_input) print(f"\nclaw> {reply}")def main(): ensure_workspace() client = ChatClient(api_key=os.environ["OPENAI_API_KEY"]) # 伪代码,需自己实现 repl(client)if __name__ == "__main__": main()
九、效果展示
配置好 API Key,运行 python toyclaw.py,你就可以和这个极简 Agent 对话了。
用户对话:
you> who am iclaw> I don't have specific details about you yet. If you'd like, you can tell me your name, preferences, or anything you'd like me to remember to assist you better!
安装技能:
在对话里输入:
install this skill https://raw.githubusercontent.com/.../SKILL.md
它会自动下载并保存到 skills/ 目录。
查特价机票:提供 API Key 后,AI 就能构造 curl 命令去调用第三方 API,把结果整理成自然语言回复你。
这 200 行代码,去掉了所有冗余的错误处理和花哨的 TUI 界面,只保留最核心的骨架,清晰地展示了:
上下文注入 → 人设和规则
工具定义 → Shell 命令执行
推理循环 → ReAct 范式
动态扩展 → Skill 文件加载
感谢您读到这里。我是小伙子。
本公众号的内容仅用于交流与学习。
文章中如有技术或理论上的疏漏,欢迎在留言区指出,理性讨论、共同进步。
可以交个朋友,微信:yy_00_ff