1. 从 CLBench 39% 到 73%:写经验前的那一次只读探测,才是长跑智能体的分水岭
如果你在 Claude Code 里跑过几个小时以上的长期任务,大概率见过这种翻车现场:会话收尾时,智能体非常自信地把一条“经验”写进记忆文件,第二天另一个会话读到它,照着执行,直接报错。更糟的是,这条错误经验会被后续每一次会话反复注入上下文,错误被不断放大,最后你甚至分不清到底是模型不行还是记忆被污染了。我这次的实验环境里,写经验的智能体和做校验的探测智能体都挂在同一个模型入口上:TaoToken,注册和取 Key 的入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=probe_intro ,Base URL 统一填 https://taotoken.net/api 。整套流程跑下来最直观的感受是:探测这一步确实要多花 Token,但它省下来的,是后面几十次会话里被错误经验带偏的连锁开销。
Microsoft 那篇讲 environment-probing curation 的工作,核心动作可以概括成一句话:在长期运行智能体把经验落盘之前,先让一个只读访问环境的独立智能体去核一遍,确认这条经验是否成立、是否可复用。论文给出的结果是,CLBench 上的通过率从 39% 提升到 73%。这个提升幅度看起来像是模型能力的跃迁,但拆开看,它更像是工程细节的胜利——探测智能体能读到什么、写经验的一方拿到什么样的反馈、以及这次探测本身要烧掉多少 Token,三件事决定了这套机制能不能真的落地。
本文不聊论文本身的方法论复述,只解决一个工程问题:怎么在自己的机器上,把“写经验前先只读探测”这套流程接起来,并且让探测智能体用 TaoToken 的 Key 和 Base URL 跑通,最后产出可复现的经验校验日志和 CLBench 指标对比。涉及的所有命令和脚本都在你本地执行,探测侧只读访问沙箱环境,不碰任何生产库。
2. 只读探测智能体的最小架构:三个角色、一条 Token 预算线
要把这套机制工程化,先别急着写提示词,先把角色拆清楚。最小可用版本只需要三个角色,它们共用同一套模型入口,但权限完全不同。
角色一:Writer(经验生产者)。它是那个长期运行的智能体,负责在任务结束后总结“我这次学到了什么”。它的输出是一条候选经验(candidate),格式建议是结构化的:结论、适用条件、验证方式、失效边界。关键点在于,Writer 只有写候选经验的权限,没有直接写持久记忆的权限。
角色二:Prober(只读探测者)。它拿到候选经验后,只做一件事:去环境里找证据。它的权限必须被严格限制为只读——只读挂载的代码目录、只读的日志文件、只读的接口快照、本地导出的数据副本。它不能写文件、不能改配置、不能调用任何有副作用的命令。它的输出是一份裁决:accept / reject / revise,外加证据引用。
角色三:Store(持久记忆)。它只接受 Prober 裁决为 accept 或 revise 之后的经验,并且记录来源候选 ID、证据摘要、生效范围。Store 是唯一可写的那一层。
这套拆分的意义在于,它把“记忆污染”这个模糊问题变成了一个可观测的流水线问题:候选经验有多少条被拒、拒绝原因分布是什么、每条经验的探测成本是多少。
从 Token 预算的角度看,这里有一条很反直觉的线。探测智能体的输入是「候选经验 + 环境证据片段」,输出是一段裁决,长度可控,通常几百到一两千 Token 就能完成一次校验。但如果没有这道关卡,一条错误经验进入 Store 之后,它会被后续每一次会话注入上下文;假设错误经验残留概率为 p,每次会话注入这条经验的成本为 c,后续还有 n 次会话,那么期望浪费大约是 p × n × c。当 n 到几十上百的时候,这个数字会远远超过一次探测的成本。
所以“只读探测消耗 Token”这件事,正确的算账方式不是看单次探测花了多少,而是看它拦截掉了多少条会被反复注入的错误经验。这一点在后面的 Token 账本一节会给出可计算的估算方式。
3. 先把 Key 和 Base URL 准备好:TaoToken 侧的三步准备
在配置任何客户端之前,先把凭证准备好。这一步不需要写代码,全部在浏览器里完成。
第一步,进入 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=probe_setup ,完成账号注册。第二步,在控制台创建 API Key,创建入口是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=probe_keys 。创建时建议按用途拆 Key:给探测智能体单独一把 Key,给写经验的 Writer 另一把。这样后面算 Token 账的时候,你能一眼看出探测环节到底消耗了多少,而不是和主任务混在一起。
第三步,把 Base URL 记下来:https://taotoken.net/api 。这个地址是所有客户端配置里唯一需要统一的字段,注意它不带任何 UTM 参数。Key 在本文里统一用占位符 YOUR_API_KEY 表示,你替换成自己创建的那一串即可。
准备完成之后,先在本地的 shell 里验证一次连通性,命令由你在本地执行:
export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="YOUR_API_KEY" # 只验证鉴权与路由是否可达,不发送任何业务数据 curl -sS -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ "${TAOTOKEN_BASE_URL}/v1/models"如果返回 200,说明 Key 和 Base URL 这一层没问题,可以往下配客户端了。这里要提醒一句:探测智能体和 Writer 建议使用不同的 Key,不是为了安全隔离,而是为了计量隔离。论文里 CLBench 从 39% 到 73% 这种指标,如果没有干净的计量口径,你根本无法判断提升到底来自探测机制,还是来自某个环节偷偷换了更强的模型。
4. Claude Code 配置:用 settings.json 把探测智能体接上 TaoToken
Claude Code 的配置走的是 ANTHROPIC_* 这一套环境变量。最稳的做法不是每次开终端都 export,而是写进 settings.json,这样无论你从哪个 shell 启动、从哪个目录启动,配置都一致。
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "你在模型列表里选定的主力模型 ID", "ANTHROPIC_SMALL_FAST_MODEL": "你在模型列表里选定的轻量模型 ID" }, "permissions": { "allow": [ "Read", "Glob", "Grep" ], "deny": [ "Write", "Edit", "Bash(rm:*)", "Bash(git push:*)" ] } }这份配置里有两点值得单独说。
第一,permissions是只读探测能成立的关键。探测智能体的全部价值来自“它只读”,如果它同时拥有 Write 和 Edit 权限,它在探测过程中就可能顺手改动环境,那探测结论就不再可信。把 Write / Edit 关掉,把带副作用的 Bash 命令 deny 掉,只保留 Read、Glob、Grep 这类只读动作,探测才是纯粹的观测。
第二,模型选择上建议主力模型和轻量模型分开配。探测任务的特点是输入不长、判断逻辑明确,用轻量模型通常足够;而 Writer 需要做经验抽象,更适合主力模型。这种分工能显著压低探测环节的单位成本,而成本正是很多人不敢上这套机制的原因。
配置完成后,先用一个最小的探测任务验证链路。下面这段提示词可以直接丢给 Claude Code,让它只做只读核查,不做任何修改:
你是一个只读探测智能体。下面是候选经验: 候选经验:在仓库根目录执行 `make test` 会跳过集成测试,需要加 `-tags=integration`。 请完成: 1. 只读访问当前仓库,查找能支持或反驳该结论的证据; 2. 引用具体文件路径与行号; 3. 给出裁决:accept / reject / revise; 4. 输出 JSON,不要输出任何解释性长文。如果它返回了带文件路径引用的 JSON 裁决,说明 Claude Code 这一侧的链路已经通了。关于 Claude Code 更细的接入参数,可以对照文档 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=probe_claudecode 逐项核对。
需要强调的一点:ANTHROPIC_* 这套变量只适用于 Claude Code,不要把它们套到 Codex 上。两个客户端的配置体系完全不同,混用最常见的后果是客户端读不到 Key,报鉴权失败,然后你会误以为是服务端问题。
5. Codex 配置:config.toml 里只改两处,跑同一个 Base URL
Codex 走的是 config.toml,与 Claude Code 完全不同的字段体系。核心只有两处:供应商的 base_url,以及读取 Key 的环境变量名。
# ~/.codex/config.toml model = "你在模型列表里选定的模型 ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"配套的环境变量在本地设置:
export TAOTOKEN_API_KEY="YOUR_API_KEY"这里有几个容易踩的点。
一是env_key写的是变量名而不是变量值。写成env_key = "YOUR_API_KEY"这种形式,客户端会去读一个叫 YOUR_API_KEY 的环境变量,结果当然是读不到。正确做法是变量名和 export 的名字保持一致。
二是 base_url 统一用 https://taotoken.net/api ,不要在客户端里自己拼路径,也不要带 UTM 参数。部分客户端版本会自动补/v1,如果你的本地版本对路径处理不一样,以你实际能跑通的那一种为准,但源头地址始终是这一个。
三是 Codex 侧的探测任务,同样要用只读沙箱来跑。Codex 的命令执行能力比较强,如果你把探测智能体直接放在一个有写权限的工作目录里,它可能在核查过程中创建临时文件、修改配置,污染后续复现。
配置完之后,可以用一个最小命令验证:
codex exec "只读检查当前目录是否存在 go.mod,并输出其 module 名称,不要修改任何文件"能正常返回 module 名称,说明 Codex + TaoToken 这条链路已经通了。
6. CC Switch 三件套:让探测智能体和 Writer 各走各的配置
如果你同时在用 Claude Code 和 Codex,或者需要在“探测配置”和“写经验配置”之间来回切,手动改配置文件会非常痛苦。CC Switch 这类切换工具的价值就在这里。不管具体界面长什么样,它管理的永远是三件套:
供应商(Provider):Base URL,统一填 https://taotoken.net/api 。凭证(Credential):API Key,探测用一把、Writer 用另一把。模型映射(Model Map):默认模型与轻量模型分别指向哪个模型 ID。
一个示意配置长这样,字段名以你本地工具版本为准,关键是这三类值:
profiles: - name: probe-readonly base_url: "https://taotoken.net/api" api_key: "YOUR_PROBE_API_KEY" model_map: default: "你的主力模型 ID" fast: "你的轻量模型 ID" - name: writer-memory base_url: "https://taotoken.net/api" api_key: "YOUR_WRITER_API_KEY" model_map: default: "你的主力模型 ID"把探测和写入拆成两个 profile,带来的最大好处是可计量。你可以在月底直接看到:probe-readonly 这个 profile 烧了多少 Token,writer-memory 烧了多少。如果探测的成本占比过高,你可以针对性地优化探测策略——比如缩短证据片段、减少探测轮次——而不是笼统地觉得“这套机制太贵”。
另外一个实践建议:切换 profile 之后,先在只读沙箱里跑一次最小探测任务,确认链路通了再接入正式的长期运行任务。CC Switch 切换的只是配置指向,它不会替你校验权限,权限那部分还是要在客户端自己的配置里锁死。
7. 经验校验日志:Prober 应该输出什么,才能被复盘
整套机制里最容易被忽略、但价值最高的一环,是探测日志。没有日志,你只知道“通过率从 39% 涨到了 73%”,但完全不知道涨在哪里。建议把 Prober 的输出固化成 JSONL,一行一条裁决,便于后续统计。
{"ts":"2026-01-01T10:00:00Z","candidate_id":"exp-0041","writer_profile":"writer-memory","claim":"仓库根目录 make test 会跳过集成测试","probe_actions":["grep_makefile","read_docs_cli"],"evidence":["Makefile:142","docs/testing.md:88"],"verdict":"revise","revise_note":"需追加 -tags=integration 且仅对 test-integration 目标生效","reuse_scope":"repo:demo, branch:main","token_cost":{"probe_in":812,"probe_out":244}} {"ts":"2026-01-01T10:04:12Z","candidate_id":"exp-0042","writer_profile":"writer-memory","claim":"配置文件改动后无需重启服务","probe_actions":["read_config_doc","grep_reload_handler"],"evidence":["docs/config.md:31"],"verdict":"reject","reject_reason":"证据显示热加载仅在 watch 模式下生效","token_cost":{"probe_in":655,"probe_out":198}}这几类字段各有用途。verdict用来算拦截率;reject_reason和revise_note用来分析错误经验的类型分布;evidence用来在事后追溯——当一条经验后来被证明有问题时,你能顺着证据引用回到当时的环境状态;token_cost则直接支撑成本核算。
判断一条候选经验是否该通过,建议至少覆盖四个维度:
正确性:结论在当前环境下是否成立,有没有反例。可复用性:它是只在这个仓库、这个分支成立,还是可以泛化。作用域写得越窄,通过门槛可以越低。时效性:它依赖的版本号、配置项是否可能在下一次升级后失效。可验证性:这条经验是否留下了可执行的验证方式,而不是一句无法证伪的断言。
其中“可复用性”是最容易被 Writer 忽略的。长期运行的智能体倾向于把一次偶发观察写成普适规律,比如“某命令会失败”,而真实情况是“某命令在特定参数组合下会失败”。Prober 的价值很大程度上就是把后者从前者里剥离出来。
8. CLBench 指标复现:把 39% 到 73% 拆成你能观测的量
论文里 CLBench 通过率从 39% 提升到 73%,这个数字在你的环境里不可能原样复现,因为评测集、任务分布、模型版本都不一样。但你可以复现它的对比方法:同一套任务、同一套模型,只切换“是否在写经验前做只读探测”,然后对比通过率。
推荐的组织方式是两条流水线并行跑:
baseline:Writer 直接写经验进 Store,无探测。probing:Writer 写候选经验,Prober 只读校验后,再决定是否入 Store。
每条任务结束后输出一行 JSONL,记录任务是否成功、探测消耗的 Token、被拒绝的候选经验条数。然后本地跑一个统计脚本:
# run_probe_eval.py —— 在你本地执行 import json import statistics from pathlib import Path def load(path): return [json.loads(line) for line in Path(path).read_text(encoding="utf-8").splitlines() if line.strip()] def pass_rate(records): if not records: return 0.0 return sum(1 for r in records if r.get("task_success")) / len(records) baseline = load("runs/baseline.jsonl") probing = load("runs/probing.jsonl") print("baseline pass@1 :", round(pass_rate(baseline), 4)) print("probing pass@1 :", round(pass_rate(probing), 4)) probe_tokens = [r.get("probe_tokens", 0) for r in probing if r.get("probe_tokens")] if probe_tokens: print("probe token mean:", round(statistics.mean(probe_tokens), 1)) print("probe token p95 :", round(sorted(probe_tokens)[int(len(probe_tokens) * 0.95) - 1], 1)) rejected = sum(1 for r in probing if r.get("verdict") == "reject") revised = sum(1 for r in probing if r.get("verdict") == "revise") print("rejected:", rejected, "revised:", revised)跑完之后你手上会有四组数字:baseline 通过率、probing 通过率、探测的平均与 p95 Token 成本、拒绝与修订的分布。这四组数字放在一起,才构成一个可以拿来做决策的结论。只看通过率而不看 Token 成本,你会倾向于把所有东西都塞进探测;只看 Token 成本而不看通过率,你会觉得探测纯属浪费。
需要提醒的是,CLBench 这类长程记忆基准的有效性高度依赖任务长度。如果每个任务只有三五轮交互,探测带来的收益会被稀释到几乎看不见。要让对比有意义,任务至少要长到“错误经验有机会被后续会话复用”的程度。
9. 常见报错与排障:探测链路第一次跑通前最容易卡的地方
第一次接这套流程,绝大多数问题都集中在配置层,而不是模型层。下面这张表覆盖了我实际遇到的大部分情况。
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 401 鉴权失败 | Key 没带、带错,或环境变量名不一致 | 核对 client 里的 auth 字段与本地 export 的变量名是否完全一致 |
| 404 model not found | 模型 ID 与账号可用列表不一致 | 到模型列表页核对拼写,注意大小写与分隔符 |
| 请求超时 | 探测智能体一次性读了过多文件 | 只喂证据片段,不要让它遍历整个仓库 |
| 上下文超限 | 候选经验与证据拼接后过长 | 把一次探测拆成多轮,每轮只验证一个子结论 |
| 配置改了不生效 | 只在当前 shell export,GUI 启动没继承 | 把变量固化到 settings.json / config.toml |
| 探测结论不可信 | Prober 拥有 Write / Edit 权限 | 关掉写权限,只保留只读动作与只读命令 |
| Codex 读不到 Key | 把 ANTHROPIC_* 套到了 Codex 上 | Codex 用 config.toml 的 env_key,不要混用 |
还有一个不太像报错、但非常消耗时间的问题:探测智能体“过度探测”。它会为了验证一条很简单的经验,去读十几个文件,最后 Token 成本和耗时都远超预期。解决办法是在提示词里给出明确的探测预算,比如“最多执行 5 次只读操作,找不到证据就返回 reject”。探测的价值在于快速拦截,而不是做一次穷尽式的代码审计。
10. Token 账本:只读探测到底贵不贵,用公式算一遍
回到那个核心问题:写经验前多做一次只读探测,值不值。把它写成一个可以代入数字的式子:
总成本 = Σ(每次探测的 Token) + Σ(错误经验残留导致的额外 Token) 错误经验残留导致的额外 Token ≈ p × n × c × k p = 错误经验进入 Store 的概率 n = 后续会读这条经验的会话数 c = 每次注入这条经验的 Token 数 k = 因为错误经验导致重试的次数假设一条经验平均 120 Token,被注入到后续 50 次会话中,其中 30% 的情况会引发一次重试,那么仅这一条错误经验带来的额外注入成本就是 120 × 50 = 6000 Token 量级,再加上重试的推理成本。而一次只读探测的输入通常是候选经验加几段证据片段,输出是一段结构化裁决,几百到一两千 Token。这个对比关系说明,只要探测能拦下哪怕一小部分错误经验,账就是划算的。
更有意思的是长尾效应。错误经验一旦进入 Store,它不会自己消失,除非你显式清理。随着会话数增长,错误经验的注入成本是线性累积的,而探测成本是每次写经验时一次性发生的。时间越长,探测的相对收益越高。这大概也是为什么这类机制在“长期运行智能体”场景下才显得关键——短任务里它确实是纯开销。
要让这本账算得清,前提是计量口径干净:探测用独立的 Key,日志里带token_cost字段,并且定期把 reject 的候选经验拿出来人工抽查一小批,确认 Prober 不是在一味拒绝。一个健康的探测流水线,拒绝率不应该是 0%,也不应该高到 90%——前者说明形同虚设,后者说明 Writer 的产出质量或者探测提示词需要调整。
11. 落地清单:从今天开始把探测跑起来
把上面所有内容压缩成一份可以照着做的清单:
- 在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=probe_checklist 完成注册,并在控制台创建两把 Key,一把给 Prober,一把给 Writer。
- 统一 Base URL 为 https://taotoken.net/api ,不要带任何 UTM 参数。
- Claude Code 侧把配置写进 settings.json,用 ANTHROPIC_* 系列变量,并关闭 Write / Edit 权限。
- Codex 侧把配置写进 config.toml,只改 base_url 和 env_key 两处,别把 ANTHROPIC_* 搬过来。
- 用 CC Switch 之类的工具做 profile 切换,探测和写入各一个 profile,便于计费拆分。
- 准备只读沙箱:代码目录只读挂载,日志只读,命令只限只读操作,不接任何生产库。
- 固定 Prober 的输出格式为 JSONL,字段至少包含 candidate_id、evidence、verdict、token_cost。
- 跑 baseline 与 probing 两条流水线,用统计脚本对比通过率与 Token 成本。
- 定期抽查被拒的候选经验,校准探测提示词的松紧度。
这套流程最容易被低估的一点是:它并不要求你换一个更强的模型,只要求你在“写”和“存”之间,插入一个权限更小、动作更克制的角色。很多时候,长期运行智能体的可靠性问题,不是推理能力不够,而是它太容易相信自己刚刚得出的结论。
如果你还没决定用哪个模型来做探测,可以先在模型对话页里试跑几轮校验任务,比较不同模型在“给出裁决并附证据引用”这件事上的稳定性:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=probe_chat 。确认长期跑量之后,再考虑按用量选择更合适的 Coding Plan,入口是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=probe_plan 。Key 还没建的话直接去 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=probe_keys 建。Claude Code 的具体参数和权限写法,按 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=probe_claudecode 里的说明逐项核对即可。