1. 为什么 Coding Agent 的上下文会越跑越脏
DeepSeek Harness 的 Trajectory 是一套仅追加的会话事件流,它把系统提示、工具调用与结果、子 Agent 调度、上下文注入都写进同一条可检索、可分叉、可回放的记录里。它能做什么?简单说,就是让 Coding Agent 的每一次决策都有据可查,而不是只留一串越来越长的聊天消息。适合谁?适合已经在用 Codex、Claude Code、VSCode AI 插件、Ollama 或 LiteLLM 跑多轮编码任务,并且开始遇到“模型记错文件版本”“旧方案阴魂不散”的开发者。
我试过把一个支付回调服务的排查任务连续跑了三十多轮,最先崩掉的不是代码生成质量,而是上下文。模型在第 8 轮已经确认幂等键在旧客户端下不一致,第 22 轮却又引用第 3 轮那条“可能是消息队列重投”的猜测,最后给出一个同时改队列重试和幂等键的混合补丁。改动范围翻倍,回归测试还说不清哪一处真正生效。
这类污染通常来自五个方向:文件已经改了,模型还在引用旧版本代码;某个假设已被人工否决,却继续留在摘要里;工具调用失败,模型把空结果解释成业务事实;分支 A 的结论被带进分支 B,方案串线;外部文档里的指令性文本被当成运行规则。它们有一个共同点——不是模型不够聪明,而是我们把不同证据等级的内容塞进了同一层上下文。
我更愿意把 Agent 记忆拆成三层。第一层是事实状态,比如当前仓库提交、接口 Schema、测试结果、人工批准;第二层是运行轨迹,比如模型计划、工具调用、错误、分支;第三层是归档证据,比如完整日志和原始响应。模型每一轮主要读第一层和少量相关轨迹,不该默认读全部归档。很多上下文污染,本质就是把归档里的旧日志直接提升成了当前事实。
事实还需要有效期。接口 Schema 在提交变化后失效,测试结果在依赖升级后要重跑,人工授权在任务结束后应过期。让事实能失效,比不断追加“最新事实”更容易控制模型看到的内容。个人项目也能轻量实现:state.md存当前事实,events.jsonl存事件摘要,artifacts/存原始报告,模型默认只读前两者的选定部分。
这就是 Trajectory 和普通聊天记录的分水岭。聊天记录强调交流顺序,Trajectory 应该强调事件来源、状态变化和因果关系。一个“测试失败”事件得知道由哪个命令产生、针对哪个提交、退出码是什么、后面是否被新结果替代。没有这些,存再多文本也无法可靠恢复任务。
2. 用 TaoToken 统一 Key 打通模型节点
上下文治理和模型接入是两件事,但接入方式会直接影响你能不能做对比实验。TaoToken 提供统一的 Key 和 API 通道,官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。它的作用是把不同模型节点收敛到一个可替换的接口层,这样你在做 Trajectory 分叉、压缩、回放时,切换模型不会牵动整套事件流结构。
为什么这件事重要?因为上下文治理的评估需要“同一份输入、不同模型”的对照。如果每个模型都要单独配一套鉴权和地址,你根本没法快速回放。统一通道让你把模型当成可替换节点:事件流、分支、数据等级、权限都由应用侧控制,模型只负责在被允许的上下文范围内做判断。
先拿 Key。进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 创建一个新 Key。建议按用途分 Key:一个给本地调试,一个给回放任务,一个给长期编码 Agent。这样出问题时能快速定位是哪个环节在消耗额度。
拿到 Key 后,先确认模型列表和接入文档,文档地址是 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。文档里会说明当前可用的模型标识和请求格式。你需要记下三件套:Base URL、Key、Model ID。这三样在后面的配置片段里会反复出现。
如果你用的是 Claude Code 这类工具,可以参考 Anthropic 兼容接入说明 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite 。它讲的是如何把 Base URL 和 Key 填进对应配置,让工具走统一通道。注意,这里只是接入层,不改变你的事件流和上下文策略。
想先验证模型是否通,可以直接用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 发一条测试消息。如果返回正常,说明 Key 和通道没问题,再往下做 Trajectory 实验。长期跑编码 Agent 的话,可以了解 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合持续性的任务场景。
这里要强调一个边界:TaoToken 是模型接入通道,不是 Trajectory 存储,也不是编辑器替代品。事件流、分支状态、数据等级、权限判断,全部由你的应用侧负责。模型节点可以换,但当前分支、事件状态、数据等级不能由模型自行改变。把这条边界守住,后面的分叉和回放才有意义。
3. 可复制的分叉、压缩、回放配置片段
这一节给可直接抄的配置。先说明:下面是自定义结构,用于演示工程方法,不代表 DeepSeek Harness 当前的正式 Schema。你可以按自己的运行时字段名调整,但语义要保留。
先看事件的最小结构。它需要因果上游、分支标识、工作区版本、状态语义和数据等级:
{ "event_id": "evt-042", "parent_id": "evt-039", "branch_id": "fix-pagination-b", "type": "tool.result", "source": "pytest-runner", "created_at": "2026-08-18T10:24:00+08:00", "workspace_revision": "git:7d31a9c", "input_hash": "sha256:example", "status": "failed", "summary": "2 tests failed, 18 passed", "supersedes": null, "data_class": "internal-redacted" }parent_id表示因果上游,branch_id防止方案串线,workspace_revision说明事件对应哪个代码状态,supersedes声明新事件替代旧事实,data_class决定事件能否送给外部模型。完整测试日志放受控工件,事件只存摘要和哈希。
事件状态要有语义:active当前可用,superseded被新证据替代,rejected被人工否决,expired授权或缓存过期,audit-only只用于复盘。上下文构建器只选允许进入模型的状态,审计工具仍能看完整历史。
接下来是上下文策略配置。它决定每一轮模型到底看到什么:
context_policy: coding-default-v2 include: - task.contract - human.decision - tool.result.active - file.snapshot.current - model.plan.latest exclude: - event.status.rejected - event.status.expired - branch.other - data_class.restricted limits: max_events: 60 max_tool_output_chars: 12000 max_file_snippets: 16 require: - workspace_revision - source - data_class数值只是示意,真实限制按任务、模型和本地测试定。关键是规则可见:模型为什么看到某个事件、为什么没看到另一个、压缩发生在哪一步,都能复盘。
分叉配置要冻结共同起点,否则方案 A 在旧代码上跑、方案 B 在新代码上跑,结果没法比:
[branch] root_event = "evt-039" branch_id = "fix-pagination-b" freeze_workspace_revision = "git:7d31a9c" inherit_data_class = false allow_rebase = false [branch.merge_gate] require_reproducible_sample = true require_deterministic_check = true require_workspace_hash = true require_human_decision = true merge_as = "evidence-only"inherit_data_class = false很关键:不因为根任务已授权就默认继承所有输入。合并的是证据,不是整段历史。门禁缺任何一项,合并结果只能标为“参考信息”,不能变成主线事实。
压缩策略要保留负面信息。测试通过、文件找到很容易进摘要,权限拒绝、未解决问题、缺失样例更容易被删掉。把摘要固定成六个区块:
{ "compression_profile": "coding-summary-v3", "sections": [ "confirmed", "failed", "conflicts", "pending", "forbidden_actions", "next_step" ], "preserve_source_tags": true, "preserve_event_ids": true, "allow_external_promotion": false, "max_summary_chars": 4000 }allow_external_promotion = false防止外部资料经摘要后被当成人工决定。任何影响权限、数据发送、副作用的结论,必须来自策略或人工事件。
回放配置要记录策略版本、选中事件、排除类别、摘要哈希和目标模型:
{ "replay_id": "replay-2026-08-18-01", "policy_version": "coding-default-v2", "selected_events": ["evt-039", "evt-042", "evt-045"], "excluded_categories": ["branch.other", "event.status.expired"], "summary_hash": "sha256:summary-example", "target_model": "your-model-id", "base_url": "https://taotoken.net/api", "checkpoint": "tests-running" }检查点要有阶段语义:context-ready、analysis-complete、patch-prepared、tests-running、human-approved。恢复时只能从满足前置条件的检查点开始。停在tests-running可以重跑测试;停在human-approved后工作区变了,批准必须失效。
4. 验证请求与上下文长度对比
配置写完,得验证它真的在减少无效上下文。这里给一套可跟做的对比步骤,用 TaoToken 统一通道跑,方便切换模型做对照。
第一步,准备一个固定回放任务集。至少包含:正常完成、分支冲突、工具超时、工作区变化、权限过期、注入内容六类。每类准备相同的输入事件流,这样升级策略后能直接对比。
第二步,用 curl 发一次最小请求,确认通道和模型标识可用:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [ {"role": "system", "content": "You are a coding agent. Only use provided events."}, {"role": "user", "content": "Summarize the current task state from the given events."} ], "max_tokens": 512 }'把$TAOTOKEN_API_KEY换成你在 API Keys 页面创建的 Key,your-model-id换成文档里确认的模型标识。返回正常说明通道通了。
第三步,跑上下文构建器,记录两组数字:注入的事件数量和工具输出字符数。对比“全量历史”和“策略过滤后”的差异。下面是一个统计脚本示例:
import json def load_events(path): with open(path, "r", encoding="utf-8") as f: return [json.loads(line) for line in f if line.strip()] def build_context(events, policy): selected = [] for e in events: if e.get("status") in policy["exclude_status"]: continue if e.get("branch_id") in policy["exclude_branches"]: continue if e.get("data_class") in policy["exclude_data_class"]: continue selected.append(e) return selected[: policy["max_events"]] events = load_events("events.jsonl") policy = { "exclude_status": {"rejected", "expired", "audit-only"}, "exclude_branches": {"branch-a"}, "exclude_data_class": {"restricted"}, "max_events": 60, } full_chars = sum(len(json.dumps(e, ensure_ascii=False)) for e in events) ctx = build_context(events, policy) ctx_chars = sum(len(json.dumps(e, ensure_ascii=False)) for e in ctx) print(f"total_events={len(events)} total_chars={full_chars}") print(f"context_events={len(ctx)} context_chars={ctx_chars}") print(f"reduction={(1 - ctx_chars / full_chars) * 100:.1f}%")实测下来,一个跑了四十多轮的任务,全量历史约 18 万字符,策略过滤后压到 2.3 万字符左右,减少约 87%。同时被排除的类别里能看到branch.other和event.status.expired,说明过滤规则在生效。
第四步,用同一份上下文分别请求两个模型,比较它们引用事件编号的稳定性。如果某个模型无法稳定引用HUMAN-12、TEST-31这类来源编号,就降级为只提供解释建议,不让它推进需要证据的任务状态。
第五步,做污染探针。在测试事件流里放入已过期的文件片段、被人工否决的结论、其他分支日志、不可信网页指令,然后检查它们是否进入最终模型输入。探针不含真实敏感数据,只要能检测到即可。每次策略升级后跑一遍,能快速发现过滤规则是否退化。
第六步,验证恢复。让工具执行到一半停进程,修改工作区,再尝试恢复;让权限在等待期间过期;让外部请求返回未知状态。观察系统是否正确创建分支、重新核验事实、阻止重复副作用。只在顺利任务上测恢复,发现不了边界问题。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
接入和回放过程中,报错基本集中在几类。下面按真实错误对照排查。
401 Unauthorized。最常见的原因是 Key 没带上、带错、或者环境变量没生效。先确认请求头是Authorization: Bearer <key>,再确认$TAOTOKEN_API_KEY在当前 shell 里真的有值。如果 Key 是在控制台刚创建的,注意复制时别带多余空格。分 Key 用途时,回放任务用了调试 Key 也可能因为权限范围不同被拒。排查顺序:打印环境变量长度、用 curl 单独测一次、换一个 Key 复测。
local proxy failed。这个报错通常出现在本地工具链里,表示工具尝试走本地代理配置但没连上。检查你的工具配置里 Base URL 是否写成了https://taotoken.net/api,而不是某个本地地址。如果你在配置里同时留了旧的本地代理项,工具可能优先走它。把配置里多余的本地代理字段清掉,只保留统一通道地址。注意,这里说的是工具自身的配置项,不是让你去搭什么网络层。
reading choices 相关报错。这类错误一般出现在解析响应时,说明返回结构和你代码里假设的字段不一致。常见原因是模型标识写错,返回了错误对象而不是正常补全结构。先打印原始响应体,确认有没有choices字段。如果没有,检查model字段是否和文档里的一致。另一个原因是流式和非流式混用,代码按流式解析却发了非流式请求。统一请求模式后再测。
OAuth 相关报错。如果你用的是 Claude Code 这类工具,接入时可能遇到 OAuth 流程和 API Key 混用的问题。参考 Anthropic 兼容接入说明,确认是走 Key 还是走 OAuth,不要两套同时配。混用时工具可能优先走 OAuth 缓存,导致 Key 不生效。清掉旧的鉴权缓存,按文档重新配一遍三件套:Base URL、Key、Model ID。
上下文相关异常。如果模型回答里出现已经过期的事实,先别怀疑模型。检查上下文构建器的排除规则是否生效,打印本次注入的事件编号列表,看event.status.expired和branch.other是否真的被排除。如果排除列表突然变短,可能表示更多历史被送进模型,需要人工检查策略版本。
分支串线。如果方案 B 的回答里出现方案 A 的结论,检查branch_id过滤是否在上下文构建阶段生效,而不是只在展示层过滤。合并时确认走的是证据包,不是整段历史。合并门禁缺项时,结果只能标为参考信息。
重复副作用。恢复后如果出现重复文件修改,检查写操作是否有幂等标识。运行时应通过文件哈希、操作标识和事件状态确认副作用是否完成,再决定继续、补记事件或回滚。仅凭本地进程状态无法判断外部副作用是否完成,必要时进入人工对账。
Token 消耗异常。如果成本突然上升,先在应用侧看注入事件数量,再看网关统计。网关能统计 Token 和请求,但无法判断哪些事件已过期、哪些分支不相关。先在上下文构建器减少无效上下文,再由接入层记录调用成本,别用路由优化掩盖上下文污染。
6. 把 Trajectory 变成 Agent 的状态基础
回到开头那个支付回调案例。分叉之后,方案 A 的“证据不足”留在轨迹里标记暂停,方案 B 复现成功后只把可验证事实合并回主线:触发条件、请求样例哈希、失败测试、修复补丁、通过的回归测试、人工决定。模型的长篇讨论、无关搜索结果、已否决假设仍留在分支,只用于审计。主线上下文因此显著变短,未来同类问题再现时可以回放方案 B 的输入和检查。
这套方法的价值不在“记得更多”,而在“少记错误的内容”。当任务可以安全分叉、只合并证据、在中断后恢复、在升级后回放,并且能解释每次上下文注入的来源时,Trajectory 才不再是日志页面,而成为 Agent 的状态基础。
落地时从最小事件集开始:任务契约、人工决定、工具结果、文件版本、最终验收。随后根据重复出现的问题增加分支、检查点、数据等级和回放。不要一开始记录所有模型文本,也不要把“日志越多”当成可观测性越强。团队还要明确事件所有者:工具插件负责工具事实,工作区服务负责文件版本,策略层负责权限决定,人工负责业务确认,模型摘要不能替这些所有者发言。
想先验证模型节点是否通,可以去模型对话页面发一条测试消息;要长期跑编码 Agent,可以了解 Coding Plan;接入配置和 Key 管理分别在接入文档和 API Keys 页面。把统一通道配好,再把上下文策略、分叉门禁、压缩区块、回放检查点逐项落地,你的 Coding Agent 才算真正有了可解释、可恢复、可复现的运行基础。