One host registry serves every composition in the process, so its two service-wide collections answered per-owner questions process-wide. `start()` asked only whether SOME surface was attached, so an agent whose own composition loads no `tool-tasks` could start work it has no tool to collect or stop as soon as any other preset attached one — and the answer changed depending on which sessions happened to be open. `settle()` walked every registered listener, so a task settling without a waiter injected one completion notice per mounted preset into the same owner. Both collections now sit in `ScopedLayers`, the layered-registry primitive `tools` and `skills` already use: a registration files into its registering context's scope, and a read unions the global layer with the owner's scope chain. A surface or listener registered from an unscoped context lands in the global layer and serves every owner, which is exactly the host-plane composition's own controls, so the TUI path is unchanged without a special case. This supersedes the consumer-side filter in the previous commit. That filter produced the right notices but sat in the wrong layer: it left the `start()` gate process-wide, it could not be enforced against a producer that resolves the registry directly, and it made a Consumer carry scope knowledge that the other layered registries keep in the registry. `tool-tasks` is scope-agnostic again and the `dsh-scope` edge moves to `tasks-local`. `start()`'s refusal is now owner-relative, so its model-visible text names the agent rather than the process. The shipped `minimal` preset keeps `enableRunInBackground: false`, no longer as the safety boundary — the registry owns that now — but so an agent that could never collect a task is not offered the parameter at all. Refs #2141
2.8 KiB
@deepseek-ai/dsh-tasks-local
English | 中文
@deepseek-ai/dsh-tasks 注册表约定的进程本地实现:LocalTaskService 把每条记录保存在内存中,按 kind 签发 <kind>-N id,并且只交出全新快照,从不交出实时状态。它没有配置;作为插件加载后即注册为 ctx.tasks。
生命周期
任务属于其所有者和后端,而不是生产方工具 fiber,因此重载生产方或接口不会停止任务。某个所有者的第一个任务会把一个会被等待的 effect 附加到对应 Agent 对象的 scope 上。所有者的 dispose(资源释放)会取消该对象的任务,等待生产方完全停稳,并移除其快照;复用的 agent(智能体)id 或会话 id 无法重定向旧的清理操作。
服务 dispose 会关闭监听器、取消所有存活任务、等待其记录完成,并从仍存活的所有者 scope 中分离 effect。如果销毁期间的取消操作抛出异常,服务会强制将记录标为失败,并警告工作可能成为孤立工作,而不会死锁。取消操作已返回但 done 始终未结算时,系统无法将其与缓慢停止区分开,销毁过程可能因此停滞。
结算遵循首次结算优先原则:最早出现的终止结果(生产方结算、作为 failed 隔离处理的 done 拒绝,或销毁时的强制失败)只记录一次,也只通知监听器一次;各监听器的故障会单独隔离,随后释放等待方。挂起的等待会在监听器运行前把任务标记为已报告,因此呈现完成情况的表层不会重复发出通知。
表层与监听器按注册方所在的 scope 分层,形状与 tools 注册表一致:一次注册归档到其注册上下文的 scope,一次读取则把全局层与所有者的 scope 链求并集。因此一个进程级注册表能逐所有者地回答逐所有者的问题——对自身组合未附加任何表层的所有者,无论其他组合附加了多少,start() 都会拒绝并抛出 background tasks unavailable: no control surface serves this agent (load @deepseek-ai/dsh-tool-tasks in its composition);一次结算也只会抵达其所有者所属组合注册的监听器。
模型体验
通过生产方插件和 dsh-tool-tasks 间接影响;它们会呈现任务 id、输出、状态、取消和完成通知。
KV Cache 影响
不会直接导致 KV Cache 失效;请求前缀变更由上述消费方负责。
已知限制与暂缓事项
- 任务只存在于进程本地:记录会随 harness 进程终止而消失;持久或跨重启执行需要一个单独实现该 seam 的后端。
- 静默无效的取消可能使销毁过程停滞:只有显式抛出异常才能安全地强制标为失败。