
字节笔记本
2026年10月6日 · 约 6 分钟读完
DeepSeek Harness 跑完一轮任务要几步
DeepSeek 开源的 agent 框架 deepseek-harness(npm 包名 @deepseek-ai/dsh)此前因「一切皆插件」的架构受到不少关注,本站也拆解过它的插件设计与 goal 机制。这篇来看更底层的问题:用户发出一条消息之后,框架内部到底按什么顺序把一轮任务跑完。素材来自仓库 docs 目录下的官方文档 agent-lifecycle(MIT 协议),它以一张时序图配合架构文档,完整描述了轮次(turn)与步骤(step)的生命周期。
先分清轮次与步骤
文档给了两个定义:一个步骤是一次模型请求加上它调用的工具;一个轮次包含零个或多个步骤,在领取首条输入之前打开,在不再欠下任何工作时关闭。
用户调用 followup(content) 提交内容后,消息先进 inbox:agent/inbox/spliced 与 agent/inbox/inserted 两个事件先向 UI 或 SDK 广播队列变化,排队的任务随后唤醒驱动器 Driver,agent/status 切到 running,然后落下一个持久的 turn/start 事件。

驱动器接着做一次认领:取走挂起的 next-step 输入,外加一条排队的 prompt,每条消息各发一个 agent/inbox/claimed。并非所有消息都会立刻唤醒驱动器,注入的上下文会留在 inbox 里,直到另一条消息把它捎带唤醒。
开工前的闸门:pre-step 瀑布
正式请求模型之前,驱动器先发出 agent/pre-step 瀑布事件。它是 waterfall(瀑布式)事件,监听器必须调用 next() 才把消息交给下游;文档强调以返回的决策为准,包装 next() 的监听器会保留下游消息,除非有意替换。监听器可以改写已认领的消息,也可以直接拒绝。被拒绝时,已认领的批次保持移除状态,打开的轮次不消耗任何步骤就关闭,但这次尝试仍会作为持久轮次留在日志里。steering(中途引导)和注入的上下文,会在后续认领取得下一步骤批次后经过同一道闸门。
一步之内:拼提示词、流式回包、记录用量
通过闸门后是 step/start,被接受的消息逐条落为 user/message。随后 system-prompt/assemble 瀑布把插件注册的提示词片段与工具 schema 组装成请求,agent/request 与 llm/stream 两道瀑布把它交给模型提供方,返回的 StreamChunk 逐个记为 assistant/chunk,并经 session/event 广播给监听方。
值得注意的是 assistant/message:它会记录每一次成功的提供方调用,包括返回空内容或以 max-tokens 结束的调用。空内容不会进入派生历史,但这条持久事件仍保留用量,并通过 sourceEventSeqs 精确列出对应的 assistant/chunk 事件,包括显式空列表。对做计费与审计的人来说,这个细节相当实用。
工具执行:barriers 与有界滚动池
拿到模型回复后,驱动器按 executionMode 给挂起的工具调用分类,然后在 barriers 与有界滚动池构成的循环里执行,调用启动前还会重新分类。每个调用依次经历 tool/call、ordered pre(有序前置)、并发 execute、ordered post(有序后置),最后落 tool/result。需要排队把关的前后置与有界并发的真正执行被拆到两段,这是它工具流水线的核心取舍。
失败怎么办:压缩与重试轮次
如果模型请求以最终适配器错误或流内终止性错误告终,驱动器先落 step/end,再发 agent/request-error 瀑布,由监听器返回重试动作或保留原始错误。上下文压缩则由 dsh-compaction-basic 承担:它在派生请求之前经 agent/pre-step 处理压力,agent/request-error 只用于规范的上下文溢出。任一条件触发后,系统先做可选的工具结果剪枝,再选择摘要。恢复发生在失败步骤结束之后、失败轮次结束之前;只有当剪枝或摘要推进了 surface replacement generation,才会开启一个全新的重试轮次,否则仍以原始请求错误为准。

收尾与两套事件总线
自然停止且 next-step inbox 已空时,驱动器发出 agent/turn-stopping 串行检查点;它是 serial 事件,没有 next()。若此时还有挂起的 next-step 输入,驱动器就地认领,再走一遍 pre-step,把轮次续成下一个步骤。全部欠账结清后,turn/end 落库,agent/status 回到 idle。
这套流程里其实跑着两条事件总线:session/event 携带 turn/、step/、user/message、assistant/、tool/ 等持久事实,重载后仍然存在,需要可回放 transcript(文本记录)的 SDK 用户应当消费它;agent/* 则是实时协调接口,负责队列与状态、提示词拦截、请求构造、steering、续跑和错误处理。架构文档还给出一条不变量:模型可见即已记录,凡抵达模型请求的输入都必须能从会话日志重建。
想给 agent 框架做分层设计的团队,这两条事件线的切分方式值得借鉴。需要留意的是,deepseek-harness 目前仍处于开发者预览阶段,各事件的精确签名以仓库生成的 Cordis API 目录为准,迭代较快。



