筆記(3)TodoWrite 待辦寫入與 TaoToken 配置)
1. 為什么長鏈路任務(wù)里模型總在“裝忙”TodoWrite 要解決的真實痛點如果你跟著 learn-claude-code 的 s01、s02 一路寫下來會發(fā)現(xiàn)一個很尷尬的現(xiàn)象單步任務(wù)讀文件、改一行、跑個 bash它干得挺利索可一旦你丟給它“重構(gòu) hello.py加類型注解、補 docstring、再加 main guard”這種多步任務(wù)它就開始表演了。前三步做得像模像樣第四步突然回頭把第一步又做了一遍或者干脆跳過某一步直接宣布“已完成”。這不是模型笨而是長鏈路任務(wù)里上下文被工具結(jié)果不斷填滿系統(tǒng)提示的約束力被稀釋模型對“我現(xiàn)在做到哪了”這件事失去了清晰感知。TodoWrite 這個模塊要干的事說白了就是給 Agent 裝一塊白板。模型每做一步必須先在白板上寫清楚哪些任務(wù) pending、哪個 in_progress、哪些 completed。這塊白板不依賴對話歷史而是獨立存在的一個 Python 對象每次工具調(diào)用后把渲染結(jié)果塞回給模型看。這樣一來哪怕對話已經(jīng)滾了二十輪模型抬頭就能看到“哦任務(wù) 2 還在進行中任務(wù) 3 還沒開始”不會跑偏。我實測下來加了 TodoWrite 之后一個 6 步的 Python 包創(chuàng)建任務(wù)完成率從原來的“做一半就開始即興發(fā)揮”變成了基本能按順序走完。關(guān)鍵不在于模型變聰明了而在于它有了一個外部的、結(jié)構(gòu)化的狀態(tài)錨點。這篇筆記就帶你從零把這個模塊寫出來同時把 TaoToken 的 Key 和 API 通道配好讓 ClaudeCode 能真正跑起來。適合誰看已經(jīng)寫過 s01/s02 的讀者或者手頭有一個能調(diào)通的 Agent loop、想加上任務(wù)規(guī)劃能力的開發(fā)者。如果你還沒配過 API 通道第三節(jié)的配置可以直接抄。2. TaoToken 前置統(tǒng)一 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]它讀兩個環(huán)境變量ANTHROPIC_BASE_URL和MODEL_ID。認(rèn)證 token 走的是ANTHROPIC_AUTH_TOKEN但代碼里有一行很關(guān)鍵if os.getenv(ANTHROPIC_BASE_URL): os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)這行的意思是如果你設(shè)了自定義 base_url它就把 auth token 清掉改用 base_url 里攜帶的通道認(rèn)證。所以我們的接入思路很清晰——把 TaoToken 的 API 地址填進ANTHROPIC_BASE_URL把 Key 通過 TaoToken 的通道機制傳進去模型 ID 填MODEL_ID。TaoToken 在這里扮演的角色是統(tǒng)一 Key 和 API 通道你不需要在代碼里硬編碼任何密鑰也不需要為不同模型維護多套 base_url。一個 Key 走一個入口模型 ID 決定實際調(diào)用哪個模型。對 learn-claude-code 這種教學(xué)項目來說好處是你 clone 下來之后只改環(huán)境變量就能跑代碼本身一行不用動。具體要準(zhǔn)備三樣?xùn)|西第一一個 TaoToken 的 API Key。去控制臺創(chuàng)建一個復(fù)制出來形如sk-開頭的一串字符。這個 Key 不要提交到 git放.env里。第二確認(rèn)你要用的模型 ID。比如claude-sonnet-4-20250514或者你賬號下可用的其他模型標(biāo)識。這個 ID 會傳給MODEL_ID環(huán)境變量最終由 TaoToken 通道路由到對應(yīng)模型。第三API 入口地址。TaoToken 的 API 地址是https://taotoken.net/api注意這個地址不帶任何查詢參數(shù)直接作為 base_url 使用。如果你在 Claude Code 或 Cline 這類工具里配置Base URL 就填這個。這里要提醒一句不要把 Key 寫死在 Python 文件里。learn-claude-code 用python-dotenv加載.env你就在項目根目錄建一個.env把 Key 和模型 ID 放進去。.gitignore里加上.env這是基本操作。配好之后你的 Agent 就有了一個穩(wěn)定的模型調(diào)用通道。接下來我們寫 TodoWrite 的代碼讓它在這個通道上跑起來。3. 可復(fù)制配置settings.json 與 config.toml 骨架 TodoWrite 工具注冊這一節(jié)給你兩份可直接抄的配置骨架以及 TodoWrite 在 Agent loop 里的注冊方式。先看 Claude Code 側(cè)的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 風(fēng)格的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指向的環(huán)境變量名你在 shell 里 export 或者寫進.env都行。三件套就是 Base URL、Key、Model ID缺一不可。Cline 的 MCP 配置也是同樣的邏輯Base URL 填https://taotoken.net/apiKey 填你的 TaoToken KeyModel ID 填模型標(biāo)識?,F(xiàn)在回到 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()方法把當(dāng)前任務(wù)列表渲染成帶標(biāo)記的文本[ ]表示 pending[]表示 in_progress[x]表示 completed最后附上完成計數(shù)。這個渲染結(jié)果會作為 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 的工具注冊就完成了。模型在需要規(guī)劃多步任務(wù)時會主動調(diào)用todo工具傳入一個 items 數(shù)組。你的TodoManager校驗后存儲并渲染結(jié)果回傳給模型。下一節(jié)我們驗證它是否真的生效。4. 驗證請求跑通 s03 并確認(rèn)待辦寫入生效配置和代碼都就位后先確認(rèn)環(huán)境變量加載正確。在項目根目錄建.envANTHROPIC_BASE_URLhttps://taotoken.net/api ANTHROPIC_AUTH_TOKENsk-你的TaoToken密鑰 MODEL_IDclaude-sonnet-4-20250514然后跑一個最小驗證腳本確認(rèn)通道能通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)預(yù)期輸出就是OK。如果這一步報 401說明 Key 或 base_url 有問題先解決再往下走。通道通了之后啟動 s03cd learn-claude-code python agents/s03_todo_write.py你會看到提示符s03 。輸入一個多步任務(wù)Refactor the file hello.py: add type hints, docstrings, and a main guard預(yù)期行為是模型第一輪就會調(diào)用todo工具創(chuàng)建一個包含 3 到 4 個條目的任務(wù)列表其中第一個標(biāo)記為in_progress其余為pending。終端會打印類似 todo: [ ] #1: Read hello.py [] #2: Add type hints [ ] #3: Add docstrings [ ] #4: Add main guard (0/4 completed)然后模型開始執(zhí)行第一個任務(wù)讀文件、改代碼。每完成一步它會再次調(diào)用todo把當(dāng)前任務(wù)標(biāo)記為completed下一個標(biāo)記為in_progress。你會在終端看到進度不斷更新最后的渲染結(jié)果類似[x] #1: Read hello.py [x] #2: Add type hints [x] #3: Add docstrings [x] #4: Add main guard (4/4 completed)如果你連續(xù)三輪模型都沒有調(diào)用todo工具nag reminder 會注入到 tool_result 里你會看到模型收到reminderUpdate your todos./reminder后重新調(diào)用 todo 更新進度。這個機制在agent_loop里通過rounds_since_todo計數(shù)器實現(xiàn)used_todo False for block in response.content: if block.type tool_use: # ... 執(zhí)行工具 ... 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:開頭的行以及渲染結(jié)果里的[ ]、[]、[x]標(biāo)記是否隨任務(wù)推進而變化。如果模型從頭到尾沒調(diào)用 todo檢查TOOLS列表里是否正確注冊了todo項以及TOOL_HANDLERS里是否有對應(yīng)的 lambda。5. 本篇常見錯排查401、local proxy failed、reading choices、OAuth這一節(jié)把我在配 TaoToken learn-claude-code 過程中踩過的坑列出來對照真實報錯給解法。401 authentication_error最常見。報錯信息通常是Error code: 401 - {error: {message: Invalid API key}}。原因有三個Key 復(fù)制時帶了空格或換行.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但本地有殘留的代理設(shè)置可能會報local proxy failed。檢查環(huán)境變量里有沒有HTTP_PROXY、HTTPS_PROXY指向一個已經(jīng)關(guān)掉的本地端口。在 shell 里unset HTTP_PROXY HTTPS_PROXY再跑。另外確認(rèn)ANTHROPIC_BASE_URL沒有被其他工具的配置覆蓋。reading choices of undefined這個報錯通常出現(xiàn)在用 OpenAI 兼容格式調(diào) Claude 模型時。learn-claude-code 用的是 Anthropic SDK返回結(jié)構(gòu)是response.content不是response.choices。如果你在代碼里混用了 OpenAI 的解析方式就會報這個。檢查你的agent_loop里是不是用了response.choices[0].message.content改成response.content并遍歷 block。OAuth token 相關(guān)報錯如果你之前配過 Claude Code 的 OAuth 登錄環(huán)境里可能殘留ANTHROPIC_AUTH_TOKEN或CLAUDE_CODE_OAUTH_TOKEN。learn-claude-code 的代碼里有一行os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)但如果你在別的地方又設(shè)了 OAuth token可能會沖突。在.env里顯式清空ANTHROPIC_AUTH_TOKEN留空或者確保只走 TaoToken 的 Key 通道。模型 ID 不匹配報錯model not found或invalid model。檢查MODEL_ID是否是你 TaoToken 賬號下可用的模型標(biāo)識。不同賬號可用的模型列表可能不同去控制臺確認(rèn)一下。另外注意模型 ID 大小寫敏感不要自己拼。todo 工具不觸發(fā)模型一直不調(diào)用 todo只調(diào) 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。把它加回去。排查順序建議先確認(rèn)通道通最小腳本返回 OK再確認(rèn)工具注冊TOOLS 和 TOOL_HANDLERS 都有 todo最后看模型行為SYSTEM 提示是否引導(dǎo)。三步都過了TodoWrite 基本就能穩(wěn)定工作。6. 把 TodoWrite 用起來從 s03 到真實編碼任務(wù)的接入建議TodoWrite 這個模塊本身不復(fù)雜但它解決的是一個很本質(zhì)的問題讓 Agent 在多步任務(wù)里有可追蹤的狀態(tài)。你把它跑通之后可以試著做幾件事。第一把TodoManager的render()輸出格式改成你習(xí)慣的樣子。比如加個進度條或者把 completed 的任務(wù)折疊起來只顯示計數(shù)。渲染結(jié)果會回傳給模型格式清晰對模型理解進度有幫助。第二調(diào)整 nag reminder 的閾值。默認(rèn)是 3 輪你可以改成 2 輪讓模型更頻繁地更新或者改成 5 輪減少干擾。這個值在rounds_since_todo 3那行改。第三把 todo 工具和你的真實項目結(jié)合。比如你在做一個 Django 重構(gòu)可以讓模型先列 todo每改一個文件就更新狀態(tài)。這樣即使對話很長你隨時能看到“現(xiàn)在做到哪個文件了”。如果你還沒配 TaoToken 的 Key去控制臺創(chuàng)建一個然后按第三節(jié)的 settings.json 或 config.toml 填好三件套。配好之后模型對話可以用來快速驗證通道Coding Plan 適合長期編碼任務(wù)API Keys 頁面管理你的密鑰。接入文檔里有各工具的詳細(xì)配置說明。最后說一個我踩過的坑.env文件不要提交到 git。learn-claude-code 的.gitignore里可能沒有默認(rèn)排除你自己加一行.env。Key 泄露了就去控制臺吊銷重發(fā)不要心存僥幸。TodoWrite 讓 Agent 有了計劃能力但計劃能不能執(zhí)行好取決于你的通道穩(wěn)不穩(wěn)、提示清不清晰。把這兩件事做好剩下的就是讓模型干活了。