waterfall: Intercept, Wrap, and Short-Circuit with next()
Follow a Cordis waterfall chain to see listeners delegate downstream, wrap results, or short-circuit when they own the decision.
Remember one rule: an observer must call next(); only a listener that owns the decision should omit it and return directly.
waterfall is not a broadcast. Each listener receives the arguments and a continuation. It can delegate to the default behavior or wrap the result that comes back from downstream.
01 / The chain is nested functions
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)
}The innermost function is the default behavior. Listeners wrap around it in registration order, and next() is what moves execution downstream.
02 / Wrapping and short-circuiting are different intents
- Delegate: the listener does not own the result, so it calls
next(). - Wrap: it awaits
next()and transforms the result, such as uppercasing or adding diagnostics. - Short-circuit: it owns the decision, skips
next(), and returns a replacement.
In the example, hello reaches the default behavior and becomes HELLO; blocked words returns ** blocked ** at the policy listener, so the default function never runs.
03 / Forgetting next() silently consumes the default
ctx.on('agent/request', async (request, next) => {
console.log('observed request')
return next()
})If an observer omits next(), its return value ends the entire waterfall. A downstream model request, tool execution, or default policy may never happen without looking like an exception.
Calling or not calling next is therefore a responsibility declaration: omission means veto or replacement; calling it means delegation.
04 / Two DSH decision points
DeepSeek Harness uses waterfall for replaceable policy:
agent/requestlets a plugin replace model-request configuration or wrap it before the provider.approval/requestlets a policy answer for the user, while ordinary observers can delegate downstream.
The owning service may define its own error boundary: for example, approval/request normalizes an answerer exception into an unavailable result. That behavior is not universal to every waterfall event.
The policy does not need to be embedded in the agent loop. A plugin attaches to the event, and waterfall semantics make result ownership explicit.
05 / Ask three questions before using it
- What is the default behavior? Write it as the innermost
nextfunction. - Is this listener observing, or does it own the replacement decision? Observers must delegate.
- Should the result be wrapped? If yes, transform it after
await next()instead of bypassing downstream.
Answering these questions turns waterfall into a readable middleware chain instead of an opaque callback list.
Where to go next
- Events: Let Plugins Cooperate Without Knowing Who Listens
- Configuration: Options with Validation
- Cordis Inside DeepSeek Harness
Sources: events tutorial, waterfall semantics in the primer, and Events implementation.
More Posts
Cordis: Five Concepts for Reading a Plugin Tree
Build the smallest useful mental model of Cordis with Plugin, Context, inject, Events, and Effect.
Services and Context: Share Capabilities Without Binding Implementations
Use Service, Context, and declaration merging to understand how providers, consumers, and scopes connect DeepSeek Harness capabilities.
cordis.yml: Turn Plugin Rows into a Mutable Application Composition
Understand Cordis entries, stable ids, groups, isolation, and patch layers as one runtime configuration tree.
Newsletter
Join the community
Subscribe to our newsletter for the latest news and updates