1. 从context_length_exceeded到记忆字段:先给 Key 和 Base URL
长任务跑到第 N 轮,Claude Code 返回API Error: 400 prompt is too long: context_length_exceeded,或者 Codex CLI 出现stream error: context length exceeded,表面看是上下文窗口被塞满,实际是记忆系统没有做字段分层。很多团队第一反应是把历史对话整包丢给摘要模型,结果摘要调用本身又消耗 Token,而且下一轮召回了不该召回的旧内容,todo 状态照样丢。先把接入路径固定下来:到 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields 拿到 Key,再把请求地址设为https://taotoken.net/api。后续的字段保留清单、压缩前后 Token 对照,都基于这个入口复现。Base URL 在工具配置里不要带 UTM,只写https://taotoken.net/api。
我以“记忆系统设计者”的视角处理这个问题。Token 消耗方不是整段对话,而是记忆压缩与摘要调用。也就是说,每一次把长上下文压成 memory pack,都要有预算、有字段白名单、有可回滚版本。原文讨论的 Harness 层四类机制可以保留:上下文预算与卸载、压缩、todo-state 复述、跨会话记忆。但落到工程上,不能只写概念,要回答三个问题:哪些字段必须跨会话保留?压缩调用走哪个 Base URL?压缩前后 Token 怎么对照?
先给一条最小接入路径。
1.1 Claude Code:settings.json 与 ANTHROPIC_*
Claude Code 侧建议把配置写进~/.claude/settings.json,让环境变量随会话加载。不同版本可能读取ANTHROPIC_API_KEY或ANTHROPIC_AUTH_TOKEN,二选一即可,不要同时写两个不同值。
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "YOUR_API_KEY", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514", "ANTHROPIC_SMALL_FAST_MODEL": "claude-3-5-haiku-20241022" } }如果当前 Claude Code 版本使用 auth token 字段,可以替换为:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }改完后重启 Claude Code,用/status或一次最小对话确认请求确实走了https://taotoken.net/api。如果仍然报 401,优先检查 Key 是否复制完整、是否有多余空格、当前终端是否覆盖了旧环境变量。
1.2 Codex:config.toml,不要套 ANTHROPIC_*
Codex 侧不要使用ANTHROPIC_*。它走的是 Codex 自己的 provider 配置。典型配置在~/.codex/config.toml,核心是 provider、base_url、env_key 三项。
model = "gpt-5" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"本地环境变量这样设置:
export TAOTOKEN_API_KEY="YOUR_API_KEY"如果你的 Codex 版本要求wire_api = "responses",以本地实际报错为准切换。关键是:Codex 的 Key 走TAOTOKEN_API_KEY,Base URL 走https://taotoken.net/api,不要把 Claude Code 的ANTHROPIC_*变量复制过来。
1.3 CC Switch 三件套
如果你用 CC Switch 管理多套配置,核心只保留三件套:provider 名称、Base URL、API Key。模型映射可以另加,但不要和 Key 混在同一个字段里。
{ "provider": "TaoToken", "baseUrl": "https://taotoken.net/api", "apiKey": "YOUR_API_KEY", "defaultModel": "claude-sonnet-4-20250514" }不同 CC Switch 版本的字段名可能略有差异,但排查时先确认这三项:请求地址是不是https://taotoken.net/api,Key 是不是YOUR_API_KEY对应的真实值,当前激活的 provider 是不是 TaoToken。更多控制台操作入口统一走 TaoToken 官网:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_config 。
2. TaoToken Key 字段保留清单:must_keep / summarize / drop 三档
跨会话记忆压缩最容易犯的错,是把“保留字段”做成“保留对话”。对话是载体,字段才是状态。作为记忆系统设计者,我会把 memory pack 分成三档:
must_keep:跨会话必须原样或近原样保留,压缩后仍要能恢复目标。summarize:可以摘要,但必须保留来源引用和摘要版本。drop:不写回记忆,需要时从本地文件或日志重新读取。
一个可直接落地的字段保留配置如下:
{ "memory_policy_version": "1.0", "must_keep": [ "task_goal", "hard_constraints", "decisions", "todo_state", "artifact_refs", "interface_contracts", "failed_attempts" ], "summarize": [ "conversation_history", "tool_outputs", "test_logs", "long_file_excerpts" ], "drop": [ "api_keys", "pii", "full_source_code", "duplicate_stacktraces", "temporary_debug_prints" ], "budget": { "total_memory_tokens": 3000, "task_goal": 300, "hard_constraints": 400, "decisions": 900, "todo_state": 600, "artifact_refs": 300, "failed_attempts": 500 } }这份配置的重点不是字段多,而是字段之间有优先级。task_goal和hard_constraints决定任务会不会跑偏;decisions决定后续不重复讨论;todo_state决定下一步动作;artifact_refs决定模型知道去哪里读取真实文件;failed_attempts决定不会重复踩坑。相反,完整源码、重复堆栈、临时调试输出、密钥、个人身份信息,都不应该进入跨会话记忆包。
可以把每个字段的保留策略写成表:
| 字段 | 是否跨会话保留 | 压缩方式 | 示例 |
|---|---|---|---|
| task_goal | 必须 | 原文保留,限制 300 Token 内 | 将旧项目迁移到新构建链 |
| hard_constraints | 必须 | 编号去重,保留否定条件 | 不得改动公共 API |
| decisions | 必须 | 只留结论、理由、日期 | 选择方案 B,因兼容旧数据 |
| todo_state | 必须 | 只留未完成和阻塞项 | 待补测试用例,等待接口确认 |
| artifact_refs | 必须 | 路径 + 行号 + 校验和 | src/a.ts:120 |
| interface_contracts | 必须 | 保留入参、出参、错误码 | POST /v1/job |
| failed_attempts | 必须 | 保留失败命令与结论 | 升级依赖后启动失败 |
| conversation_history | 可摘要 | 分段摘要 + 来源 ID | 第 1-8 轮讨论记录 |
| tool_outputs | 可摘要 | 只留异常行和统计 | 测试失败 3 例 |
| full_source_code | 不保留 | 卸载到本地文件 | 通过 artifact_refs 读取 |
| api_keys | 不保留 | 禁止写入 | YOUR_API_KEY只在环境变量 |
这张表可以直接变成压缩 prompt 的约束。摘要模型不需要“聪明地猜”,它只需要按字段白名单输出。记忆压缩调用本身也要走 TaoToken,入口仍然是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_pipeline 。
3. 压缩链路的工程实现:预算、卸载、摘要、todo-state 复述
上下文预算不是“还剩多少窗口”,而是“每一类记忆最多占多少”。我的做法是三层预算:
- 系统层固定预算:系统提示、工具定义、输出格式,通常不可压缩。
- 任务层记忆预算:task_goal、constraints、decisions、todo_state,按上表分配。
- 证据层按需预算:文件、日志、工具输出,不直接进 prompt,只保留引用,需要时本地读取。
卸载机制很关键。不要把所有文件内容都塞进上下文。把大文件转成artifact_refs:
{ "artifact_refs": [ { "path": "src/service/order.ts", "line": 120, "reason": "订单状态机入口", "checksum": "sha256:..." }, { "path": "logs/test-run-042.log", "line": 880, "reason": "失败堆栈首次出现位置", "checksum": "sha256:..." } ] }下一轮需要细节时,由本地工具按路径和行号读取,而不是让记忆包携带全文。这样既减少 Token,也避免旧文件内容污染新任务。
摘要调用可以这样写。下面脚本使用 OpenAI 兼容方式访问 TaoToken,Base URL 写https://taotoken.net/api,Key 从环境变量读取:
import os import json from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", ) MEMORY_POLICY = { "must_keep": [ "task_goal", "hard_constraints", "decisions", "todo_state", "artifact_refs", "interface_contracts", "failed_attempts", ], "summarize": [ "conversation_history", "tool_outputs", "test_logs", ], "drop": [ "api_keys", "pii", "full_source_code", "duplicate_stacktraces", "temporary_debug_prints", ], } def compress_memory(raw_messages, model="claude-sonnet-4-20250514"): system_prompt = f""" 你是跨会话记忆压缩器。只输出 JSON,不要输出解释。 必须保留字段:{", ".join(MEMORY_POLICY["must_keep"])} 可摘要字段:{", ".join(MEMORY_POLICY["summarize"])} 禁止写回字段:{", ".join(MEMORY_POLICY["drop"])} 输出结构: {{ "task_goal": "", "hard_constraints": [], "decisions": [], "todo_state": [], "artifact_refs": [], "interface_contracts": [], "failed_attempts": [], "summaries": {{ "conversation_history": "", "tool_outputs": "", "test_logs": "" }} }} """ resp = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": json.dumps(raw_messages, ensure_ascii=False)}, ], temperature=0.1, ) content = resp.choices[0].message.content usage = resp.usage return content, usage if __name__ == "__main__": with open("raw_session.json", "r", encoding="utf-8") as f: raw = json.load(f) memory_pack, usage = compress_memory(raw) with open("memory_pack.json", "w", encoding="utf-8") as f: f.write(memory_pack) print("prompt_tokens:", usage.prompt_tokens) print("completion_tokens:", usage.completion_tokens) print("total_tokens:", usage.total_tokens)这段脚本的重点不是模型多强,而是把must_keep、summarize、drop变成了可执行约束。每次压缩后记录usage,就能做压缩前后 Token 对照。
todo-state 复述不能只放在记忆包里,还要在每一轮请求的尾部显式复述。推荐模板:
[Memory Pack v1] 目标:... 硬约束: 1. ... 已完成: - ... 未完成: - ... 阻塞: - ... 下一步: - ... 相关文件: - src/service/order.ts:120把 todo-state 放在靠近当前用户消息的位置,能明显降低长任务中“忘记下一步”的概率。跨会话恢复时,先读取memory_pack.json,再把 todo-state 复述到当前轮,而不是把整个历史重新加载。
4. 压缩前后 Token 对照:可复现实验与字段保留结果
为了判断压缩是否有效,不要只看“感觉短了”。建议做一张压缩前后 Token 对照表。下面是一次示例运行的对照数据,实际数字以你的项目为准:
| 字段 | 压缩前 Token | 压缩后 Token | 策略 |
|---|---|---|---|
| task_goal | 320 | 180 | 原文保留,去掉背景修辞 |
| hard_constraints | 860 | 240 | 编号去重,合并同类限制 |
| decisions | 4200 | 900 | 只留结论、理由、日期 |
| todo_state | 1500 | 600 | 只留未完成和阻塞项 |
| artifact_refs | 1800 | 300 | 路径 + 行号 + 原因 |
| interface_contracts | 900 | 320 | 保留入参、出参、错误码 |
| failed_attempts | 2600 | 500 | 保留失败命令与结论 |
| conversation_history | 42000 | 1200 | 分段摘要 + 来源 ID |
| tool_outputs | 68000 | 0 | 卸载为本地日志引用 |
| test_logs | 21000 | 260 | 只留异常行和统计 |
| 合计 | 142180 | 4500 | 压缩比约 31.6:1 |
这个对照表要配合版本号。每次压缩输出memory_policy_version和memory_pack_version,比如:
{ "memory_policy_version": "1.0", "memory_pack_version": "2025-06-01T10:00:00Z", "task_goal": "将旧项目迁移到新构建链", "hard_constraints": [ "不得改动公共 API", "必须保留旧数据兼容层" ], "decisions": [ { "date": "2025-05-30", "decision": "采用方案 B", "reason": "兼容旧数据,迁移成本低" } ], "todo_state": [ { "id": "T1", "status": "pending", "content": "补充迁移后回归测试", "blocked_by": "等待接口确认" } ], "artifact_refs": [ { "path": "src/service/order.ts", "line": 120, "reason": "订单状态机入口" } ], "interface_contracts": [ { "name": "POST /v1/job", "request": ["jobId", "payload"], "response": ["status", "traceId"], "errors": ["400", "409", "500"] } ], "failed_attempts": [ { "command": "npm run build:legacy", "result": "失败", "reason": "旧依赖与 Node 22 不兼容" } ], "summaries": { "conversation_history": "第 1-8 轮确认迁移范围;第 9-14 轮对比两套构建链;第 15 轮确定方案 B。", "tool_outputs": "测试失败 3 例,均为旧数据字段映射缺失。", "test_logs": "失败集中在 order_status 映射,见 logs/test-run-042.log:880。" } }生成这张表的方法也很简单。压缩前用 tokenizer 估算原始 transcript,压缩后再估算 memory pack,同时记录摘要调用的usage.prompt_tokens和usage.completion_tokens。重点看三个指标:
- 记忆包 Token 是否稳定在预算内,比如 3000 到 4500。
- 摘要调用 Token 是否下降,而不是每轮都重新摘要全量历史。
- todo 完成率是否提升,任务目标是否在跨会话后仍然一致。
如果记忆包越来越长,通常是must_keep里混入了日志;如果摘要调用越来越贵,通常是每一轮都把全量历史重新发给摘要模型。正确做法是增量摘要:上一版 memory pack + 本轮新增对话,生成下一版 memory pack。这样压缩成本与增量上下文成正比,而不是与总历史长度成正比。
5. Claude Code / Codex / CC Switch 排障:Key 字段、模型映射与常见错误
接入 TaoToken 后,常见问题通常集中在 Key、Base URL、模型名、协议四类。下面按工具拆开。
5.1 Claude Code 常见错误
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 401 Unauthorized | Key 错误或环境变量未加载 | 检查YOUR_API_KEY,重启 Claude Code |
| 404 Not Found | Base URL 写错或多了路径 | 确认ANTHROPIC_BASE_URL为https://taotoken.net/api |
| model not found | 模型 ID 不在当前账号可用列表 | 换成控制台可见模型 ID |
| prompt is too long | 记忆包未压缩或全量历史注入 | 启用字段白名单和 artifact_refs |
| 输出格式不稳定 | 摘要 temperature 过高 | 压缩调用 temperature 设为 0.1 或 0 |
Claude Code 的配置建议只保留一个 Base URL 来源。不要同时在 shell、settings.json、项目.env里写不同地址,否则很难定位请求到底走了哪里。
5.2 Codex 常见错误
Codex 的排障重点是不要套用ANTHROPIC_*。如果config.toml里 provider 配错,常见表现是 401 或直接回退到默认 provider。
model = "gpt-5" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"检查顺序:
echo $TAOTOKEN_API_KEY cat ~/.codex/config.toml确认TAOTOKEN_API_KEY有值,base_url是https://taotoken.net/api,当前model_provider是taotoken。如果报协议不匹配,再切换wire_api,不要改 Key 字段名。
5.3 CC Switch 三件套检查
CC Switch 的问题通常不是配置写错,而是激活了旧 provider。检查三件套:
{ "provider": "TaoToken", "baseUrl": "https://taotoken.net/api", "apiKey": "YOUR_API_KEY" }如果切换后仍走旧地址,先看 CC Switch 当前激活项,再重启终端或 IDE。模型映射字段只负责把本地模型名映射到服务端模型名,Key 和 Base URL 不要放在模型映射里。
6. 验收与回滚:压缩后目标丢失怎么办
记忆压缩不是一次性任务,而是持续管道。建议每次生成 memory pack 后做四项验收:
- 目标一致性:让模型只读 memory pack,复述任务目标,和原目标对比。
- 约束完整性:检查
hard_constraints是否包含所有否定条件和边界。 - todo 连续性:检查未完成项、阻塞项、下一步是否都在。
- 引用可解析:
artifact_refs的路径和行号是否能在本地打开。
如果压缩后目标丢失,不要直接让模型“再想一遍”。优先做回滚:
- 保留上一版
memory_pack.json。 - 提高
task_goal和hard_constraints的预算。 - 把
decisions从摘要改为“结论 + 理由”双字段。 - 检查
summaries.conversation_history是否覆盖了关键决策轮次。 - 减少
drop名单误伤,比如不要把接口契约误判为长文档。
一个最小回滚配置如下:
{ "rollback": { "enabled": true, "keep_versions": 5, "compare_on_restore": [ "task_goal", "hard_constraints", "todo_state", "decisions" ] } }安全上,记忆包一定不要写入密钥、个人身份信息、完整源码和生产数据。需要查库或执行命令时,由读者在本地环境执行,不要把数据库连接交给 Agent 直连。记忆系统只保存恢复任务所需的字段和引用。
7. 结语:把 TaoToken Key 当作记忆压缩链路的入口
回到最初的问题:跨会话压缩不是“把聊天记录总结一下”,而是设计一套字段保留策略。task_goal、hard_constraints、decisions、todo_state、artifact_refs、interface_contracts、failed_attempts这些字段必须跨会话保留;长对话、工具输出、测试日志可以摘要;完整源码、重复堆栈、密钥、个人身份信息不写回。压缩调用走 TaoToken,Base URL 固定为https://taotoken.net/api,Key 用YOUR_API_KEY占位,所有压缩前后 Token 对照都记录usage。
如果你还没接入,可以先到 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_cta 了解入口;然后按顺序做四步:
- 模型对话:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_chat
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_plan
- 创建 Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_keys
- Claude Code 文档:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=memory_compress_fields_doc
把 Key 拿到后,Claude Code 用settings.json和ANTHROPIC_*,Codex 用config.toml和TAOTOKEN_API_KEY,CC Switch 只检查 provider、Base URL、API Key 三件套。最后用一份字段保留配置和一张压缩前后 Token 对照表验收。这样长任务跨会话时,目标不会漂,todo 不会丢,摘要调用也不会变成新的 Token 黑洞。