跪拜 Guibai
← All articles
Cordis · DeepSeek Harness · Agent · Plugin System · TypeScript

Cordis Context and Plugins: The Container Pattern Behind Agent Frameworks

By 两万五千个小时 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Agent frameworks are inherently modular—tools, sessions, prompts, and loops all need to share state and respond to events. A container-based plugin system like Cordis avoids the wiring spaghetti that comes from manual dependency injection or global singletons, and the three plugin forms give teams a clear gradient from quick script to production service without changing the architecture.

Summary

Cordis, the framework underneath Koishi and DeepSeek Harness, reduces its core to two abstractions: a Context container and plugins that populate it. A Context holds services that any plugin can register with `ctx.provide('name', implementation)` and any other code can retrieve as `ctx.name`. Plugins come in three forms—functions for quick prototypes, objects when a name or metadata is needed, and classes for stateful components that accept configuration through the constructor.

The pattern is a service locator with explicit registration order. Plugins activate asynchronously via `ctx.plugin()`, so awaiting registration is mandatory before consuming a service. Internal state stays private inside closures or class fields; only the methods exposed through `ctx.provide()` are reachable from outside.

This container model becomes the foundation for the Agent framework built in later installments: Session management, tool systems, prompt assembly, and the agent loop will each be services living inside the same Context, wired together through the same provide-and-consume mechanism.

Takeaways
Context is a container: `ctx.provide('name', impl)` stores a service, `ctx.name` retrieves it.
Plugins always receive a Context and activate asynchronously through `ctx.plugin()`, which must be awaited.
Function plugins are the simplest form—no metadata, no config, state lives in closures.
Object plugins add a `name` property and an `apply` method, useful for debugging and dependency declarations.
Class plugins accept configuration via the constructor's second argument and handle complex internal state naturally.
All three plugin forms ultimately do the same thing: call `ctx.provide()` to mount services onto the Context.
Service names are arbitrary strings; the name passed to `provide()` determines the property name on the Context.
Conclusions

The three plugin forms are not about capability—they all end up calling `ctx.provide()`. The choice is purely about code organization: how much ceremony a given module deserves.

Private state in closures (function and object plugins) is a deliberate encapsulation boundary. External code cannot reach the `logs` array or `count` variable directly, only through the methods exposed on the service object.

Cordis uses a service locator pattern rather than constructor-based dependency injection. Plugins don't declare what they need; they just reach into the Context and grab it, which means ordering and activation timing matter.

The framework's minimal surface area—two concepts, three plugin forms—suggests the complexity in an Agent system will come from the services themselves, not from the wiring between them.

Concepts & terms
Context
A container object in Cordis that holds services and manages plugin lifecycles. Services are registered with `ctx.provide('name', value)` and accessed as `ctx.name`.
Plugin
A unit of functionality that receives a Context and registers services into it. Can be written as a function, an object with an `apply` method, or a class whose constructor takes Context and an optional config object.
ctx.provide()
The method that mounts a service onto a Context. The first argument is the service name (which becomes the property name on the Context), and the second is the implementation object or class instance.
Service Locator Pattern
A design pattern where components fetch their dependencies from a shared registry (the Context) rather than receiving them through constructor injection. Cordis implements this pattern with `ctx.provide()` as the registration side and direct property access as the consumption side.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗