The VitePress config declared no position for the subsystem or other-interface sections, so `indexOf` returned -1 and sorted them ahead of every declared group: the reference landing page's own sidebar entry sat 1549px below the fold. Four subsystem pages also shared `order` values with pages in the same section, resolved only by sort stability and array concatenation order. Section placement and collapse move into the manifest as a per-locale declaration, and `sectionSpec` throws for an undeclared section instead of sorting it silently to the top. Subsystem pages are grouped by concern, the six topical groups collapse until one holds the page being read, and page order derives from array position. The projector drops the language-switcher line and repository badge the canonical pages carry for their GitHub readers. The navigation bar gains the DeepSeek wordmark, a release-stage tag, and a favicon; the sidebar scrollbar rests invisible and appears while scrolling. Subsystem pages carry a two-level outline, and the two plugin-development tracks now cross-link.
3.8 KiB
Plugins and lifecycle
English | 中文
This page describes the Cordis plugin model and lifecycle state machine.
Fiber state machine
Every loaded plugin owns a Fiber scope with the following states:
PENDING → LOADING → ACTIVE
↘ FAILED
ACTIVE → UNLOADING → DISPOSED
| State | Meaning |
|---|---|
| PENDING | Declared, but required dependencies are not ready |
| LOADING | Dependencies are ready and apply is running |
| ACTIVE | The plugin is running |
| FAILED | apply threw an error |
| UNLOADING | The plugin is unloading and disposing resources |
| DISPOSED | The plugin is fully unloaded |
Dependency-driven loading
A plugin with inject waits for every required service before loading:
export const inject = ['tools', 'llm']
export function apply(ctx: Context) {
// ctx.tools and ctx.llm are ready here.
}
If a required service disappears, for example during provider replacement, the plugin unloads automatically (ACTIVE → DISPOSED) and loads again when the service returns.
Automatic cleanup
Every registration made through ctx is undone when the plugin unloads:
export function apply(ctx: Context) {
// Event listener: removed automatically on unload.
ctx.on('some-event', handler)
// Custom resource: the returned disposer runs on unload.
ctx.effect(() => {
const connection = createConnection()
return () => connection.close()
})
}
The framework tracks and disposes all of these operations:
ctx.on(event, handler)— event listenerctx.tools.register(tool)— tool registrationctx.llm.registerAdapter(names, adapter)— LLM adapter registrationctx.effect(() => cleanup)— custom resource
During unload, disposer invocation starts in reverse registration order, but multiple async disposers run concurrently and have no serial completion guarantee. Put order-dependent cleanup in one disposer returned from a single ctx.effect() and await its steps serially there.
Nested contexts
ctx.plugin() creates a child Fiber that inherits the parent context but has an independent lifecycle:
export function apply(ctx: Context) {
// Register a child plugin.
ctx.plugin(childPlugin)
// The child has its own Fiber and unloads with its parent.
}
Dispose semantics
To stop a plugin instance early:
import type { Context } from '@deepseek-ai/cordis'
declare const ctx: Context
declare function myPlugin(ctx: Context): void
const fiber = ctx.plugin(myPlugin)
// Dispose it manually later.
await fiber.dispose()
dispose guarantees:
- All registrations owned by the plugin are removed.
- Child plugins are recursively unloaded.
- The returned promise resolves after all asynchronous cleanup finishes.
Hot replacement (HMR)
With @deepseek-ai/cordis-plugin-hmr loaded from cordis.yml, editing a plugin source file triggers:
- Unload the old plugin and clean up its registrations.
- Load the new code.
- Run the new
apply.
Because plugin registrations clean themselves up, hot replacement does not retain registrations from the old instance.
Example lifecycle
export function apply(ctx: Context) {
console.log('plugin loading')
ctx.effect(() => {
console.log('effect registered')
return () => console.log('effect cleaned up')
})
}
Loading prints:
plugin loading
effect registered
Unloading prints:
effect cleaned up
Next steps
- Services and dependencies — expose a capability to other plugins
- Event system — communicate between plugins
- Cordis tutorial — the same lifecycle, services, and events built step by step against the Cordis runtime