$dshubuser@dshub:~/plugins$
  • 插件
  • 提交插件
  • 博客
waterfall:用 next() 做拦截、包装与短路
2026/08/26

waterfall:用 next() 做拦截、包装与短路

沿着一条真实的 Cordis waterfall 链路,理解监听器如何委托下游、包装结果,或在拥有决策权时直接短路。

记住一个规则:观察者必须调用 next();只有真正拥有决策权的监听器,才应该不调用它并直接返回。

waterfall 不是普通广播。每个监听器拿到参数和一个 continuation,可以把下游当作默认行为来委托,也可以在链条外层包装下游结果。

01 / 一条链像一组嵌套函数

Cordis waterfall listeners wrapping a downstream default function from outer to inner
import type { Context } from '@deepseek-ai/cordis'

declare module '@deepseek-ai/cordis' {
  interface Events {
    'demo/transform'(input: string, next: () => Promise<string>): Promise<string>
  }
}

export async function apply(ctx: Context) {
  ctx.on('demo/transform', async (input, next) => {
    const downstream = await next()
    return downstream.toUpperCase()
  })

  ctx.on('demo/transform', async (input, next) => {
    if (input.includes('blocked')) return '** blocked **'
    return next()
  })

  const input = 'hello'
  const result = await ctx.waterfall('demo/transform', input, async () => input)
  console.log(result)
}

最内层函数是默认行为。监听器按注册顺序包在它外面,调用 next() 才会继续走向下游。

02 / 包装和短路是两种不同意图

Cordis waterfall decision diagram showing delegate, wrap, and short-circuit outcomes
  • 委托:监听器不决定结果,调用 next()。
  • 包装:监听器等待 next() 返回,再转换结果,例如统一大写或追加诊断信息。
  • 短路:监听器拥有决策权,不调用 next(),直接返回替代结果。

示例中,hello 会经过默认逻辑,再被外层转换成 HELLO;blocked words 在策略监听器处直接得到 ** blocked **,默认逻辑根本不会运行。

03 / 忘记 next() 会悄悄吞掉默认行为

ctx.on('agent/request', async (request, next) => {
  console.log('observed request')
  return next()
})

如果这个只负责记录的监听器没有调用 next(),它的返回值就会结束整个 waterfall。下游模型请求、工具执行或默认策略可能因此完全不发生,而且表面上不像一次异常。

所以“是否调用 next”不是风格偏好,而是监听器的责任声明:不调用意味着否决或替代;调用意味着继续委托。

04 / DSH 中的两个决策点

DeepSeek Harness 使用 waterfall 连接可替换策略:

  • agent/request 允许插件替换模型调用配置,或在请求进入 provider 前包装它。
  • approval/request 允许策略插件代替用户作答,也允许普通监听器继续交给下游。

具体服务仍可定义自己的异常边界:例如 approval/request 会把答复者抛出的异常规范化为 unavailable 结果;这不是所有 waterfall 事件的通用行为。

这些扩展点不需要把策略写进 agent loop。插件只挂到事件上,返回结果的责任由 waterfall 语义明确表达。

05 / 使用前先问三个问题

  1. 这个事件的默认行为是什么?把它作为最内层 next 函数写出来。
  2. 监听器只是观察,还是有权替换决策?观察者必须委托。
  3. 返回值是否需要包装?如果需要,在 await next() 之后转换,而不是绕过下游。

把这三个问题写清楚,waterfall 就是可读的中间件链,而不是一串难以追踪的回调。

下一步

  • 事件:不知道谁在听,也能让插件协作
  • 配置:带校验的选项
  • Cordis 在 DeepSeek Harness 里

来源:事件教程、Cordis primer 的 waterfall 语义、Events 实现。

全部文章

作者

avatar for DSH Research
DSH Research

分类

  • Agent 基础设施
01 / 一条链像一组嵌套函数02 / 包装和短路是两种不同意图03 / 忘记 next() 会悄悄吞掉默认行为04 / DSH 中的两个决策点05 / 使用前先问三个问题下一步

更多文章

Cordis:五个核心概念,读懂一棵插件树
Agent 基础设施

Cordis:五个核心概念,读懂一棵插件树

用 Plugin、Context、inject、Events 与 Effect 五个概念,建立 Cordis 运行时的最小心智模型。

avatar for DSH Research
DSH Research
2026/08/19
生命周期与 effect:让 Cordis 注册可以回卷
Agent 基础设施

生命周期与 effect:让 Cordis 注册可以回卷

跟随 Fiber 从 PENDING 到 DISPOSED,理解 effect、disposer 与 HMR 如何共同管理插件副作用。

avatar for DSH Research
DSH Research
2026/08/24
动手:从 hello.ts 写出你的第一个 Cordis 插件
Agent 基础设施

动手:从 hello.ts 写出你的第一个 Cordis 插件

用一个函数插件和一份 cordis.yml,跑通 Loader、Context 与 Fiber 的最短路径。

avatar for DSH Research
DSH Research
2026/08/20

邮件列表

加入我们的社区

订阅邮件列表,及时获取最新消息和更新

[dshub] [zsh]$ dshub ls$ dshub submit$ dshub blog
-- NORMAL -- ✓ 2026 dshub