已实现并通过测试的工作项。每次推进时把 ToDo.md 里完成的条目搬到这里。 关联文档:Plan.md(策略路线图) · ToDo.md(待办与已知问题)
目标:CLI → DeepSeek → 文本回复(无工具)。对应 Plan.md "Phase 0 — Hello Loop"。
-
pyproject.toml— 依赖(openai, pydantic, typer, rich, python-dotenv),entry pointmy-agent -
.env.example/.gitignore -
README.md— Setup / Run / Test 三段式 - 目录结构(含 Phase 1+ 的
tools/、ui/占位空目录)
-
ContentBlockdiscriminated union:TextBlock | ToolUseBlock | ToolResultBlock -
Message(role, content: list[ContentBlock])+text()helper -
ToolSpec数据模型(Phase 1 用得上,Phase 0 先就位) - JSON round-trip 测试通过(验证 discriminator 正确还原子类型)
-
LLMClientABC:chat(messages, system, tools) -> Message -
OpenAIStyleClient:base_url=https://api.deepseek.com/v1,model=deepseek-chat - 内部
Message↔ OpenAI wire format 翻译(Phase 0 仅 text,工具翻译留给 Phase 1)
- 单轮
Agent.run(user_input) -> str - 自动记录 user / assistant 消息到
self.messages
-
typer入口,支持my-agent "msg"与 stdin 两种输入 - 选项:
--model/--base-url/--api-key-env/--system - 缺 API key 时优雅退出(exit 1)+ 提示
cp .env.example .env - API 异常时退出 code 2
-
MockLLMClient测试夹具(无网络,可注入 canned responses) - 7 个 pytest 用例全过 —
pytest -v0.52s:test_single_turn_returns_assistant_texttest_messages_record_user_and_assistant_turntest_system_prompt_is_forwarded_to_clienttest_default_reply_when_mock_runs_outtest_text_helper_concatenates_text_blockstest_message_round_trips_through_jsontest_tool_result_block_defaults
- 源码 ~250 行(types 58 / agent 25 / cli 74 / openai_style 58 / base 28 / 入口若干)
- 测试 ~120 行
- 合计 369 行(与 Plan.md 估算一致)
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 7 passed |
my-agent --help |
✅ 选项与帮助正常 |
my-agent "hi"(无 key) |
✅ exit 1 + 友好提示 |
| 真实 DeepSeek 端到端调用 | ✅ 已验证 |
目标:把 LLM 客户端从 OpenAI 兼容路径切到 DeepSeek Anthropic 兼容端点,对齐内部 ContentBlock 协议。
-
pyproject.toml:anthropic>=0.40加为主依赖,openai移到[project.optional-dependencies].openai -
.env.example:字段切到ANTHROPIC_AUTH_TOKEN/ANTHROPIC_BASE_URL/ANTHROPIC_MODEL/ANTHROPIC_EFFORT -
.gitignore已正确排除.env
-
AnthropicStyleClient(LLMClient):封装anthropic.Anthropic(auth_token, base_url) -
chat():调client.messages.create(model, system, messages, tools, max_tokens) -
_to_anthropic():内部Message→ SDKMessageParam(TextBlock / ToolUseBlock / ToolResultBlock 三向翻译) -
_from_anthropic():SDK 响应.content→ 内部[TextBlock | ToolUseBlock],thinking blocks 丢弃 -
--effort参数 +EFFORT_BUDGETS映射(low/medium/high/max/off) - thinking 禁用时显式传
{"type": "disabled"}(DeepSeek v4-pro 默认 thinking ON) -
openai_style.py保留并标记# Deferred to Phase 3 fallback provider -
llm/__init__.py导出AnthropicStyleClient(不导出 OpenAIStyleClient)
-
--api-key-env默认ANTHROPIC_AUTH_TOKEN -
--base-url默认https://api.deepseek.com/anthropic -
--model默认deepseek-v4-pro[1m]
- 老 7 个用例全部保留且通过
- 新增
tests/test_anthropic_style.py(14 个用例):_to_anthropic / _from_anthropic / tool forwarding / thinking budget / effort disabled - 全部 21 个用例通过,无网络依赖
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 21 passed |
my-agent "用一句话介绍你自己" |
✅ DeepSeek 正确响应 |
目标:agent 能用 4 个核心工具自主完成"读这个目录、找包含 X 的文件、写一个总结到 out.md"这种任务。
-
Tool抽象基类:name/description/input_model(Pydantic BaseModel) /run(input) -> str -
input_schemaproperty:自动调model_json_schema()生成 JSON Schema -
ToolRegistry:register()/get_specs() -> list[ToolSpec]/execute(name, tool_use_id, input) -> ToolResultBlock - 三层错误保护:未知工具 → Invalid input → Runtime error,全部返回
is_error=True
-
tools/read.py— 路径 → 文件内容(行号前缀cat -n风格,2000 行限制,offset/limit 分页) -
tools/write.py—path+content(父目录自动 mkdir,UTF-8) -
tools/bash.py—command+timeout(subprocess.run,捕获 stdout/stderr/exit code,默认 120s) -
tools/glob.py—pattern+path(pathlib.Path().glob(),** 递归,最多 500 结果)
-
Agent.run()改为 while 循环:直到 assistant 不再产出 tool_use -
max_iterations=20防死循环(达上限抛IterationLimitError) - 多个 tool_use 的结果统一打包成一条 user message(Anthropic 协议要求)
-
tool_use_id正确串接:assistant.id → tool_result.tool_use_id - 工具执行失败 →
ToolResultBlock(is_error=True),喂回 LLM 自行恢复
- 基础 system prompt:身份、可用工具说明、CWD、当前日期
- 支持用户
--system覆盖
-
chat()把tools=[ToolSpec...]转为 Anthropic SDKtools=[{name, description, input_schema}](Phase 0.5 已就位) -
_from_anthropic()解析tool_useblocks(Phase 0.5 已就位) - DeepSeek 工具字段稳定性验证:
name字段正常,未触发 fallback
- 工具单测:
tests/test_tools.py(19 个用例)— Read/Write/Bash/Glob 全覆盖 - 注册表单测:
tests/test_tool_registry.py(6 个用例) - Agent loop 集成测:
tests/test_agent_tool_loop.py(8 个用例)— 单轮 / 工具回路 / 多工具同轮 / 迭代上限 / 错误回流 -
MockLLMClient扩展:mock_tool_use()/mock_tool_use_with_text()工厂函数 - 老测试全部保留且通过
- 新增源码 ~420 行(tools/base 80 / read 55 / write 30 / bash 45 / glob 45 / agent 50 / prompt 40 / 注册 75)
- 新增测试 ~320 行(test_tools 110 / test_tool_registry 65 / test_agent_tool_loop 115 / mock_llm 扩展 30)
- 累计源码 ~670 行,测试 ~440 行,合计 ~1110 行
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 53 passed(21 old + 32 new) |
真实端到端:"找出 .py 文件统计行数写入 stats.md" |
✅ 成功执行 glob → read → write 完整回路 |
stats.md 内容验证 |
✅ 格式化 table,数据正确 |
目标:agent 跑得"安全"——权限控制、流式输出、配置化、CLAUDE.md 加载、REPL 模式。
-
PermissionMode枚举:read_only/ask/allow_all/danger -
PermissionPolicy:check(tool_name, input) -> Decision(allow/deny/ask) - 默认规则:
read_only拒绝 write/bash/edit,ask对写工具弹确认 - 内置 deny 模式:
rm -rf /、git push --force、shutdown、mkfs.、fork bomb 等 10+ 条 -
from_string()工厂方法
-
load_settings()优先级链:CWD.my-agent/settings.json→~/.my-agent/settings.json→ 默认值 - 环境变量覆盖:
ANTHROPIC_MODEL、ANTHROPIC_BASE_URL、ANTHROPIC_EFFORT、MY_AGENT_PERMISSION_MODE - 损坏 JSON 优雅降级
-
tools/edit.py— 精确字符串替换,强制执行 read-before-edit 不变量(通过 Registry 共享 read_files) -
tools/grep.py— ripgrep 优先(子进程),Pythonrefallback(递归搜索 + 文件过滤) - Read 工具扩展:自动追踪已读文件到 Registry.read_files
-
chat_stream()方法:SDKmessages.stream(),SSE 事件解析 -
StreamEvent事件类型:text_delta/tool_use_start/tool_use_delta/done -
_build_kwargs()重构,chat() 和 chat_stream() 共享参数构建
-
rich.live.Live+ Spinner 流式文本输出 - 工具调用实时显示(黄色 ⚙ name + dim 输入参数)
- 完成提示(✓ tool_name: summary)
-
Agent构造接受PermissionPolicy+ask_callback - 每个 tool_use 执行前通过 policy.check() 判定
- DENY → is_error ToolResultBlock 喂回 LLM
- ASK → 调 ask_callback,用户拒绝 → is_error ToolResultBlock
- 权限变更不影响消息历史(slash command /permission 动态切换模式)
- 自动加载 CWD 的
CLAUDE.md/AGENTS.md/.my-agent/instructions.md - 限制 4000 字符,超出截断
-
--permission-mode/-p选项(支持 env varMY_AGENT_PERMISSION_MODE) -
--repl/-r交互模式 - REPL slash 命令:
/help、/clear、/permission、/history -
Confirm.ask()权限确认提示(显示工具名 + 输入摘要) - 注册 6 个工具(Read / Write / Edit / Bash / Glob / Grep)
-
test_permission.py(14 个用例)— 四种模式 / deny 模式 / from_string -
test_edit.py(7 个用例)— 替换 / replace_all / read-before-edit / 错误处理 -
test_grep.py(6 个用例)— 搜索 / 过滤 / 递归 / 非法正则 -
test_config.py(6 个用例)— 默认值 / 文件加载 / env 覆盖 / 损坏 JSON -
test_agent_permission.py(5 个用例)— deny / ask+callback / allow_all
- 新增源码 ~630 行(permission 85 / config 85 / edit 75 / grep 85 / render 80 / anthropic_style stream 90 / agent 50 / cli 80)
- 新增测试 ~340 行(test_permission 90 / test_edit 60 / test_grep 55 / test_config 50 / test_agent_permission 85)
- 累计源码 ~1300 行,测试 ~780 行,合计 ~2080 行
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 90 passed(53 old + 37 new) |
| 流式输出 | ✅ rich Live + Spinner 实时展示 |
| read_only 模式端到端 | ✅ glob/grep 允许,write 正确拒绝,agent 智能恢复 |
| allow_all 模式端到端 | ✅ 完整 glob → grep → write 回路 |
summary2.md 内容 |
✅ 格式化正确 |
目标:会话能保存、能续接、能列出、能删除。
-
Session类:messages/id/created_at/updated_at/model/permission_mode/working_dir -
save()→ JSON 到~/.my-agent/sessions/<id>.json -
load(id)→ Session | None -
list_recent(limit=20)→ 最新的 sessions 列表 -
resume_latest()→ 最新 session 或 None -
delete()→ 删除持久化文件 -
summary()→ 单行摘要(ID / 消息数 / 时间 / 预览)
-
--resume <id>/--resume latest恢复会话 -
my-agent sessions list子命令(rich Table 展示) -
my-agent sessions delete <id>子命令 - 每次
chat结束后自动保存 session - REPL 内
/save、/session命令 - 恢复时消息列表注入
agent.messages,无缝续接
-
tests/test_session.py(8 个用例)— save/load round-trip / list_recent / resume_latest / delete / empty / invalid JSON
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 98 passed(90 old + 8 new) |
my-agent sessions list |
✅ 空列表正常 |
my-agent chat "记住我的名字" |
✅ 自动保存 session |
my-agent sessions list |
✅ 显示已保存 session |
my-agent chat --resume latest "我叫什么?" |
✅ 正确恢复记忆,跨轮问答 |
目标:长对话自动压缩,tool_use/tool_result 配对不拆。
-
estimate_tokens()— 按字符数 / 4 粗估 token(English/中文混合) -
should_compact(messages, threshold)— 超阈值时触发(默认 40K tokens) -
compact_messages(messages)— 压缩策略:保留最近 6 条,旧消息生成摘要 -
_align_to_pair_boundary()— 核心不变量:tool_use / tool_result 对永不拆分 -
build_compaction_summary()— 生成结构化摘要(角色 / 工具名 / 参数 / 结果) - Agent loop 集成:每次 LLM 调用前检查 + 自动压缩
-
tests/test_compact.py(6 个用例)— token 估算 / 阈值判断 / 短对话不压缩 / pair 保留
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 106 passed(98 old + 8 new) |
目标:agent 可以 spawn 子 agent 做独立任务。
-
Task工具:description/prompt/subagent_type参数 -
general-purpose类型:共享父 agent 的 ToolRegistry + PermissionPolicy -
Explore类型:只读(read/glob/grep)+ read_only 权限 - 独立 messages 上下文,返回最终 text 给父 agent
-
MAX_SUB_TURNS=10防子 agent 死循环 - IterationLimitError 优雅降级
- ToolRegistry 扩展:
set_context(client, policy)+_registry注入
-
tests/test_task.py(4 个用例)— 子 agent 执行 / Explore 模式 / 未初始化 / 迭代上限
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 110 passed(106 old + 4 new) |
目标:settings 驱动的 shell 命令,在 tool 执行前后自动触发。
-
HookEvent枚举:pre_tool_use/post_tool_use/stop -
HookConfig:从 settings.jsonhooks字段加载命令列表 -
run_hooks(event, commands, context)→HookResult(allowed, stdout, stderr) - 环境变量注入:
MY_AGENT_EVENT/MY_AGENT_TOOL_NAME/MY_AGENT_TOOL_INPUT -
pre_tool_use非零退出 → 拒绝工具执行 -
post_tool_use/stop非零退出 → 无视(仅告警) - 超时保护(单钩子 10s)
- 每个 tool_use 执行前:pre_tool_use hooks
- 每个 tool 执行后:post_tool_use hooks
- Hook 拒绝 → is_error ToolResultBlock 喂回 LLM
-
tests/test_hooks.py(7 个用例)— 配置加载 / 环境变量传递 / 非零退出 / 超时
目标:多 provider 可切换(Anthropic / OpenAI),build_client() 工厂。
- 完整
tool_calls↔ToolUseBlock双向翻译 -
_to_openai():ToolUseBlock → assistant tool_calls / ToolResultBlock → tool role 消息 -
_from_openai():响应 tool_calls → ToolUseBlock(JSON arguments 解析 + fallback) -
tool_choice="auto"默认
-
provider="anthropic"→ AnthropicStyleClient(DeepSeek 代理 / 官方 Claude) -
provider="openai"→ OpenAIStyleClient(DeepSeek 原生 / 百炼 / vLLM) -
base_url/model参数透传 -
auth_token↔api_key兼容
-
--provider anthropic|openai参数 -
build_client()替换硬编码 AnthropicStyleClient - REPL 兼容 LLMClient 基类
-
tests/test_openai_style.py(5 个用例)— to_openai / from_openai 双向翻译
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 123 passed(117 old + 6 new) |
目标:CLI 修复、Doctor 健康检查、插件系统、Slash 命令完善、可观测性。
-
my-agent "msg"恢复工作(不再需要显式chat子命令) -
main()中自动检测:第一个位置参数非已知命令 → 插入 "chat" -
doctor子命令:API key 检查 / 配置显示 / 工具列表 / 会话数 / 连通性 ping - 重构为
_run_chat()辅助函数,callback/subcommand 共用
-
discover_plugins()扫描~/.my-agent/plugins/*.py - 支持
get_tools()导出函数 + Tool 子类自动检测 -
_前缀文件跳过 - 损坏插件静默跳过,不阻塞 agent 启动
- 已集成到
_make_registry()
-
/compact— 手动压缩上下文(显示 token 变化) -
/history— 消息数 + token 估算 -
/doctor— 会话健康概览 - REPL 内共 8 个 slash 命令
-
Usage数据类:input_tokens / output_tokens / duration_ms -
chat()解析resp.usage存入last_usage -
_retry_call()指数退避重试(502/503/504/429/timeout/connection) -
verbose=True时 stderr 打印 token 消耗 + 耗时 -
max_retries=2默认配置
-
tests/test_plugins.py(4 个用例)— 空目录 / get_tools / 跳过下划线 / 损坏静默
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 127 passed(123 old + 4 new) |
my-agent "一句话介绍自己" |
✅ 自动路由到 chat |
my-agent doctor |
✅ API key ✓ / 7 tools / 连通性 1.1s |
my-agent sessions list |
✅ 子命令正常 |
-
use_cache=True默认,system prompt + tools 加cache_control: {type:"ephemeral"} -
cache_hits/cache_misses追踪
-
McpServer— JSON-RPC over stdio(initialize / tools/list / tools/call) -
McpToolWrapper— MCP 工具包装为 Agent Tool - Settings.json
mcpServers配置 + 自动启动 - 测试:
tests/test_mcp.py(8 个用例)
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 135 passed |
my-agent "一句话" |
✅ |
my-agent doctor |
✅ |
| Prompt caching | ✅ cache_control markers present |
| MCP config parsing | ✅ |
目标:集中修复 ToDo.md 中 B-编号系列 17 个 bug + D-10 / D-16 / D-18。
| # | 文件 | 修复 |
|---|---|---|
| B-1 | tools/bash.py |
cwd="/" → cwd=os.getcwd() |
| B-2 | tools/edit.py |
replace_all 用 original.count() 统计实际替换次数 |
| B-3 | ui/render.py |
Text(...) → Text.from_markup(...)(3 处) |
| B-4 | tools/task.py / tools/base.py / cli.py |
_ask_callback 注入 + set_context / register 传播到子 agent |
| # | 文件 | 修复 |
|---|---|---|
| B-5 | llm/anthropic_style.py |
chat_stream() 通过 stream.get_final_message() 更新 last_usage / cache_hits / cache_misses |
| B-6 | llm/anthropic_style.py |
chat_stream() 用 _retry_call 包装 _do_stream 闭包,添加指数退避重试 |
| B-7 | llm/anthropic_style.py |
_retry_call 直接从 resp.usage 打印当前调用统计,不再用滞后一轮的 last_usage |
| B-8 | llm/openai_style.py |
tool 消息连续排列,user TextBlock 置后,满足 OpenAI "tool 消息必须连续"约束 |
| B-9 | llm/openai_style.py |
assistant content: None → content: "",兼容拒绝 null 的端点 |
| B-10 | tools/read.py |
.split("\n") → .splitlines(),消除尾部空元素 |
| B-11 | agent.py |
agent 正常结束 / 迭代上限时调用 HookEvent.STOP |
| # | 文件 | 修复 |
|---|---|---|
| B-12 | agent.py / cli.py / tools/task.py |
Agent(max_iterations=settings.max_iterations),替代硬编码 MAX_ITERATIONS |
| B-13 | permission.py |
移除未使用的 READ_ONLY_TOOLS 死代码 |
| B-14 | agent.py / cli.py |
新增 on_tool_result 回调参数,接入 Renderer.add_tool_result() |
| B-15 | tools/base.py / cli.py |
新增 ToolRegistry.register_instance() 方法,MCP 工具不走旁路 |
| B-16 | tools/bash.py |
8000 字节 / 200 行硬截断,防止 stdout 爆 LLM 上下文 |
| B-17 | llm/anthropic_style.py |
max_tokens clamp 到 32000,避免超 DeepSeek 模型上限 |
| # | 文件 | 修复 |
|---|---|---|
| D-10 | pyproject.toml |
添加 [tool.hatch.build.targets.wheel.sources] "src" = "" 修复 Python 3.14 .pth 隐藏文件问题 |
| D-16 | mcp.py |
_build_input_model 使用 pydantic.create_model + ConfigDict(extra="allow") fallback |
| D-18 | compact.py |
移除未使用的 from .session import Session 导入 |
tests/test_anthropic_style.py:251—test_effort_max_forwards_thinking_budget断言更新:> 32000→== 32000(匹配 B-17 clamp)
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 135 passed(全部通过,无退化) |
目标:集中修复 ToDo.md 中 C-编号系列 12 个深度测试发现的 bug。
| # | 文件 | 修复 |
|---|---|---|
| C-1 | 安装产物 .pth |
chflags nohidden 清除 macOS hidden flag + pyproject.toml sources 配置 |
| C-2 | cli.py |
ask_callback() 定义移动到 registry.set_context() 之前,消除 UnboundLocalError |
| C-3 | tools/grep.py |
_rg_search 先 re.compile 校验正则;returncode not in (0,1) 时 fallback 到 Python |
| C-4 | permission.py |
read_only 改为显式 allowlist(read/glob/grep/task);ask 模式未知工具默认 ASK |
| C-5 | permission.py |
shlex tokenize 命令 + 识别 rm -rf/rm -fr/-- 变体 + ///* 目标检测 |
| C-6 | mcp.py |
Content-Length framed JSON-RPC 读写(LSP-style),兼容 newline-delimited fallback |
| C-7 | mcp.py |
threading.Thread + join(timeout) 带 deadline 读 stdout;超时 kill 子进程 |
| # | 文件 | 修复 |
|---|---|---|
| C-8 | mcp.py |
_build_input_model 读取 JSON Schema required 数组;非必填字段 default=None |
| C-9 | mcp.py |
call_tool 返回 (content, is_error) tuple;McpToolWrapper.run() 遇 isError 抛 RuntimeError |
| C-10 | tools/grep.py |
截断前保存 total = len(lines),提示 total - MAX_RESULTS |
| C-11 | session.py |
Session.load 新增 _resolve_id 前缀匹配;8 位短 ID 可唯一匹配到 12 位完整 ID |
| C-12 | 全局 | 49 个 ruff 告警清零:load_dotenv 移到 import 之后、移除 unused import、Any 正确导入 |
tests/test_agent_tool_loop.py— GetTool name"get"→"read"(适配 allowlist 权限模型)test_tool_error_is_reported_to_llm— 添加ask_callback=lambda: True通过未知工具权限检查
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 135 passed |
ruff check src/ |
✅ All checks passed |
.pth hidden flag |
✅ 已清除 |
目标:修复 C 系列首轮修复后的残余问题 + 复测新增的 6 个 bug。
| # | 文件 | 修复 |
|---|---|---|
| C-1 | .pth / scripts/fix_pth.sh |
xattr -d com.apple.provenance + chflags nohidden + 工具脚本 |
| C-13 | permission.py |
_has_destructive_command 按 `&&/;/ |
| C-14 | mcp.py |
_send_request 校验 resp["jsonrpc"] == "2.0" 且 resp["id"] == req_id |
| C-15 | mcp.py |
Popen(text=False) binary pipes;_read_frame 用 stdout.read(content_length) 字节精确读取 + .decode("utf-8") |
| C-16 | mcp.py |
_read_frame 循环读 header 直到空行,忽略 Content-Type 等未知 header |
| # | 文件 | 修复 |
|---|---|---|
| C-12 | tests/ |
ruff 26 个告警全部修复:F401 unused import / E721 == type → is type / F841 unused variable |
| C-17 | cli.py |
doctor build_client(base_url=settings.base_url or None) |
| C-18 | mcp.py |
_build_input_model non-required 字段类型 T | None,显式 null 通过 Pydantic 校验 |
| 检查 | 结果 |
|---|---|
pytest -v |
✅ 135 passed |
ruff check . |
✅ All checks passed |
my-agent --help |
✅ 可用(需 scripts/fix_pth.sh 修复 .pth) |
| Phase | 内容 | 测试数 |
|---|---|---|
| 0 | Hello Loop | 7 |
| 0.5 | Anthropic SDK 切换 | 21 |
| 1 | Tool Loop | 53 |
| 2 | 权限/流式/REPL | 90 |
| 3 | 会话/压缩/子Agent/Hook/多Provider | 123 |
| 4 | Doctor/插件/Cache/MCP | 135 |
累计:18 个模块,135 个测试。Plan.md 全部完成。