ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DeepSeek Harness 任务模型拆解:Subagent、Job、Goal 为何要分三层对象

DeepSeek Harness 任务模型拆解:Subagent、Job、Goal 为何要分三层对象 1. 为什么“都叫任务”反而最容易写错DeepSeek Harness 里 Subagent、Job、Goal 三个对象名字翻译过来都像“任务”但它们在源码里是三条完全不同的生命周期。我见过太多人在多智能体编排里把它们压成一个通用 Task 结构接口一开始很整齐都有 id、status、result、cancel。等到恢复、权限和失败处理真正接进来问题才暴露——谁拥有对话历史谁只保存执行状态谁可以继续下一轮取消后保留什么完成到底代表什么。这篇面向正在做多智能体编排的开发者交付一套可复制的任务对象定义骨架和分层校验动作。核心检索词先摆出来DeepSeek Harness 的 Subagent 负责委派子 AgentJob 负责后台执行生命周期Goal 负责同一 Session 的目标与续跑策略。适合谁适合已经在写 Agent 调度、被“异步任务”这个词带偏、取消和恢复总是对不上账的人。最容易误导设计的词是“异步任务”。异步只描述调用者是否等待不说明被管理的对象是什么。所以先看对象再看异步。你可以先问三个问题是否需要另一个 Agent 拥有自己的模型回合、工具范围或对话上下文是否需要把一段执行暴露为可列出、读取、等待、终止的后台生命周期是否需要当前 Agent 在同一个 Session 中围绕一个目标继续多轮并保留目标阶段和轮次预算答案分别指向 Subagent、Job 和 Goal。2. 三层对象职责边界与 TaoToken 前置准备在动手写配置之前先把三者的“完成”语义对齐这是后面所有校验的基准。对象稳定身份主要状态典型完成含义Subagentchild session 或 provider run对话、Activation、父子关系、输出一次委派结束或子 Activation 结算Jobkind-N JobIdrunning / stopping / completed / killed / failed生产者释放资源并提交终态GoalGoalId revisionactive / paused / blocked / complete目标被显式标记完成这张表的关键不在字段数量而在“完成”的语义完全不同。Subagent 完成一次工作不等于上层目标完成Job 结束只能证明这段生产者生命周期结算Goal complete 则是对目标状态的显式判断不是某个进程自然退出。如果你要在本地跑通这套编排并调用模型做验证需要一个稳定的模型接入点。我这边用 TaoToken 做统一入口官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。它在这里的角色很简单给 Subagent 的独立模型回合、Goal 的续跑轮次提供同一个可调用的模型通道省得每个子 Agent 各配一套 key。前置准备分三步。第一步在控制台创建 API Key入口是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。第二步把 key 写进环境变量不要硬编码进仓库。第三步确认你的 Harness 配置里模型 base_url 指向 https://taotoken.net/api 模型名按你实际开通的填。export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api注意API 端点不要加 UTM 参数只有官网和 deep link 才带。key 只放环境变量别提交到 git。3. 可复制的任务对象定义骨架下面这份骨架是 TypeScript 风格重点不是能直接编译而是把三者的字段边界钉死。你可以照着改成自己项目的 schema。3.1 Subagent 定义骨架Subagent 的核心资产是子 Session、Agent 能力和父子关系。它有两种生命周期别混。// One-shot一次前台委派消费者等待一个结果最后 dispose interface OneShotSubagentRun { kind: subagent.one_shot; runId: string; childSessionId: string; provider: string; toolScope: string[]; // 没有 steering不能 resume result: PromiseSubagentResult; dispose(): Promisevoid; } // Continuable持久子 Session可经历多个 process-local Activation interface ContinuableSubagent { kind: subagent.continuable; childSessionId: string; inbox: FIFOQueueFollowUpMessage; // 子 Agent 自己唯一的 FIFO interrupt(): Promisevoid; // 只取消当前 turn coldResume(): Promisevoid; // 无活跃 Activation 时从持久 Session 恢复 }选用条件需要独立模型回合或不同 Provider需要给子 Agent 单独限制工具、persona、输出 Schema 或委派深度需要父子 Session、子孙枚举、继续发消息或显式报告。它不自动回答后台控制问题——若产品还要向模型或 UI 暴露统一的后台 list/read/wait/kill那是 Job 的职责。3.2 Job 定义骨架Job 是 long-running producer 与ctx.jobs之间的共享 Runtime不要求生产者是 Agent。JobKindMap的固定条目已经给出两个例子bash 与 subagent。interface JobStart { kind: bash | subagent | string; label: string; owner?: string; // Owner Session用于访问控制 outputLimit?: number; run(): JobHooks; } interface JobHooks { cancel(): Promisevoid; // 请求生产者停止同步且幂等 done: Promisecompleted | killed | failed; // 生产者释放资源后给终态 readOutput?(): AsyncIterablestring; // 可选消费增量输出 }Registry 自己负责 JobId、权限、状态、等待者、监听器和 first-wins settlement。Owner 或 service dispose 时会请求取消并等待合规生产者释放资源。以下需求优先选 Job命令、下载、索引、构建或一次 Subagent 委派需要在后台运行调用者要先拿到 id之后再 list、read、wait 或 kill生产者资源释放与业务工作结束必须区分。3.3 Goal 定义骨架Goal 的对象不是子 Agent也不是某段后台执行而是同一 Session 的人类目标。持久 phase 与 process-local activation 要分开。interface Goal { goalId: string; revision: number; // compare-and-set每次持久变更递增 phase: active | paused | blocked | complete; roundsStarted: number; // 只有被 continuation consumer 接纳的轮次才增加 continuationPolicy: { maxRounds: number; shouldContinue(ctx: GoalContext): boolean; }; }每次目标变更写入goal/changeSession Event。回放会拒绝轮次缺口、旧 revision、停止状态和预算越界。Goal 不创建工作线程不运行命令也不天然委派子 Agent——它只是给同一 Agent 的后续轮次提供目标域和持久状态。4. 分层校验验证请求与成功结果骨架写完后用一组校验动作确认三层没有互相吞掉。下面这段是校验脚本的核心逻辑跑通它比读十遍文档管用。async function validateLayering(ctx: HarnessContext) { // 校验 1Goal 触发 JobJob 包装 Subagent三者身份独立 const goal await ctx.goals.create({ objective: 完成一份可发布的技术调研, continuationPolicy: { maxRounds: 8, shouldContinue: () true }, }); const job await ctx.jobs.start({ kind: subagent, label: 调研委派, owner: ctx.session.id, run: () { const sub ctx.subagents.spawnContinuable({ provider: taotoken, toolScope: [search, read], }); return { cancel: () sub.interrupt(), done: sub.waitSettlement().then(() completed as const), readOutput: () sub.outputStream(), }; }, }); // 校验 2Job 拿到 id 后可 list/read/wait const listed await ctx.jobs.list({ owner: ctx.session.id }); console.assert(listed.some((j) j.id job.id), Job 应可列出); // 校验 3Goal 的 revision 在变更后递增 const before goal.revision; await ctx.goals.update(goal.goalId, { phase: blocked }); const after (await ctx.goals.get(goal.goalId)).revision; console.assert(after before, Goal revision 应递增); // 校验 4Subagent 完成不等于 Goal 完成 await job.done; const finalGoal await ctx.goals.get(goal.goalId); console.assert(finalGoal.phase ! complete, 委派结束不应自动完成目标); }成功结果长这样Job 能按 owner 列出Goal revision 单调递增Subagent 结算后 Goal 仍停在 active 或 blocked。如果校验 4 失败说明你把 Subagent 的 result 直接当成了 Goal 的完成信号这是最常见的偷换状态。模型侧验证可以用模型对话入口快速确认通道正常 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你要长期跑编码类 AgentCoding Plan 更适合 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。5. 本篇常见错排查5.1 取消语义混用把三者混成 Task最先出错的通常是 cancel。Subagent one-shot 的 caller signal 同时覆盖启动前与发布后剩余 turn work消费者 dispose Run 以取消残余并达到 quiescence。Continuable Subagent 的 follow-up 在 inbox 接受后便脱离 caller signalinterrupt()只取消当前 turn并保留未领取 inbox 和已发布 descendants。Job kill 调用 producer 的同步、幂等 cancel然后状态进入 stopping最终由 done 给出终态。等待操作自己的 signal 只取消等待不取消 Job。Goal pause/block/complete 改变的是目标 phase 并解除自动 continuation它们不是进程信号。disarm()甚至只移除 process-local continuation authority不改变持久 phase 或 revision。所以 API 不应提供一个含糊的task.cancel()然后由调用者猜测它意味着取消当前模型 Turn、终止后台生产者、丢弃排队消息还是暂停长期目标。5.2 把“可恢复”当成同一种Continuable Subagent 的 durable child Session 和 descriptor 可以支持 cold resumeprocess-local Activation 会重建。Goal 的 snapshot、revision、phase 与 roundsStarted 由 owning Session log 中的goal/change和被接纳的消息事件折叠出来。固定 LocalJobRegistry 是 process-local ProviderJobs 文档保证 Registry 生命周期、owner cleanup 与 settlement不等于跨进程 durable queue。因此不能得出“所有后台 Job 重启后自动恢复”的结论也不能得出“子 Agent Session 已 flush 就一定由某个持久化后端保存成功”。Subagent 文档明确写到最终 flush 的 participation boolean 不能证明任意 persistence backend 已经落盘失败会记录但 Activation 仍会释放。5.3 组合时偷换状态一个实际流程可以同时出现三者Goal 是“完成一份可发布的技术调研”下面挂 Job bash-1 跑仓库测试挂 Job subagent-2 包装一次可读取的委派subagent-2 里再放一个拥有自己模型回合的 Subagent当前 Session 下一轮根据结果继续、阻塞或完成。组合时最重要的是不要偷换状态Subagent 返回 completed只能说明这次委派结束父 Goal 仍应检查结果是否满足验收Job killed 只说明 Registry 接受了终止并最终得到 killed若生产者取消实现不合规不能假设外部副作用已经停止Goal blocked 不应自动 kill 所有 Job阻塞可能是在等待人工输入后台只读收集仍可继续Goal complete 也不等于所有子资源已释放资源结算仍应等待 Job done 或 Subagent Activation disposal。5.4 接入报错排查如果 Subagent 的模型回合报 401先查 key 是否写进环境变量、base_url 是否指向 https://taotoken.net/api 。如果 Job 一直停在 stopping检查 producer 的 cancel 是否真的幂等且会释放资源——Registry 会等合规生产者释放不合规就会卡住。如果 Goal 回放报 revision 冲突说明有旧 revision 的写入没被拒绝检查 compare-and-set 是否漏了。6. 继续接入与验证入口排障和接入相关的操作统一从 API Keys 和接入文档走创建 key 在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 接入说明在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。验证模型通道是否正常用模型对话入口最快 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。长期跑编码或 Agent 编排Coding Plan 更省心 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。最后留一个我踩过的坑别急着把三个对象合并成“更优雅”的 Task。先确定“需要保存什么事实”再决定接口。需要对话历史就不能只存 Job output需要后台资源状态就不能只存 Goal phase需要长期目标就不能拿 Subagent 的一次 result 代替。三种状态保持正交再用显式策略连接——哪一个 Goal 触发了哪一个 Job哪个 Job 包装了哪次 Subagent 委派什么证据允许 Goal complete阻塞或取消时分别处理哪些执行对象。这样每一种完成、取消和恢复都有可以核对的语义。
返回列表