Dependency Resolution and Ordering
Cordis's "load order" is not a topological sort but a service-availability-driven reactive system — a fiber only transitions LOADING → ACTIVE when all services declared in inject are active; otherwise it stays PENDING forever (no error, because a provider may arrive later).
Not a Topological Sort
This is the key mental shift for understanding Cordis plugin loading: there is no explicit "load A before B" ordering. A plugin declaring inject: ['tools', 'systemPrompt'] stays PENDING until both services are active; if tools itself declares inject: ['systemPrompt'], Cordis resolves transitively.
_checkImpl: Whether a Service Is Injectable
_checkImpl(name) at vendor/cordis/src/fiber.ts:597-609:
_checkImpl(name) {
// look up ctx.reflect._getImpl(name, true)
// if the impl exists and check() passes, write into _store
// otherwise delete
}This is the "is the service injectable" check.
_refresh: The Epoch String
_refresh() at vendor/cordis/src/fiber.ts:611-623:
_refresh() {
// iterate Object.keys(this.inject)
// if any _store[name] is missing, set epoch to INACTIVE (PENDING)
// otherwise concatenate ':uid:uid' into the new epoch string
}_setEpoch(epoch) at vendor/cordis/src/fiber.ts:625-639: an epoch change triggers _updateState —:
- INACTIVE → non-INACTIVE triggers
_reload()(LOADING) - The reverse triggers
_unload()(UNLOADING)
Why uid Concatenation Instead of a Boolean
_setEpoch concatenates uids instead of using a boolean: when a provider is reloaded its uid changes, which changes the epoch string of every downstream fiber and cascades a reload — this is the root cause of HMR needing no manual bookkeeping.
Two Ways to Write inject
Resolved Inside plugin()
plugin(plugin, config) at vendor/cordis/src/registry.ts:316-336:
plugin(plugin, config) {
// new runtime → new Fiber(ctx, config, Inject.resolve(plugin.inject), runtime, …)
// inject is resolved at fiber construction time into ctx.intercept overrides
}inject() Sugar
inject(inject, callback) at vendor/cordis/src/registry.ts:300-302 is sugar for plugin({ inject, apply: callback, name: callback.name }): the callback executes when the service becomes available, and is unloaded and re-run when the service disappears.
Dynamic Plugin Startup: guardedPlugin
startHostHalf
startHostHalf(group, plugin, reportGuardFailure) at packages/extensions/cordis-host-runner/src/lifecycle.ts:22-45:
async function startHostHalf(group, plugin, reportGuardFailure) {
await group.await() // wait for the group to be ready
const fiber = group.ctx.plugin(guardedPlugin(plugin)) // mount after wrapping
await fiber.await()
// on failure fiber.dispose() — no FAILED fiber residue left
}group.await() ensures the cordis-dynamic group fiber that the dynamic plugin mounts onto is ready before spawning a child fiber, avoiding orphan fibers mounted while the group is not yet ACTIVE.
The guardedPlugin Sandbox
The guardedPlugin wrapper: the apply of a dynamic plugin receives sandboxContext(ctx, reportFailure) rather than the real ctx, exposing only a whitelist of verbs (ctx.get / ctx.on / ctx.provide / ctx.effect); framework internals and context-valued returns are rejected.
The Sandbox is not a security boundary: the
host-sandbox.tscomment explicitly states "is not containment: host-realm helper functions remain an escape route". Dynamic plugins are for cooperative packages (observable and disposable), not for defending against malicious code.
Diagnostics: Why My Plugin Won't Load
missingServices(ctx, fiber) at packages/extensions/cordis-host-runner/src/lifecycle.ts:55-57:
function missingServices(ctx, fiber) {
// use ctx.get(service) === undefined to list services the fiber is still waiting on
}This is the diagnostic answer to "why won't my plugin load". The "settled-but-pending" report of the cordis_inspect_self tool comes from this function, telling the agent: "your plugin is waiting on the fs service".
Reactive Model Summary
There is no "scheduler" ordering anything; each fiber independently observes the services it cares about and reacts automatically when service availability changes.
What to read next
- Unloading and Hot Reload — how uid changes drive cascading reloads
- Lifecycle and Middleware — the full definition of the Fiber state machine