一个运行在本地、通过与大语言模型交互来自主读写文件、执行命令、完成编程任务的智能体——类似一个极简的 Claude Code / Codex / OpenCode。
- 模型:DeepSeek(OpenAI 兼容接口,默认
deepseek-v4-flash) - 语言:Python 3.10+
- 依赖:零第三方依赖——连 HTTP 层、SSE 流式解析、token 估算、单元测试,全部只用标准库
# 1. 配置密钥(.env 已被 .gitignore 排除,不会入库)
cp .env.example .env # 编辑 .env 填入 DEEPSEEK_API_KEY
# 或:export DEEPSEEK_API_KEY=sk-...
# 2. 运行(单次执行)
python main.py "写一个快速排序的实现并跑通测试"
# 交互对话模式(持续多轮,像 ChatGPT 一样追问)
python main.py
# 3. 运行单元测试
python -m unittest discover -s tests -v常用参数:
| 参数 | 说明 |
|---|---|
--workdir DIR / -w |
工作目录(沙箱根目录,路径约束在其中) |
--max-iter N |
最大迭代步数(默认 25) |
--plan |
先让模型制定执行计划,人工确认后才执行(可多轮修订) |
--approve |
审查模式:写/改/删文件、执行命令前逐个让你确认(含 diff 预览) |
--ask |
仅对 shell 命令做人工确认 |
--save FILE |
结束后把会话保存为 JSON |
--resume FILE |
从已保存的会话继续(追问) |
不带任务参数启动即进入 REPL,会话在内存中持续累积,可像聊天一样多轮追问:
python main.py交互命令:
| 命令 | 作用 |
|---|---|
/new [名称] |
新建会话(缺省自动命名),并切换到它 |
/list |
列出所有会话(* 标记当前) |
/switch <名称> |
切换到指定会话 |
/del <名称> |
删除指定会话 |
/save [会话] [文件] |
保存全部(或单个)会话,缺省文件 sessions.json |
/load <会话> [文件] |
从文件加载单个会话 |
/clear |
清空当前会话 |
/plan |
切换计划模式(先出计划并人工确认后执行) |
/approve |
切换审查模式(改文件/执行命令前逐个确认) |
/review |
查看/回滚改动(/review git 看 git diff,/review git reset 回滚 checkpoint) |
/skills |
列出当前可用的技能(来自 .agents/skills/) |
/memory |
查看长期记忆(项目记忆 MEMORY.md + 用户画像 USER.md) |
/usage |
显示累计 token 消耗 |
/help |
显示帮助 |
exit / quit |
退出 |
每个会话拥有独立的对话历史,可互不干扰地并行推进多个任务;/save + /load 支持单会话/全量持久化与跨进程恢复。
核心是一个 「思考 → 行动 → 观察」 循环:
[system 提示 + 用户任务]
│
▼
调用 DeepSeek(带工具 schema)
│
├── 返回 tool_calls ──► 本地沙箱并发执行 ──► 结果回填为 tool 消息 ──┐
│ │
└── 无 tool_calls ──► 任务完成,返回最终回答 │
每轮请求无状态:完整消息历史在本地内存中重建后发送,不依赖服务端托管的任何状态、代码执行或文件工具。
围绕「可插拔、可观测、可恢复」三个目标,做了几处工程化设计:
llm.py 定义 ChatProvider 协议,Agent 只依赖该协议而非 DeepSeek 的具体实现。换成其它 OpenAI 兼容模型、或在测试里注入 mock,都不必改动 agent 核心。
模型上下文有限,而工具输出(读文件、命令结果)往往占大头且可重新获取。逼近预算时按优先级压缩(context.py):
- 截断最旧的工具输出正文(保留头部,可重新读取);
- 摘要较早的对话为一段「记忆」(调用模型压缩);
- 兜底丢弃最旧的工具调用轮次。
始终保留 system 提示与最初的用户任务。
- API 层:网络错误 / 5xx / 429 指数退避重试,4xx 直接抛出;
- 模型层:工具名未知、参数非法、JSON 解析失败 → 以字符串形式回喂给模型,让它自行修正,而不是让循环崩溃;
- 执行层:命令超时、非零退出码、路径越界,都作为结果返回给模型判断。
--approve(或 REPL 里 /approve)开启审查:任何写文件 / 改文件 / 删文件 / 执行命令都会先展示统一 diff 或完整命令,再问你是否放行:
y允许 → 真正执行;n拒绝 → 结果回喂给模型,让它调整方案;m拒绝并留言 → 你的意见作为 tool 结果传回模型,它据此改进;- 相同调用有缓存,拒绝过的调用不会重复弹窗打扰。
所有文件改动都会被记录(Workspace.changes),随时用 /review 查看每个操作的统一 diff,并用 /review revert <序号|all> 按需回滚。整个链路:计划审查(事前)→ 工具审批(事中)→ 改动审查与回滚(事后)。
进入 REPL 时若工作目录不是已有 git 仓库,会自动 git init(仓库内身份,不污染全局配置),每轮任务有改动就自动提交 checkpoint(commit message 用模型的总结)。/review git 查看最近一个 checkpoint 的 diff——能覆盖 execute_command 的副作用(构建产物、安装的依赖等文件工具追踪不到的部分);/review git reset 一键回滚到上一 checkpoint。若工作目录已是 git 仓库(如你自己的项目),自动进入「只读监视」模式,不做自动提交与破坏性回滚。
- 三级压缩:截断旧工具输出 → LLM 摘要早期历史 → 兜底丢弃最旧轮次,始终保住
system与原始任务; - 过载保护:单条消息超 60000 字符自动截断、消息总数超上限自动清理最旧轮次;
- 大结果落盘:超大工具输出完整落盘到
.my_agent_core/results/,上下文只放头部预览 + 路径占位(可重新读取); - token 估算动态校准:每轮用 API 返回的真实
usage反向校准字符估算(usage anchoring); - 摘要防注入:LLM 摘要用
<analysis>/<summary>双标签,提取时只取<summary>正文,防止历史中的恶意内容伪造摘要; - 用户输入与模型输出各有长度上限,从源头防止上下文失控。
agent 有 memory 工具 + 工作目录 .my_agent_core/memory/:可把项目约定、踩坑记录、用户偏好写入长期记忆,下次会话仍生效。两类记忆分文件存储:MEMORY.md(项目事实,上限 2200 字符)与 USER.md(用户偏好画像,上限 1375 字符),memory 工具用 target=memories|user 选择写入目标。关键设计是冻结快照——记忆在启动时注入 system prompt 后本会话全程静止,既不污染当前上下文也保护前缀缓存;写操作只落盘、不动快照,新记忆下个会话生效。
技能是 .agents/skills/<技能名>/SKILL.md 组成的声明式扩展:frontmatter 提供 name/description,正文是按步骤的指令。启动时 agent 只把轻量技能清单(名称+描述)注入 system prompt,零正文 token 开销;当任务与技能匹配时,agent 调用 invoke_skill(name) 按需加载完整指令再严格照做——用纯文本声明能力,无需改一行代码。可用 /skills 查看、/memory 查看记忆。
模型在同一次响应里给出多个工具调用时,仅当全部为只读工具才并发;批内含任何写操作则严格按模型输出顺序串行——避免 read 先于 write 读到旧数据(因果倒置)。审查/确认模式下自动退回顺序。
--plan 让模型先产出分步计划,然后暂停等你确认:y 按计划执行、m 给修改意见重拟、n 重新制定、c 取消。可多轮往返直到你满意,计划获批后才进入执行循环,避免模型带着未经确认的计划贸然动手。
推理文本流式打印;模型输出是 Markdown,终端不会渲染——项目内实现了一个纯标准库的 Markdown → ANSI 渲染器(markdown.py,按行缓冲避免流式切碎标记),支持标题、加粗、行内代码、代码块、列表;工具调用与结果可视化,结尾统计 token 消耗。
main.py 命令行入口(单次执行 + 交互 REPL + 多会话 + 审查模式)
agent.py 主循环 + 终止条件 + 并发执行 + 计划/摘要编排 + 审批回喂
llm.py ChatProvider 协议 + DeepSeek 客户端(标准库 HTTP + SSE 流式解析)
context.py 对话历史、token 估算、三级上下文压缩、过载保护
session.py 会话管理:多会话创建/切换/删除/持久化(含校准系数恢复)
gitwrap.py Git 集成:自动 init + 每轮任务 checkpoint + git diff/回滚
memory.py 长期记忆:MEMORY.md + USER.md + 冻结快照注入(跨会话)
skills.py 声明式技能:.agents/skills/ 发现、frontmatter 解析、清单注入
markdown.py 轻量 Markdown → ANSI 终端渲染器(纯标准库,流式按行缓冲)
parser.py 模型输出解析(工具参数 JSON 容错、content 兜底识别)
config.py 配置(环境变量 / .env)
tools/
__init__.py 工具注册表 + 工作目录沙箱(越界防护、diff 预览、审批)
errors.py RequestDenied 异常
files.py read_file / write_file / edit_file / list_files / delete_file
shell.py execute_command(subprocess + 超时 + 确认)
search.py search_files(文件内容 grep)
memory.py memory 工具(target 选择 MEMORY.md / USER.md)
skills.py invoke_skill 工具(按需加载技能正文)
tests/ 核心逻辑的单元测试(标准库 unittest)
| 题目要求 | 实现位置与做法 |
|---|---|
| 对话历史与上下文管理 | context.py:token 估算 + 三级压缩 |
| 工具定义与本地执行 | tools/:JSON Schema + 本地 executor,沙箱约束 |
| 模型输出解析 | llm.py 流式增量拼接 + parser.py JSON 容错 |
| 循环终止条件 | agent.py:无工具调用 → 完成;达最大步数 → 收尾;异常 → 退出 |
| 错误处理 | API 退避重试;工具错误回喂自愈;命令超时/截断/越界防护 |
- 所有文件路径经
Workspace.resolve归一化并做越界防护,约束在--workdir内; - 命令在沙箱目录下执行,带超时(默认 60s)与输出截断(默认 12000 字符);
- 密钥只从环境变量或
.env读取,绝不进入代码、仓库或日志。