When Plugin A unloads, its tool, listener, timer, watcher, and provider disappear. Plugin B’s independent contributions remain.
acquire ctx.effect(() => { const timer = setInterval(work, 1000) return () => clearInterval(timer) }) release
A composability runtime for systems that must keep changing without leaving behind stale dependencies, listeners, tools, timers, or assumptions.
cordis_system_guide_for_coding_agents.md@deepseek-ai/cordis.Cordis supplies composition semantics. DeepSeek Harness supplies the agent meanings: sessions, tools, LLMs, policies, shell, sandbox, and user surfaces.
Agent semantics are capabilities, not framework primitives.
Owns lifecycle, dependency topology, scopes, and reversible contribution.
injectThe external world that plugins can safely integrate with, but cannot magically roll back.
The independently mountable lifecycle boundary.
One live activation instance and its cleanup ownership.
The scoped capability environment a plugin can see and alter.
A stable, named capability - never a concrete provider import.
Spatiotemporal composability combines clean withdrawal with live dependency rebinding. Either alone is insufficient.
When Plugin A unloads, its tool, listener, timer, watcher, and provider disappear. Plugin B’s independent contributions remain.
acquire ctx.effect(() => { const timer = setInterval(work, 1000) return () => clearInterval(timer) }) release
When the current llm, shell, or memory provider changes identity, consumers unload their active episode and reactivate against the replacement.
Consumer is active.
Owned contributions withdraw.
Consumer reactivates.
ctx.effect() and return the disposer. Keep order-dependent teardown inside one disposer.Mounted, but required capability missing.
Activation and setup in progress.
Current valid activation episode.
Effects and children settle.
Runtime identity is permanently gone.
Configuration says what exists. Injection says what must be ready. YAML row order is never the dependency system.
Interface and request/result types.
Service implementation.
Tool, feature, or workflow.
Use this three-role seam when implementations must vary independently. Do not create three packages for a tiny, one-off helper.
inject = ['tools']Plugin must not run without the capability.
ctx.get('metrics')Plugin is valid and complete without it.
ctx.isolate('shell')Different subtrees resolve the same service name independently.
ctx.intercept(...)Use only when the service explicitly supports interception.
export const name = 'greet-tool'
export const inject = ['tools', 'greeter']
export function apply(ctx: Context) {
// registrations inherit this Fiber's lifecycle
ctx.tools.register(defineTool({ /* ... */ }))
// unmanaged resources must declare cleanup
ctx.effect(() => {
const watcher = watch(path, onChange)
return () => watcher.close()
})
}A model-facing tool is not a Cordis primitive. It is an ordinary plugin registering into the Harness tools Service.
Claim input. Assemble system prompt and available tool schemas.
Persist input, derive model history, and dispatch agent/request.
Receive assistant chunks and message. Run any emitted tool calls.
tools/pre-execute → tools/execute → tools/post-execute → tool/result.
Repeat only if work remains. Otherwise emit turn completion.
Runtime observation and policy seams: emit, parallel, serial, waterfall.
Replay-critical model-visible facts: messages, tool calls, outcomes, turn history.
A durable session record does not automatically create a same-named live Cordis event.
next() unless they deliberately veto or replace downstream behavior. Forgetting it silently swallows the core operation.A feature may involve several primitives, but each has a distinct job. Do not let one concept impersonate another.
| Need | Use | Why |
|---|---|---|
| Mountable or replaceable behavior | plugin | Lifecycle and composition boundary. |
| Direct callable capability | Service | Stable provider abstraction for code. |
| Hard dependency | inject | Activation gates on a live service implementation. |
| Raw resource with cleanup | ctx.effect() | Explicit Fiber-owned disposer. |
| Open notification or policy seam | event | Producer does not know observers. |
| Model-callable operation | ctx.tools.register() | Harness tool registry, not framework magic. |
| Per-subtree provider | ctx.isolate() | Service realm, not an OS security boundary. |
| Durable replay/model fact | session event/log | Do not keep it only in a transient callback. |
Use stable row IDs in cordis.yml. Treat apply() as something that can happen repeatedly in one process.
- id: llm-provider name: './providers/deepseek.ts' - id: my-tools name: './tools/index.ts'
The Harness Loader tries to validate a changed candidate before destroying a working entry, then restores a prior config on failure when possible. It cannot rescue unmanaged global timers, stale module singletons, or irreversible external mutations.
Most “nothing happened” and “it fired twice” failures are lifecycle or dependency-graph mistakes, not timing problems.
disabled state.PENDING, locate the missing injected service.apply().ctx.effect().Who owns me? What do I require? How do I leave? What happens when my provider changes?
Read local vendored code and generated Harness surfaces before assuming upstream examples apply. The local Harness checkout is the implementation authority.
This review template turns the framework’s invariants into a repeatable agent workflow.
| Prompt | What to write before implementation |
|---|---|
| Capability | The problem, external contract, and owning domain subsystem. |
| Plugin boundary | Why it needs independent lifecycle, replacement, or config. |
| Provides / injects | Stable service names, required dependencies, and optional dependencies. |
| Effects / disposal | Every listener, timer, watcher, process, socket, and cleanup order. |
| Config / Loader | Schema, safe defaults, stable IDs, and update behavior. |
| Events / durability | Live event mode and which facts must become session records. |
| HMR / replacement | Why repeated activation and provider replacement remain safe. |
| Tests | Startup, unload, reload, provider replacement, error, and cancellation paths. |
Make behavior a plugin only when it benefits from independent lifecycle or composition. Declare required capabilities with inject; access them through stable service names. Treat every registration and resource as a Fiber-owned effect. Design for activation, cleanup, and activation again. Persist model-visible facts durably. Never confuse a flexible plugin graph with a security sandbox.