多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

DeepSeek Harness 快照测试:fork 子代理重放的 seedLength 边界与混合 spawn+fork 场景

DeepSeek Harness 快照测试:fork 子代理重放的 seedLength 边界与混合 spawn+fork 场景 DeepSeek Harness 快照测试fork 子代理重放的 seedLength 边界与混合 spawnfork 场景【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness导读本文聚焦 DeepSeek Harnessdeepseek-ai/dsh-*插件化 Agent 框架快照测试体系中的一个关键细节fork继承父会话上下文的子代理与 spawn全新会话的子代理在完整转录快照重放中的路由差异。原 Agent Note 2026-06-22-fork-snapshot-scenarios.md 记录了两个新增场景的动机与决策subagent-fork-in-process与subagent-mixed。读完本文你将理解seedLength边界为何是 fork 子代理重放正确的基石、为什么父代理必须完成一个回合才能测试到该边界以及如何在默认门禁default gate中以无 key 方式重放这两个场景并掌握重新录制快照的具体命令。背景fork 与 spawn 的本质差异DeepSeek Harness 为 Agent 之间的委派提供了两类进程内in-process子代理后端二者在快照重放中行为完全不同后端委派工具子会话形态子日志中的seedLengthsubagent-spawn-in-processsubagent全新会话从零开始缺省等价 0subagent-fork-in-processsubagent_fork以父会话已平衡完成的回合前缀作为种子非零 继承的事件条数两者在 packages/bundle/base/cordis.patch.yml 中注册subagent-spawn-in-process以providerName: spawn挂载subagent-fork-in-process以providerName: fork挂载对应的委派工具名为subagent与subagent_fork。fork 后端的核心实现在 packages/subagent/subagent-fork-in-process/src/index.tscompletedTurnPrefix取父会话日志中**最后一个turn/end之前含**的全部事件作为种子——正在进行的工具调用回合是不平衡的不能作为合法的子会话种子若父会话一个回合都未完成则种子为空等价于全新 spawn。问题fork 子代理的重放曾存在静默错路由在新增 fork 场景之前快照测试体系存在一个真实缺口完整转录快照层full-transcript snapshot tier——唯一会真实启动acp-agent并端到端重放嵌套转录的测试层——只有 spawn 型子代理场景subagent-spawn、subagent-multifork 路由逻辑仅由llm-replay的单元测试一个手工合成的子 fixture和持久化往返测试覆盖因此一个让单元测试全绿、却在完整转录层逃逸的 fork 路由回归正是快照层本该捕获、却因缺少真实 fork 场景而无法捕获的 bug 类别。根本原因在于子代理脚本的推导方式llm-replay通过deriveReplayScript把子会话日志按(turn, step)分组为每个stream()调用一个重放条目。对 spawn 子代理其日志只含自己的模型调用推导天然正确但对 fork 子代理其.jsonl以父会话的事件开头包括父会话的assistant/chunk随后才是子会话自己的回合——若直接对整个日志推导父会话记录的响应会被当成子代理的模型调用重放出去造成静默错路由。决策一持久化seedLength边界修复方案在配套笔记 2026-06-22-fork-child-replay-seed-boundary.md 中记录核心是显式优于隐式会话头新增seedLengthSessionHeader增加可选seedLength: number表示该会话有多少条前导事件是继承而来而非本会话产生。fork 后端创建子会话时盖章 种子前缀长度spawn 则缺省≡ 0。它经CreateSessionOptions.meta传递并在SessionStore.prepare中写入。seedLength必须是显式的绝不从seed.length推断重建resume/load路径会用整个存储日志重新播种此时seed.length是完整长度而非原始边界因此 resume 路径从已加载的头中把持久化的seedLength原样传回与createdAt的保留方式一致。两个持久化后端都往返保存JSONL会话头行的seedLength字段见 packages/session/session-persistence-jsonl/src/format.ts 的toHeaderLine/fromHeaderLineSQLitesessions表的seed_length列随seed_length/source_event_seqs/surface_op一起构成 schema 版本 4预发布策略下任何非当前user_version一律拒绝打开、不做迁移。重放从边界之后推导子脚本llm-replay的parseSessionHeader读取seedLength缺省 0loadSessionScripts对子 fixture 执行parseSessionLog(text).slice(header.seedLength)——即只取边界处及之后的事件、只重放子会话自己的模型调用见 packages/test-support/llm-replay/src/index.ts。spawn 子代理seedLength为 0slice 是空操作因此既有 spawn 场景字节级不变。决策二录制两个真实 fork 场景原笔记最终录制了两个针对真实 API 的场景且都在默认门禁中无 keykeyless重放subagent-fork-in-process聚焦回归父代理先完成一个回合确立一个事实再通过subagent_fork委派一个子任务。fork 子代理继承对话其日志携带非零seedLength因而能直接从父上下文作答。这是聚焦的回归用例子 fixture 的seedLength是重放切片依赖的边界且来自真实 fork 而非手工合成。对应 fixture 位于 snapshots/sdk/subagent-fork-in-process/session.jsonlsession.1.jsonl两个子日志 snapshot.yml。subagent-mixed混合 spawnfork父代理完成一个回合后在同一转录中先后委派一次subagent全新 spawn 子代理seedLength0和一次subagent_forkfork 子代理非零seedLength。这是 seed-boundary 与 per-session-replay 笔记都点名未来要补的混合场景一个转录同时覆盖两种传输方式、两条切片分支seedLength0 空操作seedLength 0 裁剪继承前缀且两个子代理按createdAt排序为 spawn 在前、fork 在后。对应 fixture 位于 snapshots/sdk/subagent-mixed/session.jsonl父日志 session.1.jsonl/session.2.jsonl两个子日志。从子日志头可见实际形态fork 子会话头携带parentSession与seedLength:39而 spawn 子会话没有这两个字段。两个场景的snapshot.yml均为version: 1、profile: sdk、composition: default、recording: live。为什么必须完成 turn-1fork 后端只把父会话已平衡完成的回合前缀作为种子。若父代理在第一个回合就 fork则没有任何已完成的回合可继承种子为空≡ 全新 spawnseedLength0——这测不到切片逻辑。因此两个场景都使用双提示输入第一个提示完成一个回合确立一个暗号codeword供子代理稍后被要求回忆如 fixture 中Remember this fact for later: the project codeword is SAFFRON. Reply with the single word OK and stop.第二个提示发起 fork 委派。子代理转录中回忆出的暗号只是模型的自然行为承载验证功能的产物是子 fixture 中记录的seedLength——它才是重放切片真正消费的边界。快照基础设施如何支撑 fork 场景两个进程内后端早已接入cordis.yml/cordis.snapshot.yml作为两个模型可见工具暴露subagent→ spawnsubagent_fork→ forkharness 采集每个子日志重放按seedLength键控的每子代理 fixture。缺的只是驱动一个 fork 子代理走过全流程的录制场景。装配细节packages/bundle/base/cordis.patch.yml 声明subagent-spawn-in-process、subagent-fork-in-processproviderName: fork与tool-subagent-forkprovider: fork、toolName: subagent_forkllm-replay插件通过Config.file$DSH_SNAPSHOT_FILE、Config.overrideFile$DSH_SNAPSHOT_OVERRIDE、Config.childFiles$DSH_SNAPSHOT_CHILD_FILES路径分隔符列表解析 fixture 路径见 packages/test-support/llm-replay/src/index.ts重放按首次调用的活会话顺序绑定父/子脚本installLlmReplay以createdAt排序[primary, ...children]新出现的活会话认领下一个未绑定的脚本assertConsumed()在 teardown 时把录制脚本从未绑定脚本未消费完变成明确诊断packages/test-support/llm-replay/src/index.ts。影响与验证护栏真的咬住了原笔记记录的 Consequences 已由实现证实守卫上移fork 路由切片现在受完整转录层守护而非仅单元测试。移除slice(seedLength)重放整个子日志会让两个新场景同时变红——fork 子代理会收到父会话记录的 chunk 而非自己的——证明护栏有效场景落地时验证过红→绿。这是本笔记最有说服力的验证点guard 是可证咬人的不是纸面声明。首个双后端场景subagent-mixed是第一个在单个转录中驱动两个不同子代理后端、同时跨 spawn 与 fork 子代理验证 per-session 重放键控的快照场景。边界明确进程外ACP子代理重放形态不同每个子代理是独立进程、独立重放仍由TODO(acp-subagent-replay)跟踪——这两个场景仅限进程内。可重录pnpm run test:snapshot:record会从实时 API 重新生成全部四个 fork/spawn fixture与其他录制场景一致两个新场景在没有 key 时会自跳过self-skip。实操录制与重放仓库根目录 package.json 中的相关脚本# 从实时 API 重新录制全部快照 fixture含本笔记的 fork/spawn 四个 fixture pnpm run test:snapshot:record # 以重放模式跑快照门禁 pnpm run test:snapshot:refreshtest:snapshot:recordDSH_SNAPSHOTrecord vitest run --config vitest.snapshot.config.ts --updatetest:snapshot:refreshDSH_SNAPSHOTrefresh vitest run --config vitest.snapshot.config.ts。录制出的产物即 snapshots/sdk/subagent-fork-in-process/ 与 snapshots/sdk/subagent-mixed/ 下的父日志、子日志与snapshot.yml可在任何无 LLM key 的环境中重放验证。延伸阅读种子边界机制的完整设计2026-06-22-fork-child-replay-seed-boundary.mdper-session 子代理快照重放的提出2026-06-22-subagent-snapshot-replay.mdfork 后端子代理实现packages/subagent/subagent-fork-in-process/src/index.ts重放插件实现packages/test-support/llm-replay/src/index.ts子代理子系统总览docs/subsystems/subagent.md【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表