# 🐾 ToyClaw
千行代码实现的 OpenClaw。
ToyClaw 是一个极简但功能完整的 AI Agent 框架,参考 nanobot 架构设计,用一千多行 Python 实现了 Agent Loop、Tool Use、Cron 定时任务、Heartbeat 心跳、Sub-agent 后台任务、Web Search 等核心能力。
OpenClaw 已经达到了惊人的40w+行的代码量,抽象层套抽象层,耦合程度堪比仙人排队跳———套里还有套。它看起来很复杂,各种功能:聊天软件支持、多 Provider、Skills、MCP、长Memory、Cron定时任务、Sub-agent……但核心能力其实很简单。Agent Loop 就是一个 while 循环:LLM 思考,调用工具,拿到结果,再思考,直到给出最终回复。其他一切都是生产级需求的包装。
我决定砍掉一切非核心复杂度,仅用 1000 行 Python 代码实现一个功能完整的OpenClaw。我把它命名为toyclaw。在openclaw自身的辅助下,总共花费时间不超过2小时,代码量千行出头,但是它能做的事情几乎和 nanobot 一样——支持工具调用、定时任务、skills扩展、Session 持久化等核心功能。
| 保留 | 砍掉 | 原因 |
|---|---|---|
| Agent Loop(LLM ↔ Tool) | MessageBus | CLI 直连 Agent,不需要消息总线 |
| Tool Registry + 9 个内置工具 | MCP 协议 | 内置工具直接注册,不需要外部协议 |
| Cron 定时任务 | 多 Channel(Telegram/Discord) | 只做 CLI 入口 |
| Heartbeat 心跳 | Memory Consolidation | Agent 自己用 write_file 管理记忆 |
| Sub-agent 后台任务 | 多 Provider / LiteLLM | 一个 OpenAI 兼容端点够用 |
| Skills 动态发现 | 多模态(图片/语音) | 纯文本交互 |
| Session 持久化 | OAuth / 权限系统 | 个人工具,无需鉴权 |
┌─────────────────────────────────────────────────────┐
│ CLI REPL │
│ (cli.py — 交互/单次模式) │
└─────────────┬───────────────────────────────────────┘
│ user input
▼
┌─────────────────────────────────────────────────────┐
│ Agent Loop │
│ (agent.py — 核心处理引擎) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Context │ │ Provider │ │ Tool Registry │ │
│ │ Builder │ │ (OpenAI) │ │ (9 tools) │ │
│ └──────────┘ └──────────┘ └───────────────┘ │
└──────┬──────────────────────────────────┬───────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────┐ ┌─────────────────┐
│ Session │ │ Skills │ │ Background Svc │
│ (JSONL) │ │ Loader │ │ │
└──────────────┘ └──────────┘ │ ┌───────────┐ │
│ │ Cron │ │
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Heartbeat │ │
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Sub-agent │ │
│ └───────────┘ │
└─────────────────┘
User Input
→ ContextBuilder.build_messages() (system prompt + history + skills + runtime)
→ Provider.chat() (发送到 LLM)
→ LLM 返回 tool_calls?
├─ YES → ToolRegistry.execute() → 结果追加到 messages → 回到 Provider.chat()
└─ NO → 最终回复 → Session 持久化 → 返回给用户
toyclaw/
├── pyproject.toml # 构建配置 + 依赖声明
├── README.md
├── toyclaw/
│ ├── __init__.py (3) # 版本号
│ ├── __main__.py (5) # python -m toyclaw 入口
│ ├── config.py (43) # JSON 配置加载
│ ├── provider.py (98) # OpenAI SDK 封装
│ ├── context.py (129) # System prompt + 消息组装
│ ├── session.py (94) # JSONL 会话持久化
│ ├── skills.py (57) # Skills 目录发现
│ ├── agent.py (184) # 核心 Agent Loop
│ ├── subagent.py (125) # 后台子 Agent
│ ├── cron.py (241) # 定时任务引擎
│ ├── heartbeat.py (115) # 心跳唤醒服务
│ ├── cli.py (174) # CLI REPL + 组件装配
│ └── tools/
│ ├── __init__.py (1)
│ ├── base.py (77) # Tool 抽象基类 + Registry
│ └── builtin.py (452) # 9 个内置工具
└── tests/
├── test_functional.py # 12 项单元测试
└── test_e2e.py # 4 项端到端测试
git clone https://github.com/yourname/toyclaw.git
cd toyclaw
pip install -e .
创建配置文件 ~/.toyclaw/config.json:
{
"api_key": "sk-your-api-key",
"api_base": "https://api.openai.com/v1",
"model": "gpt-4o-mini"
}
# 交互模式
toyclaw
# 或者
python -m toyclaw
# 单次执行
toyclaw -m "帮我写一个 hello world"
# 指定配置文件
toyclaw -c /path/to/config.json
| 命令 | 说明 |
|---|---|
/new |
清空当前会话,开始新对话 |
exit / quit / /exit / :q |
退出 |
~/.toyclaw/config.json 支持以下字段:
{
"api_key": "", // 必填 — LLM API Key
"api_base": "https://api.openai.com/v1", // API 端点
"model": "gpt-4o", // 模型名称
"workspace": "~/.toyclaw/workspace", // 工作区路径
"brave_api_key": null, // Brave Search API Key(可选)
"max_iterations": 30, // Agent 单轮最大迭代次数
"heartbeat_interval": 1800, // 心跳间隔(秒)
"heartbeat_enabled": true // 是否启用心跳
}
启动后自动创建:
~/.toyclaw/workspace/
├── memory/ # 长期记忆(MEMORY.md 等)
├── sessions/ # 会话历史(JSONL 格式)
├── skills/ # 技能扩展目录
├── cron/ # 定时任务持久化
├── AGENTS.md # Agent 指令文件(可选)
├── SOUL.md # 人格设定(可选)
├── HEARTBEAT.md # 心跳任务清单(可选)
└── ...
ToyClaw 提供 9 个内置工具,LLM 可自主决定何时调用:
| 工具 | 名称 | 功能 |
|---|---|---|
| 📖 ReadFile | read_file |
读取文件内容(128KB 上限,支持路径解析) |
| ✏️ WriteFile | write_file |
写入文件(自动创建父目录) |
| 🔧 EditFile | edit_file |
精确搜索替换(失败时给出 diff 提示) |
| 📁 ListDir | list_dir |
列出目录内容 |
| 💻 Shell | exec |
执行 Shell 命令(带安全拦截 + 60s 超时) |
| 🔍 WebSearch | web_search |
Brave Search API 网页搜索 |
| 🌐 WebFetch | web_fetch |
抓取网页并提取正文(readability 算法) |
| ⏰ Cron | cron |
创建 / 列出 / 删除定时任务 |
| 🚀 Spawn | spawn |
派生后台子 Agent 执行耗时任务 |
内置危险命令拦截,以下模式会被自动阻止:
rm -rf, del /f, rmdir /s, format, mkfs, diskpart,
dd if=, shutdown, reboot, fork bomb
在 workspace/skills/ 下创建子目录,放入 SKILL.md 即可被自动发现:
workspace/skills/
└── my_skill/
├── SKILL.md # 技能说明(必须存在)
├── script.py # 任意辅助文件
└── ...
Agent 的 system prompt 会自动包含所有已发现技能的摘要。LLM 可以通过 read_file 工具读取 SKILL.md 获取详细指令。
这是整个系统的心脏。核心逻辑只有一个循环:
for _ in range(max_iterations):
response = await provider.chat(messages, tools)
if response.has_tool_calls:
for tc in response.tool_calls:
result = await tools.execute(tc.name, tc.arguments)
messages.append(tool_result)
else:
return response.content # 最终回复
关键设计:
<think> 标签清理 — 兼容 DeepSeek 等带思考过程的模型薄封装层,将 OpenAI SDK 的响应标准化为 LLMResponse 数据类:
@dataclass
class LLMResponse:
content: str | None
tool_calls: list[ToolCallRequest]
finish_reason: str
@dataclass
class ToolCallRequest:
id: str
name: str
arguments: dict[str, Any]
兼容任何 OpenAI 格式的 API(OpenAI / DeepSeek / OpenRouter / vLLM / Ollama)。
支持三种调度模式:
| 模式 | 示例 | 说明 |
|---|---|---|
at |
2026-03-10T10:30:00 |
一次性定时,执行后自动删除 |
every |
every_seconds=300 |
固定间隔循环 |
cron |
0 9 * * * |
标准 cron 表达式(依赖 croniter) |
任务持久化为 JSON,重启后自动恢复。触发时通过回调调用 Agent.process(),形成完整闭环。
定期读取 workspace/HEARTBEAT.md,将内容交给 LLM 判断是否需要执行。LLM 通过专用 tool call 返回 skip(无事可做)或 run(有待办任务),避免无意义的 Agent 唤醒。
后台 asyncio.Task 运行独立的 Agent Loop,拥有受限工具集(不含 cron 和 spawn,防止递归)。完成后通过 on_complete 回调通知主进程。
JSONL 格式持久化:
.jsonl 文件python tests/test_functional.py
覆盖 12 项:Config、Provider 数据类、ToolRegistry、文件工具(读/写/编辑/列目录)、Shell(含安全拦截)、Session 持久化、Skills 发现、Context 组装、Cron 全生命周期、CronTool 接口、Heartbeat、HTML 清洗。
python tests/test_e2e.py
覆盖 4 项:LLM 直接对话 → Tool Call 生成 → 完整 Agent Loop → Agent 调用工具创建文件并验证。
MIT
Built as a learning exercise — understanding AI agents by building one from scratch.