恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
DeepSeek Harness 子代理生命周期观测增强:为 subagent/end 补充 lastAssistantMessage
首页
资讯中心
/
DeepSeek Harness 子代理生命周期观测增强:为 subagent/end 补充 lastAssistantMessage
DeepSeek Harness 子代理生命周期观测增强:为 subagent/end 补充 lastAssistantMessage
发布时间:2026/9/18 12:36:43
DeepSeek Harness 子代理生命周期观测增强为 subagent/end 补充 lastAssistantMessage【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness导读DeepSeek Harness 的 subagent子代理能力缝capability seam允许 Agent 将工作委托给子代理并通过subagent/start/subagent/end生命周期事件向插件暴露运行过程。本文基于仓库中的 Agent Note 2026-06-30-subagent-observe-enrich.md 展开讲解一次已落地的观测性增强在结束事件SubagentRunEndInfo负载中新增lastAssistantMessage字段使 hooks 桥接或原生插件无需持有运行句柄即可获知子代理的最终输出。读完本文你将理解该字段的选取规则、可选optional语义、事件时序以及为何这次改动刻意保持 observe-only纯观测、无控制流变更。背景hooks 桥接需要知道子代理产出了什么DeepSeek Harness 的 hooks 子系统提供拦截缝interception seams让插件能够在 Agent 生命周期的关键节点进行观测与门控gate。作为行业参照Claude Code 与 Codex 都暴露了SubagentStart / SubagentStop钩子其中 Claude Code 的 SubagentStop 会携带子代理的最终消息final message。Harness 本身早已通过ctx.subagents服务发射subagent/start与subagent/end生命周期事件详见 subagent 子系统文档 与 事件类型源码。但问题在于这两个事件的负载此前过于单薄——subagent/start仅携带runId、provider、id、localsubagent/end在此基础上只多了stopReason。对于一个 hooks 桥接层而言这些信息不足以向外部如 Claude Code 风格的 SubagentStop 处理器报告这个子代理到底产出了什么想获取产出桥接层不得不另行触达尚在运行中的 run 对象这不仅增加耦合也让生命周期事件本身的信息价值大打折扣。设计决策向 SubagentRunEndInfo 增加 lastAssistantMessage该 Agent Note 的决策非常收敛——在SubagentRunEndInfo上新增lastAssistantMessage字段承载子代理的最终输出。当前仓库源码中的类型定义印证了这一决策packages/subagent/subagent/src/types.tsexport interface SubagentRunEndInfo { /** Unique identity shared with the paired start event. */ readonly runId: SubagentRunId /** The same provider name carried by the paired start event. */ readonly provider: string /** The child agents id. */ readonly id: SessionId /** Snapshot of whether SubagentRun.localAgent was present when start fulfilled. */ readonly local: boolean /** The terminal stop reason. */ readonly stopReason: SubagentResult[stopReason] /** * The childs final assistant output, selected by the same rule as * {link SubagentResult.output}; absent on infrastructure rejection or when * the child produced none. */ readonly lastAssistantMessage?: ContentBlock[] }字段语义正常收尾与基础设施失败两种路径正常收尾路径子代理运行结算settle时lastAssistantMessage就是只读的、带类型的SubagentResult.output。观测者不持有 run也能看到子代理产出的内容。基础设施拒绝路径当一次运行因为基础设施故障如传输层崩溃失败、根本没有产生SubagentResult时该字段不出现事件以stopReason: error报告结局。无产出路径子代理没有产出任何 assistant 内容时字段同样被省略见下方源码中result.output.length 0 ? {} : { lastAssistantMessage: result.output }的处理。借用不可变负载契约SubagentRunEndInfo的所有字段均为readonlylastAssistantMessage的元素类型ContentBlock[]同样按不可变数据对待。该契约建立在提供方与监听方都是可信的同进程协作者这一前提之上负载是借用的borrowed不可变数据监听者不得修改或长期持有可变引用。事件模型保持不变仍是纯 emit、严格 observe-only这次增强刻意不动事件模型。subagent/end依然是一个普通的emit而非拦截缝常用的 waterfall 模式并且只用于观测不具备任何控制流能力。从源码packages/subagent/subagent/src/lifecycle.ts看一次 one-shot 运行的生命周期对是这样发布的export function observeRun( emit: LifecycleEmitter, provider: string, parent: Agent, run: SubagentRun, ): SubagentRun { const identity { runId: SubagentRunId(randomUUID()), provider, id: run.id, local: run.localAgent ! undefined, } // Attach the terminal observer before dispatching start. void run.result.then( (result) { emit(subagent/end, { ...identity, stopReason: result.stopReason, ...result.output.length 0 ? {} : { lastAssistantMessage: result.output }, }, parent) }, () { emit(subagent/end, { ...identity, stopReason: error }, parent) }, ) emit(subagent/start, identity, parent) return run }事件时序的三个关键性质先挂接结束观察者再同步发射 startobserveRun在派发subagent/start之前就把run.result的 then 回调注册好了。由于 Promise 反应回调必然在同步的 start 发射之后才执行因此保证了start → end的严格先后顺序。start 之后即可解析子代理异步的SubagentService.start()将结果观察挂接到已就绪的 provider run 上、发射subagent/start随后才返回 run。因此进程内监听器可以在subagent/start通知期间通过ctx.agents.get(info.id)拿到已发布的子代理而远程 provider如 ACP、Claude Code 后端则不需要在本地注册表中有对应条目订阅者不应假设ctx.agents.get必然命中。被拒绝的 start 不发射任何事件如果 provider 的start()被拒绝如能力不支持、初始化失败说明从未产生被接受的 run此时既无subagent/start也无subagent/end生命周期事件对保持完整。监听器隔离per-listener containment生命周期发射器对每个监听器做独立异常隔离createLifecycleEmitter某个回调同步抛错或返回 rejected promise都会被记录日志而不会拖垮同级监听器、改变运行状态或破坏后续派发。一个坏订阅者既不会让活跃的 run 悬挂也不会饿死后面的监听器。备选方案与取舍为什么只加这一个字段Agent Note 记录了评审过程中被放弃的备选方案理解这些取舍有助于把握本次增强的边界。agentType 子代理类别标签被放弃早期草案曾计划在请求与两个生命周期负载上同时增加agentType标签对标 Claude Code 的subagent_type。该方案在评审中被放弃原因很干脆agent_type是 Claude Code 的概念并不适配 Harness 自己的缝——当前仓库没有任何代码解释这个字段唯一可能的消费者只是 CC 方言桥接层。因此CC 桥接在转发 SubagentStart/Stop 时直接为agent_type匹配器填入 Claude Code 自己的默认值general-purpose而 Harness 侧只保留一个增强lastAssistantMessage。控制流 subagent/end被推迟另一种让 end 返回停/继续决策的控制流方案被明确推迟详见下节。为什么是 observe-only控制流版本需要什么一个控制流subagent/end像其他拦截缝那样await 一个返回 stop/continue 决策的瀑布流并非小改它需要三件事把subagent/end从 emit 重塑为 waterfall拦截缝的 await 模式重构SubagentService.start在结算settle之前 await 所有监听器在进程内 provider 中实现resume能力让continue决策能真正重跑子代理。这属于背景/转向background/steering子代理重构的范畴——正是能力缝 Agent Note 中已经推迟的同一个重构方向该重构还会统一 subagent 与 bash 对长时运行工具的处理。因此本次增强只交付 hooks 桥接今天需要的观测部分源码中以FIXME(subagent-continuation)/TODO锚点标记了控制流版本将来可能的落点。后果与消费方式hooks 桥接如何受益订阅现有 emit 即可转发最终输出一个 hooks 桥接层或原生插件现在可以直接订阅既有的subagent/end把lastAssistantMessage转发给外部的 SubagentStop 处理器不需要新增任何控制流接口。典型消费路径订阅subagent/end用runId与subagent/start配对同一对事件的runId相同读取stopReason判断子代理如何收尾若存在lastAssistantMessage即为子代理的最终输出可转发到 Claude Code / Codex 风格的 SubagentStop 处理器若字段缺失且stopReason error说明是基础设施失败无产出可报告。事件契约与文档同步词汇表变更已同步到 subagent 子系统文档 的事件说明subagent/start/subagent/end两节见 subagent/end — emit 与 subagent/start — emit事件目录catalog随之重新生成。需要说明的是原 Agent Note 提到同步位置为docs/core-data-structures/subagent.md当前仓库中该文档位于docs/subsystems/subagent.md以当前实际路径为准。无生产行为变化事件触发的时机与之前完全一致只是 end 负载多了一个可选字段因此无需任何快照snapshot或端到端e2e测试变更。这体现了可选字段向后兼容的设计旧监听器忽略新字段照常工作新监听器依赖新字段也不会破坏旧路径。源码印证最终输出的选取规则与不变式校验最终输出选取规则lastAssistantMessage与SubagentResult.output共享同一条选取规则packages/subagent/subagent/src/assistant-output.ts优先取最后一条非空 assistant 消息空内容消息包括仅含 usage 的消息会被跳过若没有任何非空消息则回退到累积的 assistant 文本流两者皆无时返回undefined事件中即省略该字段。finalAssistantOutput会以增量折叠fold方式遍历事件AssistantOutputFold支持按会话事件push或按文本流pushText两种输入供不同传输会话事件后端 vs ACP 内容块复用同一条规则。可续子代理continuable同样适用同一语义也适用于可续子代理的 Activation 驻留时段createActivationObserver的settle()在派发subagent/end时做同样的字段省略处理output undefined ? {} : { lastAssistantMessage: output }。冷恢复cold resume会被当作一个带新runId的新 epoch观测者看到的是与 one-shot run 完全一致的词汇表。不变式校验与测试事件配对与字段合法性由不变式校验器守护packages/subagent/subagent/src/invariant.tssubagent/end必须与已发射的subagent/start通过runId配对且身份字段provider、id不得漂移。对应测试位于 packages/subagent/subagent/tests/invariant.spec.ts覆盖了接受合法的 run 生命周期对、拒绝畸形/未配对的转换以及注册结束后仍接受已记录的历史 provider 名等场景同时验证了subagent/end负载构造含stopReason: completed默认值与覆盖项的合法性。小结lastAssistantMessage是 DeepSeek Harness 子代理生命周期缝的一次小而克制的观测性增强它以可选字段的形式让subagent/end事件真正能够回答子代理产出了什么从而支撑 hooks 桥接与 Claude Code / Codex 方言适配同时严格守住 observe-only 边界——不引入控制流、不改变事件时机、不需要快照更新。对于想要基于 subagent 生命周期构建观测、上报或外部适配的插件开发者而言理解这条缝的词汇表与边界是正确接入的第一步。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考