DeepSeek Harness学习手册
0/33
阅读路径 / 阶段 4
Chapter 04 · Services & Events

服务、隔离与事件

插件之间如何协作:服务、作用域、事件分发与能力三角色。

4 节 · 约 2 小时 · 前置:阶段 3每节含目标 / 前置 / 要点 / 任务 / 自检
本阶段进度 0 / 4
4.1

服务与依赖:Service 基类

约 35 分钟服务 原文 ↗ 仍然成立
审计依据 ·

Service 基类与声明合并来自教程;内置服务清单以仓库生成文档为准。 查看本节审计记录

学习目标

用 Service 基类提供命名服务,让消费方通过 inject 获得完整类型提示。

前置知识

完成 3.7。

核心要点
  1. 服务是挂在 ctx 上的命名能力;super(ctx, 'metrics') 把它注册为 ctx.metrics。
  2. 消费方声明 inject: ['metrics'],apply 执行时服务已就绪。
  3. 用 declare module '@deepseek-ai/cordis' 合并 Context 类型,获得自动补全与编译期检查。
  4. 必需依赖用 inject;可选依赖用 ctx.get(),可能返回 undefined。
  5. 服务消失时依赖者自动 dispose,服务回来时自动重载。
动手任务

写一个 MetricsService,提供 record(event, value);再写一个消费插件注入它。分别测「服务存在 / 不存在」两种加载结果。

自检
服务名由什么决定?
由 super(ctx, 'metrics') 的第二个参数决定;它同时决定 ctx 上的键与 inject 的名字。
类型提示是怎么来的?
TypeScript 声明合并:在模块声明里给 Context 补上 metrics: MetricsService,编译期就能检查拼写与类型。
进度保存在本机浏览器
4.2

服务隔离、scope 与工具过滤

约 35 分钟隔离 原文 ↗ 仍然成立
审计依据 ·

隔离与 scope 语义来自教程;lineage 字段与 restriction 行为以当前版本为准。 查看本节审计记录

学习目标

用 isolate 让不同插件组看到不同服务实例,并区分 scope、shadowing 与 restriction。

前置知识

完成 4.1。

核心要点
  1. isolate: { shell: true } 配合 group 插件,让不同组各自实例化同一个服务。
  2. scope 按 agent 划分,扁平两层;一个活跃 agent 就是其自身 scope 的 key。
  3. 带作用域的注册不会继承给 subagent;父子关系用 lineage 数据表达,不影响可见性。
  4. shadowing:同 scope 内用同名项替换全局工具/片段,其它 agent 仍看到全局版本。
  5. restriction:tools.restrict 让某个 scope 完全看不见某些全局工具,调用也会被拒绝。
动手任务

配两个 group,各自隔离 shell 并设置不同 timeout(5s / 60s);跑一条会超时的命令,确认两组互不影响。再给一个 scope 加工具 restriction,验证工具不可见。

自检
isolate 和 scope 是一回事吗?
不是。isolate 让同一服务按插件组实例化;scope 是按 agent 划分注册与可见性的单位。
restriction 与「调用时拒绝」有什么区别?
restriction 让工具不进入提示词、调用也无法匹配,相当于不存在;调用时拒绝仍会让模型看到工具并尝试。
进度保存在本机浏览器
4.3

事件系统:五种分发模式

约 35 分钟事件 原文 ↗ 建议补充
审计依据 ·

教程自己指出官方文档把 waterfall 标为「不 await」,但示例普遍 await;建议补充一段明确的版本差异说明。 查看本节审计记录

学习目标

为通知、决策、初始化、处理链、扇出五类场景选对分发模式,并区分两套事件。

前置知识

完成 3.1。

核心要点
  1. emit 广播、bail 短路、serial 顺序、waterfall 流水线、parallel 并行;每个事件只有一种分发模式。
  2. waterfall 监听器必须调用 next() 委托;不调用就是故意短路,可用于拦截或网关。
  3. ctx.on 注册的监听器随插件卸载自动移除。
  4. Cordis 事件(如 tools/result)是实时分发;turn/*、tool/call 等是持久化会话事件。
  5. 观察持久化事件要监听 session/event 并检查 event.type,不要按同名 Cordis 事件监听。
动手任务

写一个 tool-logger:监听 tools/result,打印工具名、参数与结果前 100 字符。再写一个 bail 检查,命中条件时返回拒绝。

自检
bail 和 serial 的区别?
bail 是决策:第一个有效返回值胜出并停止;serial 是按顺序执行并等待,遇到有效返回值也会终止。两者都用于「谁先表态谁生效」。
turn/start 应该怎么监听?
它是持久化会话事件,不是 Cordis 事件;监听 session/event,判断 event.type === 'turn/start'。
进度保存在本机浏览器
4.4

能力三角色:Definition / Provider / Consumer

约 35 分钟架构 原文 ↗ 仍然成立
审计依据 ·

三角色划分与 Bash 示例来自教程;具体包名以当前仓库为准。 查看本节审计记录

学习目标

用三角色拆一个能力,并解释为什么换 Provider 不需要改 Consumer。

前置知识

完成 4.1。

核心要点
  1. Definition:接口 + Request/Result 类型,例如 dsh-shell 注册 ctx.shell。
  2. Provider:真正实现能力,针对一种运行环境,例如 dsh-bash-local、dsh-bash-sandbox。
  3. Consumer:把能力包装成模型可调用的工具,例如 dsh-tool-bash。
  4. 三者可以同包也可以分包;判断标准是这些角色是否需要独立演进或替换。
  5. Provider 与 Consumer 互不依赖,都只依赖 Definition;所以换实现时接口与工具不用动。
动手任务

选 ctx.fs 或 ctx.llm,列出 Definition / Provider / Consumer 对应的包,并画一张依赖箭头图(只允许指向 Definition)。

自检
为什么单一角色不是 seam?
seam 是「可替换的完整能力」:只有接口没有实现不能工作,只有实现没有工具模型调不到;三角色合起来才是接缝。
换 Provider 要改什么?
只改配置里加载的 Provider 包;Definition 与 Consumer 保持不变。前提是 Provider 遵守同一个接口。
进度保存在本机浏览器