Lifecycle and Effects: Make Cordis Registrations Reversible
Follow a Fiber from PENDING to DISPOSED and see how effects, disposers, and HMR manage plugin side effects together.
The conclusion: a plugin creates resources while loading and runs a disposer while unloading; Fiber binds both actions to one lifecycle.
That is why HMR is not merely importing a file again. The implementation may import the replacement module before unloading the old Fiber; the invariant is that the old Fiber's listeners, services, child plugins, and external resources are reversed before the new Fiber becomes active.
01 / Fiber is the runtime handle for a plugin instance
A plugin instance moves through:
PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
↘ FAILEDPENDING: declared, but dependencies are not ready.LOADING/ACTIVE:applyis running or complete.FAILED: configuration validation orapplythrew.UNLOADING/DISPOSED: cleanup is running or complete.
Fiber is not just a status label. It is the lifecycle handle owned by a plugin instance. fiber.dispose() waits for cleanup and recursively disposes child plugins.
02 / Put external resources inside an effect
Cordis knows how to reverse its own registrations. Timers, connections, and watchers need an explicit wrapper:
ctx.effect(() => {
const timer = setInterval(() => console.log('tick'), 200)
return () => {
clearInterval(timer)
console.log('cleaned up')
}
})The effect body runs during load; the returned function runs during unload. The plugin does not need a global shutdown function that guesses who owns the resource.
03 / Common registrations are effects too
These operations attach cleanup to the current Fiber:
ctx.on()registers an event listener.ctx.plugin()mounts a child plugin.- A
Serviceregisters a service name. ctx.effect()wraps an external resource Cordis does not manage yet.
During unload, Fiber collects disposers in reverse registration order and starts top-level cleanup concurrently; asynchronous cleanup from sibling effects may run concurrently, and fiber.dispose() waits for all of it. If two resources require strict ordering, put their cleanup in one effect and await inside it.
04 / A small verifiable example
import type { Context } from '@deepseek-ai/cordis'
function heartbeat(ctx: Context) {
console.log('heartbeat plugin loading')
ctx.effect(() => {
const timer = setInterval(() => console.log('tick'), 200)
return () => {
clearInterval(timer)
console.log('heartbeat cleaned up')
}
})
}
export function apply(ctx: Context) {
const fiber = ctx.plugin(heartbeat)
ctx.effect(() => {
const shutdown = setTimeout(async () => {
await fiber.dispose()
console.log('disposed')
process.exit(0)
}, 700)
return () => clearTimeout(shutdown)
})
}The output shows heartbeat and ticks, followed by cleanup and disposed. The important property is that cleanup has completed before fiber.dispose() resolves.
05 / Effects are HMR infrastructure
HMR can turn a code refresh into a controlled runtime transition: import the replacement module, dispose the old Fiber and reverse its effects, then register and activate the replacement Fiber. Resources without disposers do not disappear by magic, so each plugin should give every resource it creates a clear owner.
In DeepSeek Harness, tool registrations, event listeners, model adapters, and the agent loop all enter this lifecycle through plugins. Unload is a runtime action, not a cleanup promise in documentation.
Where to go next
Sources: lifecycle tutorial, Fiber implementation, and Context API.
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.
Cordis Inside DeepSeek Harness: Making Everything a Plugin
Trace dsh-base, Context services, event domains, and the tool pipeline to see how Cordis organizes a replaceable Agent runtime.
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.
Newsletter
Join the community
Subscribe to our newsletter for the latest news and updates