DEV Community

Cover image for DeepSeek Harness (3): Everything Is a Plugin — Now What?
DA SEIN
DA SEIN

Posted on Originally published at daseinsblog.hashnode.dev

DeepSeek Harness (3): Everything Is a Plugin — Now What?

The previous article ended with a simple question:

“Everything is a Plugin” sounds clean, but once dozens of Plugins exist at the same time, does the complexity really disappear?

Of course not.

A Tool Plugin may depend on a Sandbox. The Agent Loop depends on Tools and Sessions. A permission component may need to intervene before a Tool actually runs. More importantly, these Plugins are not always loaded once at startup and left alone forever. They may be replaced by configuration, mounted only for a specific Agent, or removed once a task is finished.

If a “plugin system” only means dynamically importing a collection of modules, then the coupling that used to live inside the Harness core will simply reappear somewhere else.

That is the problem Cordis is designed to solve.

DeepSeek’s own description of Cordis is actually fairly modest: the Cordis kernel mainly manages Plugin mounting, unmounting, and dependencies, while the actual Agent capabilities remain inside the Plugins themselves. In other words, Cordis is not another, larger “Agent core.” It is closer to a set of Runtime rules: it does not decide how Shell execution should work, but it does make sure that questions like “who provides Shell, who depends on Shell, when is it available, and what should disappear when the Plugin is removed?” remain manageable.

That is the best entry point for the concepts that follow.

First, Do Not Confuse Context with the Prompt

The first overloaded word is Context.

Earlier, when discussing LLMs, we also used “context” to mean the information the model can see in one request: user messages, history, Tool schemas, System Prompt, and so on.

But Cordis ctx is not that.

A useful first approximation is to think of it as the shared runtime workspace in which Plugins discover and register capabilities. If a Plugin wants to find a service, subscribe to an event, or register something that should later be cleaned up, it usually starts from ctx.

So it is better to keep these two meanings separate:

Model Context
= information sent into the LLM for one request

Cordis Context / ctx
= runtime environment where Plugins find and register capabilities
Enter fullscreen mode Exit fullscreen mode

Why do we need such a Runtime Context at all?

Suppose one Plugin needs access to the Tool system. The most direct implementation would be:

import toolRegistry from "./tool-registry"
Enter fullscreen mode Exit fullscreen mode

But that immediately couples the Plugin to one concrete implementation. If you later want to replace the Tool Registry, swap the Session Provider, or use different implementations under different configurations, every consumer may need to change.

Cordis inserts another layer: a Plugin does not directly look for another Plugin. It looks for the Service that Plugin provides.

A Service can be understood as a capability exposed under a stable name. In DeepSeek Harness, common examples include:

ctx.tools
ctx.sessions
ctx.llm
ctx.agents
Enter fullscreen mode Exit fullscreen mode

The Provider Plugin registers the capability under a stable key. Consumer Plugins depend on that key rather than on the concrete implementation behind it.

Plugins cooperate through stable Service keys instead of importing each other's concrete implementations.

Figure 1: A Provider Plugin registers a capability into the Cordis Context; a Consumer Plugin resolves it through a stable key.

This resembles Dependency Injection, but Cordis adds something important: a Plugin can explicitly declare what it depends on.

For example:

const plugin = {
  inject: ["tools", "sessions"],

  apply(ctx) {
    // ctx.tools and ctx.sessions are available here
  },
}
Enter fullscreen mode Exit fullscreen mode

There is nothing mysterious about inject. It simply says:

This Plugin only makes sense when both tools and sessions exist.

The Plugin therefore does not need to guess whether Sessions should start first, or manually poll until Tools are ready. The Runtime can infer activation from declared dependencies. If those dependencies change later, it can react to that change as well.

This is much closer to what Cordis means by Spatial Composability.

In the previous article, we used “which Agent owns this capability?” as an intuition. That intuition is useful, but it is not the formal meaning. Spatial composability is more fundamentally about this:

A component can declare the external capabilities it depends on, and the Runtime can react as those dependencies appear, disappear, or change.

So “spatial” here is not simply a location in an Agent tree. It is about the component’s position inside a network of dependencies.

A Plugin Must Be Removable, Not Just Loadable

If dependency management answers “how Plugins work together,” the next problem is:

What happens when a Plugin leaves?

This is where many ordinary plugin systems become much less convincing.

Imagine a Plugin that does all of the following when it starts:

register a Tool
add a Prompt section
subscribe to an Event
start a Timer
open an external resource
Enter fullscreen mode Exit fullscreen mode

If unloading the Plugin only means removing it from a list, the system is not actually clean.

The Tool schema may still exist. The Event listener may still fire. The Timer may still be running. The Prompt may still tell the model that a capability exists even though the Plugin that provided it is gone.

The Runtime has accumulated ghost state.

Cordis treats these runtime changes as Effects.

The term sounds abstract, but here it can be understood very literally:

An Effect is a change a Plugin makes to the Runtime, together with enough information to undo that change later.

A simplified example looks like this:

ctx.effect(() => {
  const dispose = registerCapability()

  return () => {
    dispose()
  }
})
Enter fullscreen mode Exit fullscreen mode

When the Plugin registers something, it also returns a disposer: the logic required to remove that registration later.

So the lifecycle becomes reversible:

Mount Plugin
   ↓
register Tool / listener / timer / prompt
   ↓
Runtime uses the capability
   ↓
Unmount Plugin
   ↓
run the disposer
   ↓
restore the Runtime
Enter fullscreen mode Exit fullscreen mode

Cordis tracks Plugin registrations as reversible Effects and disposes them when the Plugin is unmounted.

Figure 2: Dynamic composition is not only about mounting Plugins; the Runtime must also be able to remove the Effects they leave behind.

This corresponds to Temporal Composability in the Cordis paper.

Its strict meaning is not simply “when in the Agent Loop should a Plugin run?” For example, “Memory injects information before the LLM request” or “Logger records results after Tool execution” describe lifecycle intervention points, but they are not the core definition.

The deeper requirement is:

When a component leaves the system, the side effects it previously introduced must be completely reversible.

A Plugin should not only be easy to add now. It should also be possible to remove later without leaving the Runtime in a half-mutated state.

Cordis uses a Fiber to track a running Plugin instance. The name may sound abstract, but for our purposes a Fiber can be understood as the lifecycle record associated with one mounted Plugin instance. It tracks whether dependencies are currently satisfied, which Effects were registered, and what must be cleaned up when the Plugin is removed.

So:

Plugin
= code and configuration

Fiber
= the running instance of that Plugin and its lifecycle record
Enter fullscreen mode Exit fullscreen mode

Now “unload a Plugin” means more than deleting an object reference. The Runtime can follow the Fiber and undo what the Plugin added.

Agent Scope: Not Every Capability Should Be Global

Once dependencies and teardown are handled, DeepSeek Harness still faces a more concrete problem:

A single Runtime may contain multiple Agents, but not every registration should be visible to every Agent.

Some capabilities naturally belong to the global layer, such as model providers, persistence infrastructure, or shared services. Others may exist only for a particular Agent: a special Prompt section, a Tool variant, or a Listener that only makes sense for the current task.

If all registrations live in one global registry, Agent A’s local capabilities can leak into Agent B.

DeepSeek Harness therefore adds a local registration layer for each Agent. A useful mental model is:

Agent A sees
= Global Layer + Agent A Local Layer

Agent B sees
= Global Layer + Agent B Local Layer
Enter fullscreen mode Exit fullscreen mode

Different Agents share the Global Layer but only add their own Local Layer.

Figure 3: Agent Scope does not duplicate the entire Runtime; it overlays agent-local registrations on top of shared infrastructure.

There is an important detail here that our earlier intuition can easily get wrong: the current implementation is not simply “child Agents automatically inherit every capability from their parents.”

It is not:

Parent Agent
   ↓ automatically inherit everything
Child Agent
Enter fullscreen mode Exit fullscreen mode

It is closer to explicitly selecting the current Agent and resolving capabilities from Global + that Agent’s Local Layer.

This gives the system much tighter control over local registrations and avoids accidentally leaking one Agent’s local capabilities into another.

But Scope should not be confused with a security boundary.

It answers:

Which registrations should this Agent operation see?

It does not automatically answer:

Should this Plugin be trusted? Can it access the host filesystem?

Approval, permissions, and Sandboxes still need separate mechanisms. Scope is a composition boundary, not a security sandbox.

If We Have Services, Why Do We Still Need Events?

At this point, one final question remains.

If Plugins can already call each other through Services, why does DeepSeek Harness make such heavy use of Events?

Because not every form of collaboration should look like:

ctx.someService.doSomething()
Enter fullscreen mode Exit fullscreen mode

Some components merely want to observe something that happened. Others want to intercept an operation before it becomes final. They should not force the Agent Loop to import every possible extension directly.

For example:

Tool is about to execute
   ↓
Permission Plugin wants to inspect it

Tool execution finishes
   ↓
Logger wants to record the result

Model request is about to be sent
   ↓
Another Plugin wants to modify or intercept it
Enter fullscreen mode Exit fullscreen mode

If the Agent Loop contains an if branch for every extension, “Everything is a Plugin” quickly collapses back into “the core knows about every module.”

Events provide shared extension points.

The simplest Event is just a notification:

Something happened; interested Plugins may respond.

DeepSeek Harness also makes frequent use of Waterfall Events. A Waterfall can be understood as a chain of middleware with explicit control handoff. A Listener handles the current request and calls next() to delegate to the next Listener. If it owns the final decision, it can stop the chain instead.

This leads to a practical rule that is easy to remember:

  • Use a Service when you need to directly call a capability.
  • Use an Event when you need to observe, intercept, or inject policy into an existing flow.

That is how Permission, Telemetry, Sandbox adapters, and other extensions can attach themselves to an existing runtime path without moving back into the Agent Loop core.

Putting the Pieces Back Together

Now Cordis no longer looks like a pile of abstract nouns.

Its core flow can be summarized like this:

Plugin declares required capabilities
        ↓
inject

Dependencies become available
        ↓
Plugin activates

Plugin resolves stable Services through ctx
        ↓

Plugin registers capabilities or listeners
        ↓
those changes are tracked as reversible Effects

While the Plugin is running
        ↓
Services handle direct capability calls
Events handle observation, interception, and policy

If a registration belongs only to one Agent
        ↓
place it in that Agent's Local Scope

Plugin is replaced or removed
        ↓
Fiber triggers teardown
        ↓
Effects are disposed
Enter fullscreen mode Exit fullscreen mode

At this point, “Everything is a Plugin” finally becomes more than an architectural slogan.

Cordis does not eliminate complexity. Tools still need implementations. Sessions still need persistence. The Agent Loop still needs to manage state transitions. What Cordis does is force that complexity into clearer boundaries:

A Plugin declares what it depends on, what it provides, what it registers, and how its runtime changes can be undone; the Runtime organizes those relationships.

That makes it possible to add a new capability beside the existing system instead of repeatedly modifying an ever-growing privileged core.

And that leads naturally to the next question:

Once all of these Plugins are mounted, how does a single user message actually travel through DeepSeek Harness?

From Session input, to Turn and Step, to the model request, Tool Call, and finally back into the next model context—that is the next layer to unpack: the Agent Loop.

References

  1. DeepSeek Harness Official Page
  2. DeepSeek Harness Architecture
  3. DeepSeek Harness Cordis Primer
  4. A Programming Paradigm for Spatiotemporal Composability
  5. DeepSeek Harness — Agent Scope Architecture Note

Top comments (0)