$dshubuser@dshub:~/plugins$
  • Plugins
  • Submit
  • Blog
inject: Let Dependencies Decide Load Order, Not YAML Line Numbers
2026/08/23

inject: Let Dependencies Decide Load Order, Not YAML Line Numbers

Understand PENDING, dependency tracking, and optional service probes, including why provider swaps move consumers automatically.

The rule: a plugin that needs a service declares inject. Cordis waits for readiness instead of making developers hand-write startup order.

This moves “who starts first?” from file layout into a runtime dependency relation that can be observed and maintained.

01 / Two rows whose order can change

Cordis dependency graph showing provider readiness before consumer activation

The provider:

import { Service, type Context } from '@deepseek-ai/cordis'

export class GreeterService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'greeter')
  }
}

The consumer:

export const inject = ['greeter']

export function apply(ctx: Context) {
  console.log(ctx.greeter.greet('world'))
}

Both entries can appear in any order in cordis.yml. The consumer’s apply runs only after greeter is registered.

The array form names services only; Cordis normalizes it to a “service name → null config” map. When a service supports local intercept configuration, inject can also use an object form that maps each service name to its config.

02 / PENDING means waiting, not half-started

Cordis injected plugin state flow from PENDING to LOADING and ACTIVE or back to PENDING

When a dependency is absent, the Fiber remains PENDING:

  • apply is not called, so the plugin does not run halfway.
  • A pending Fiber does not keep Node’s event loop alive by itself.
  • When the provider mounts, the consumer enters LOADING; it reaches ACTIVE only if config and apply succeed.

PENDING is not FAILED: a missing dependency waits, while a config or apply throw fails. This is one common explanation for “nothing happened”. Check the service name and current provider composition before debugging the plugin body.

03 / Dependencies remain live after boot

inject is not a one-time startup check. If a provider unloads or is hot-replaced, its dependent consumers unload too; when the provider returns, consumers load again.

Cordis unloading a provider and its consumers, then reloading them when the service returns

This works with effect ownership: listeners, registrations, and external resources owned by the consumer are removed while the dependency is absent, instead of holding references to an unavailable service.

A composition can therefore replace a local shell provider with an E2B provider. Every plugin with inject: ['shell'] follows the new implementation without source changes.

04 / Hard and optional dependencies

A hard dependency fits a plugin that has no useful meaning without the service:

export const inject = ['tools']

An optional dependency fits a plugin that can still operate without the service:

export function apply(ctx: Context) {
  const greeter = ctx.get('greeter')
  console.log(greeter ? greeter.greet('maybe') : 'no greeter available')
}

Do not put an optional service in inject; the plugin will correctly remain PENDING when its provider is missing. ctx.get() is a runtime probe, not a dependency edge; when the provider appears later, that probe does not automatically rerun apply.

05 / Dependencies outlast bootstrap scripts

A hand-written bootstrap script has to maintain ordering, retries, and unload behavior. inject lets Fiber and the registry own those transitions. Adding, replacing, or temporarily removing a provider reuses the same readiness semantics.

In DSH, profiles can therefore recompose tools, models, sessions, and Agents without forcing every consumer to know the complete startup graph.

Where to go next

  • Services and Context
  • Lifecycle and effects
  • Composition and HMR

Sources: services tutorial, lifecycle tutorial, and Fiber implementation.

All Posts

Author

avatar for DSH Research
DSH Research

Categories

  • Agent Infrastructure
01 / Two rows whose order can change02 / PENDING means waiting, not half-started03 / Dependencies remain live after boot04 / Hard and optional dependencies05 / Dependencies outlast bootstrap scriptsWhere to go next

More Posts

Configuration: Give Plugins Validated Options and Defaults
Agent Infrastructure

Configuration: Give Plugins Validated Options and Defaults

Define Cordis plugin configuration with Schemastery and see how defaults, path-aware errors, and FAILED Fibers prevent a sick start.

avatar for DSH Research
DSH Research
2026/08/27
Events: Let Plugins Cooperate Without Knowing Who Listens
Agent Infrastructure

Events: Let Plugins Cooperate Without Knowing Who Listens

Use typed events and five dispatch modes to understand how Cordis broadcasts facts, runs listeners concurrently, or hands over decisions.

avatar for DSH Research
DSH Research
2026/08/25
Lifecycle and Effects: Make Cordis Registrations Reversible
Agent Infrastructure

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.

avatar for DSH Research
DSH Research
2026/08/24

Newsletter

Join the community

Subscribe to our newsletter for the latest news and updates

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