Files
deepseek-harness/packages/jobs/jobs-local/README.zh.md
T

10 KiB
Raw Blame History

description, kind
description kind
进程本地后台任务注册表,供组合、容量评估或排查进程内任务的用户与维护者阅读:按所有者的准入、生命周期与销毁。 package-reference

@deepseek-ai/dsh-jobs-local

English | 中文

概述

dsh-jobs-local 在 agent(智能体)继续推进时,于 harness 进程内运行后台任务。拥有任务的 agent 可以读取、等待、列出和取消任务;同时挂载 dsh-tool-jobs 时,还会收到会话内完成通知。可配置的并发与输出保留上限约束资源使用。生产方可以提供供定期读取的输出,也可以直接追加;用户可以观察保留的输出,而不消耗 agent 尚未读取的内容。任务在拥有者或 harness 关闭时结束。

目录


使用本包

当组合需要进程内后台任务时加载本插件:长时间运行的工具注册其工作,拥有它的 agent 在不阻塞自身轮次的情况下读取、等待、列出和取消。它实现 dsh-jobs 约定;模型侧的 job_output、job_list 与 job_kill 工具来自 dsh-tool-jobs。

何时选择

当任务应存活于 harness 进程内、并随进程终止时选择它。当工作必须跨重启存活或跨进程存在时避免它:记录保存在内存中,持久或跨进程后端必须以不同方式实现同一约定。

最小配置

加载插件即注册 ctx.jobs;每个字段都是可选的。

- name: '@deepseek-ai/dsh-jobs-local'
字段 默认值 含义
maxConcurrentJobsPerOwner 10 每个精确所有者,或共享的无主桶中,running 加 stopping 任务的最大数量
retainBytes 262144 每个任务输出环的运行期保留量,UTF-8 字节
settledRetainBytes 16384 任务结算后保留的环容量,UTF-8 字节;模型尚未读取的字节保留到它的首次终态读取
pumpPollMs 150 任务拉取源的轮询间隔,毫秒

生成的配置目录是每个受支持字段的穷尽式真源。

每个所有者得到什么

上限统计精确所有者的 running 与 stopping 记录;所有无主任务共享另一个独立的服务级桶。终止历史不占用容量,只有生产方的 done 结算才释放一个停止中任务的名额。达到上限时,start() 会在生产方运行前失败,错误会指出上限并告诉 agent 终止一个不需要的任务、等它结束后再重试——注册表既不排队也不抢占。

生命周期

任务属于其所有者和后端,而非生产方工具,因此重载生产方或控制器不会停止任务。拥有任务的 agent 被释放时,其任务会被取消、生产方会被等待、快照会被移除;服务释放对每个剩余任务执行同样的操作。销毁期间抛出的取消会强制失败记录并警告工作可能成为孤立工作,因此销毁永远不会死锁。

Session 时间记录

有所有者的 job 在原始 Session 中记录注册与结算,不依赖模型收集输出。SDK 消费方通过 session.event 收到同样的事件;jobs 参考定义标识、时间戳和生产方结算报告。这些记录通过正常 Session 持久化保留;运行中的 job 和输出环不会被恢复。

生命周期事实在注册或结算提交时同步追加,先于运行期 hub 订阅者和完成等待方的反应。追加失败会记录警告,不放弃执行或阻塞清理;消费方必须将不完整历史保留为不完整。

可能出什么问题

没有服务于所有者的控制器时无法启动工作——加载 dsh-tool-jobs 即附加一个,否则 start() 会以指出它的消息拒绝。返回但始终未结算 done 的生产方取消与缓慢停止无法区分,可能使销毁停滞并持续占用一个容量名额。运行期注册表记录会在 harness 进程退出时消失;已持久化的生命周期事件会保留。


理解实现

实现细节——点击展开

本节解释注册表背后的设计决策,并指出实现它们的代码位置;可观察行为已在使用本包中完整说明。

设计理念

  • 内存记录、全新投影。 LocalJobRegistry 为每个任务保存一份 TrackedJob——生命周期状态、输出环与模型游标——每次调用都投影新的只读视图或块副本;调用方永远拿不到实时状态。
  • 环的保留量有界。 追加超出运行期上限时丢弃最老的保留块(单个超限块保留其 UTF-8 安全尾部);低于保留窗口的读取得到 lossy 结果而非错误。结算时裁剪到结算上限,但绝不低于模型游标尚未消费的字节数,因此在首次 job_output 之前结束的任务会交出运行期上限保留的全部输出;那次终态读取之后才裁剪到结算上限。
  • 按所有者分层,一个进程级注册表。 控制器与 { owners: 'scope' } 订阅归档到注册方所在的 scope(ScopedLayers),读取把全局层与所有者的 scope 链求并集——因此某个 preset 的任务控制绝不会为自身组合未加载任何控制器的 agent 保持 start() 可用,一次带 scope 的结算也只会抵达其所有者所属组合注册的监听器。
  • 启动前先预检。 start() 在调用生产方之前检查控制器服务、spec 有效性、仍存活的所有权与容量,因此拒绝不会留下 job id 或执行资源;注册一旦提交,后续不再有可失败步骤。
  • 结算首次优先,事件最后。 最早的终止结果只记录一次,等待泵的最后一次排干,裁剪环,释放等待方,然后投递一次带逐监听器隔离的 settled 事件,再跟上环的最终 output 信号。
  • 销毁永不死锁。 抛出的取消会强制失败记录并报告可能的孤立工作,而不是让释放停滞。

源码地图

文件 职责
src/index.ts 插件入口:Config schema、LocalJobRegistry、准入、生命周期、销毁
src/events.ts 按 scope 分层的事件路由:{ owner }、{ owners: 'all' } 与 { owners: 'scope' } 订阅
src/ring.ts 每个任务的有界输出环:追加、保留裁剪、按偏移读取
src/pump.ts 注册表拥有的拉取泵:每个任务一个定时器,结算前最后一次排干

scope 分层

attachController 与 { owners: 'scope' } 订阅注册到调用上下文所在的 scope 层;{ owner } 与 { owners: 'all' } 订阅不带 scope。控制器问题(servesOwner)与带 scope 的投递走同一条链:先是全局层,再沿所有者的链逐层。注册是无名 token,因此重复标签仍可独立释放。

准入与结算

activeJobCount 按精确所有者或共享无主桶统计权威记录。settle 只记录一次终止结果(把记录下来的 kill 原因合并进 killed 的 detail),清除进度行,把环裁剪到结算保留量(保留模型游标尚未消费的全部字节),解析每个等待方,然后发出带原因的 settled 与环的最终 output 信号。原因在 JobRegistry.kill 之后为 kill,在所有者或服务取消之后为 teardown,否则为 producer;dsh-tool-jobs 据此跳过没人能读的通知。

销毁

所有者释放(disposeOwned)会取消该所有者的任务、等待其结算、移除其记录,并逐条宣布移除——这是任何逐任务记录都无法表达的可见集变更。服务释放(disposeAll)会取消所有存活任务、等待结算、移除每条记录(逐条宣布移除,让挂载在服务之外的订阅者丢掉它的行),然后分离跨 fiber 的所有者清理 effect。


进一步探索

当包级约定不够用时阅读以下页面。它们从注册表约定逐步进入模型侧控制与设计记录。


模型体验

通过生产方插件与 dsh-tool-jobs 间接影响模型,注册表后端把全部模型渲染委托给它们。

KV Cache 影响

不会直接导致 KV Cache 失效;请求前缀变更由上述消费方负责。

已知限制与延期工作

这些限制说明注册表何时不合适。它们是当前包约束,不是任务积压。

  • 任务只存在于进程本地——运行期执行与输出环会随 harness 进程终止而消失;持久或跨重启执行需要一个单独实现该 seam 的后端。
  • 静默无效的取消可能使销毁停滞并持续占用容量——如果 cancel 返回后始终未结算 done,注册表就无法将其与缓慢停止区分开;该任务会在服务剩余生命周期内持续占用一个桶名额,只有显式抛出异常才能安全地强制标为失败。

开发备注

维护者的工作上下文——点击展开

无。