1. 为什么长链路任务里模型总在“装忙”TodoWrite 要解决的真实痛点如果你跟着 learn-claude-code 的 s01、s02 一路写下来会发现一个很尴尬的现象单步任务读文件、改一行、跑个 bash它干得挺利索可一旦你丢给它“重构 hello.py加类型注解、补 docstring、再加 main guard”这种多步任务它就开始表演了。前三步做得像模像样第四步突然回头把第一步又做了一遍或者干脆跳过某一步直接宣布“已完成”。这不是模型笨而是长链路任务里上下文被工具结果不断填满系统提示的约束力被稀释模型对“我现在做到哪了”这件事失去了清晰感知。TodoWrite 这个模块要干的事说白了就是给 Agent 装一块白板。模型每做一步必须先在白板上写清楚哪些任务 pending、哪个 in_progress、哪些 completed。这块白板不依赖对话历史而是独立存在的一个 Python 对象每次工具调用后把渲染结果塞回给模型看。这样一来哪怕对话已经滚了二十轮模型抬头就能看到“哦任务 2 还在进行中任务 3 还没开始”不会跑偏。我实测下来加了 TodoWrite 之后一个 6 步的 Python 包创建任务完成率从原来的“做一半就开始即兴发挥”变成了基本能按顺序走完。关键不在于模型变聪明了而在于它有了一个外部的、结构化的状态锚点。这篇笔记就带你从零把这个模块写出来同时把 TaoToken 的 Key 和 API 通道配好让 ClaudeCode 能真正跑起来。适合谁看已经写过 s01/s02 的读者或者手头有一个能调通的 Agent loop、想加上任务规划能力的开发者。如果你还没配过 API 通道第三节的配置可以直接抄。2. TaoToken 前置统一 Key 与 API 通道怎么接进 learn-claude-codelearn-claude-code 的 s03 代码里客户端初始化是这样的from anthropic import Anthropic import os client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL)) MODEL os.environ[MODEL_ID]它读两个环境变量ANTHROPIC_BASE_URL和MODEL_ID。认证 token 走的是ANTHROPIC_AUTH_TOKEN但代码里有一行很关键if os.getenv(ANTHROPIC_BASE_URL): os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)这行的意思是如果你设了自定义 base_url它就把 auth token 清掉改用 base_url 里携带的通道认证。所以我们的接入思路很清晰——把 TaoToken 的 API 地址填进ANTHROPIC_BASE_URL把 Key 通过 TaoToken 的通道机制传进去模型 ID 填MODEL_ID。TaoToken 在这里扮演的角色是统一 Key 和 API 通道你不需要在代码里硬编码任何密钥也不需要为不同模型维护多套 base_url。一个 Key 走一个入口模型 ID 决定实际调用哪个模型。对 learn-claude-code 这种教学项目来说好处是你 clone 下来之后只改环境变量就能跑代码本身一行不用动。具体要准备三样东西第一一个 TaoToken 的 API Key。去控制台创建一个复制出来形如sk-开头的一串字符。这个 Key 不要提交到 git放.env里。第二确认你要用的模型 ID。比如claude-sonnet-4-20250514或者你账号下可用的其他模型标识。这个 ID 会传给MODEL_ID环境变量最终由 TaoToken 通道路由到对应模型。第三API 入口地址。TaoToken 的 API 地址是https://taotoken.net/api注意这个地址不带任何查询参数直接作为 base_url 使用。如果你在 Claude Code 或 Cline 这类工具里配置Base URL 就填这个。这里要提醒一句不要把 Key 写死在 Python 文件里。learn-claude-code 用python-dotenv加载.env你就在项目根目录建一个.env把 Key 和模型 ID 放进去。.gitignore里加上.env这是基本操作。配好之后你的 Agent 就有了一个稳定的模型调用通道。接下来我们写 TodoWrite 的代码让它在这个通道上跑起来。3. 可复制配置settings.json 与 config.toml 骨架 TodoWrite 工具注册这一节给你两份可直接抄的配置骨架以及 TodoWrite 在 Agent loop 里的注册方式。先看 Claude Code 侧的settings.json路径是~/.claude/settings.jsonWindows 是C:\Users\你的用户名\.claude\settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的TaoToken密钥, ANTHROPIC_MODEL: claude-sonnet-4-20250514, ANTHROPIC_SMALL_FAST_MODEL: claude-sonnet-4-20250514 }, permissions: { allow: [ Bash(python:*), Read, Write, Edit ] } }如果你用的是 Codex 风格的config.toml路径是~/.codex/config.toml骨架如下model claude-sonnet-4-20250514 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY [profiles.default] model claude-sonnet-4-20250514 provider taotoken注意env_key指向的环境变量名你在 shell 里 export 或者写进.env都行。三件套就是 Base URL、Key、Model ID缺一不可。Cline 的 MCP 配置也是同样的逻辑Base URL 填https://taotoken.net/apiKey 填你的 TaoToken KeyModel ID 填模型标识。现在回到 learn-claude-code 的 s03 代码。TodoWrite 的核心是一个TodoManager类它维护一个items列表每个 item 有id、text、status三个字段。status只允许pending、in_progress、completed三种值而且同一时间只能有一个in_progress。这个约束是硬性的违反就抛ValueError。class TodoManager: def __init__(self): self.items [] def update(self, items: list) - str: if len(items) 20: raise ValueError(Max 20 todos allowed) validated [] in_progress_count 0 for i, item in enumerate(items): text str(item.get(text, )).strip() status str(item.get(status, pending)).lower() item_id str(item.get(id, str(i 1))) if not text: raise ValueError(fItem {item_id}: text required) if status not in (pending, in_progress, completed): raise ValueError(fItem {item_id}: invalid status {status}) if status in_progress: in_progress_count 1 validated.append({id: item_id, text: text, status: status}) if in_progress_count 1: raise ValueError(Only one task can be in_progress at a time) self.items validated return self.render()render()方法把当前任务列表渲染成带标记的文本[ ]表示 pending[]表示 in_progress[x]表示 completed最后附上完成计数。这个渲染结果会作为 tool_result 返回给模型模型下一轮就能看到自己的进度。工具注册部分在TOOLS列表里加一项{ name: todo, description: Update task list. Track progress on multi-step tasks., input_schema: { type: object, properties: { items: { type: array, items: { type: object, properties: { id: {type: string}, text: {type: string}, status: {type: string, enum: [pending, in_progress, completed]} }, required: [id, text, status] } } }, required: [items] } }然后在TOOL_HANDLERS里挂上TOOL_HANDLERS { bash: lambda **kw: run_bash(kw[command]), read_file: lambda **kw: run_read(kw[path], kw.get(limit)), write_file: lambda **kw: run_write(kw[path], kw[content]), edit_file: lambda **kw: run_edit(kw[path], kw[old_text], kw[new_text]), todo: lambda **kw: TODO.update(kw[items]), }到这里TodoWrite 的工具注册就完成了。模型在需要规划多步任务时会主动调用todo工具传入一个 items 数组。你的TodoManager校验后存储并渲染结果回传给模型。下一节我们验证它是否真的生效。4. 验证请求跑通 s03 并确认待办写入生效配置和代码都就位后先确认环境变量加载正确。在项目根目录建.envANTHROPIC_BASE_URLhttps://taotoken.net/api ANTHROPIC_AUTH_TOKENsk-你的TaoToken密钥 MODEL_IDclaude-sonnet-4-20250514然后跑一个最小验证脚本确认通道能通import os from dotenv import load_dotenv from anthropic import Anthropic load_dotenv(overrideTrue) if os.getenv(ANTHROPIC_BASE_URL): os.environ.pop(ANTHROPIC_AUTH_TOKEN, None) client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL)) resp client.messages.create( modelos.environ[MODEL_ID], max_tokens100, messages[{role: user, content: reply with OK only}] ) print(resp.content[0].text)预期输出就是OK。如果这一步报 401说明 Key 或 base_url 有问题先解决再往下走。通道通了之后启动 s03cd learn-claude-code python agents/s03_todo_write.py你会看到提示符s03 。输入一个多步任务Refactor the file hello.py: add type hints, docstrings, and a main guard预期行为是模型第一轮就会调用todo工具创建一个包含 3 到 4 个条目的任务列表其中第一个标记为in_progress其余为pending。终端会打印类似 todo: [ ] #1: Read hello.py [] #2: Add type hints [ ] #3: Add docstrings [ ] #4: Add main guard (0/4 completed)然后模型开始执行第一个任务读文件、改代码。每完成一步它会再次调用todo把当前任务标记为completed下一个标记为in_progress。你会在终端看到进度不断更新最后的渲染结果类似[x] #1: Read hello.py [x] #2: Add type hints [x] #3: Add docstrings [x] #4: Add main guard (4/4 completed)如果你连续三轮模型都没有调用todo工具nag reminder 会注入到 tool_result 里你会看到模型收到reminderUpdate your todos./reminder后重新调用 todo 更新进度。这个机制在agent_loop里通过rounds_since_todo计数器实现used_todo False for block in response.content: if block.type tool_use: # ... 执行工具 ... if block.name todo: used_todo True rounds_since_todo 0 if used_todo else rounds_since_todo 1 if rounds_since_todo 3: results.insert(0, {type: text, text: reminderUpdate your todos./reminder})验证待办写入是否生效最直接的办法是看终端输出里有没有 todo:开头的行以及渲染结果里的[ ]、[]、[x]标记是否随任务推进而变化。如果模型从头到尾没调用 todo检查TOOLS列表里是否正确注册了todo项以及TOOL_HANDLERS里是否有对应的 lambda。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节把我在配 TaoToken learn-claude-code 过程中踩过的坑列出来对照真实报错给解法。401 authentication_error最常见。报错信息通常是Error code: 401 - {error: {message: Invalid API key}}。原因有三个Key 复制时带了空格或换行.env里变量名写错比如写成ANTHROPIC_API_KEY而不是ANTHROPIC_AUTH_TOKEN或者 base_url 末尾多了斜杠。检查.env文件确保ANTHROPIC_AUTH_TOKENsk-xxx没有引号、没有多余空格base_url 就是https://taotoken.net/api不要加/v1或尾部斜杠。local proxy failed / connection refused如果你在 settings.json 里配了ANTHROPIC_BASE_URL但本地有残留的代理设置可能会报local proxy failed。检查环境变量里有没有HTTP_PROXY、HTTPS_PROXY指向一个已经关掉的本地端口。在 shell 里unset HTTP_PROXY HTTPS_PROXY再跑。另外确认ANTHROPIC_BASE_URL没有被其他工具的配置覆盖。reading choices of undefined这个报错通常出现在用 OpenAI 兼容格式调 Claude 模型时。learn-claude-code 用的是 Anthropic SDK返回结构是response.content不是response.choices。如果你在代码里混用了 OpenAI 的解析方式就会报这个。检查你的agent_loop里是不是用了response.choices[0].message.content改成response.content并遍历 block。OAuth token 相关报错如果你之前配过 Claude Code 的 OAuth 登录环境里可能残留ANTHROPIC_AUTH_TOKEN或CLAUDE_CODE_OAUTH_TOKEN。learn-claude-code 的代码里有一行os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)但如果你在别的地方又设了 OAuth token可能会冲突。在.env里显式清空ANTHROPIC_AUTH_TOKEN留空或者确保只走 TaoToken 的 Key 通道。模型 ID 不匹配报错model not found或invalid model。检查MODEL_ID是否是你 TaoToken 账号下可用的模型标识。不同账号可用的模型列表可能不同去控制台确认一下。另外注意模型 ID 大小写敏感不要自己拼。todo 工具不触发模型一直不调用 todo只调 bash。检查TOOLS列表里 todo 的description是否清晰以及SYSTEM提示里有没有写“Use the todo tool to plan multi-step tasks”。s03 的 SYSTEM 提示是SYSTEM fYou are a coding agent at {WORKDIR}. Use the todo tool to plan multi-step tasks. Mark in_progress before starting, completed when done. Prefer tools over prose.如果这段被改短了模型可能就不太主动用 todo。把它加回去。排查顺序建议先确认通道通最小脚本返回 OK再确认工具注册TOOLS 和 TOOL_HANDLERS 都有 todo最后看模型行为SYSTEM 提示是否引导。三步都过了TodoWrite 基本就能稳定工作。6. 把 TodoWrite 用起来从 s03 到真实编码任务的接入建议TodoWrite 这个模块本身不复杂但它解决的是一个很本质的问题让 Agent 在多步任务里有可追踪的状态。你把它跑通之后可以试着做几件事。第一把TodoManager的render()输出格式改成你习惯的样子。比如加个进度条或者把 completed 的任务折叠起来只显示计数。渲染结果会回传给模型格式清晰对模型理解进度有帮助。第二调整 nag reminder 的阈值。默认是 3 轮你可以改成 2 轮让模型更频繁地更新或者改成 5 轮减少干扰。这个值在rounds_since_todo 3那行改。第三把 todo 工具和你的真实项目结合。比如你在做一个 Django 重构可以让模型先列 todo每改一个文件就更新状态。这样即使对话很长你随时能看到“现在做到哪个文件了”。如果你还没配 TaoToken 的 Key去控制台创建一个然后按第三节的 settings.json 或 config.toml 填好三件套。配好之后模型对话可以用来快速验证通道Coding Plan 适合长期编码任务API Keys 页面管理你的密钥。接入文档里有各工具的详细配置说明。最后说一个我踩过的坑.env文件不要提交到 git。learn-claude-code 的.gitignore里可能没有默认排除你自己加一行.env。Key 泄露了就去控制台吊销重发不要心存侥幸。TodoWrite 让 Agent 有了计划能力但计划能不能执行好取决于你的通道稳不稳、提示清不清晰。把这两件事做好剩下的就是让模型干活了。