flowchart LR T["Temporal: what did I change?"] --> U["Own the effect lifetime"] S["Spatial: what do I need?"] --> V["React to dependency changes"] U --> W["Composable component"] V --> W
Why Dynamic AI Systems Break in Two Different Directions
Modern AI systems are becoming less like single programs and more like small operating environments.
An agent may have an LLM provider, tool registry, MCP servers, filesystem layer, sandbox, memory backend, session store and an agent loop. More importantly, these pieces may not remain fixed for the entire lifetime of the process.
An MCP server can disappear.
A model adapter can be replaced.
A new tool can be installed.
A memory backend can reconnect.
An agent may even modify the software environment in which it runs.
DeepSeek Harness makes this architectural problem concrete: it uses Cordis as the plugin framework, with services and capabilities composed through a shared context rather than a permanently privileged monolithic core.(DeepSeek AI 2026)
The programming-language paper behind Cordis, A Programming Paradigm for Spatiotemporal Composability, asks a deeper question:
What properties would a programming model need if components could be added, removed and replaced while the program stays alive?
The paper identifies two orthogonal problems:
- Temporal composability: Can a component be removed without leaving its modifications behind?
- Spatial composability: Can a component declare what it depends on and react correctly when those dependencies appear, disappear or change?
It addresses these using revertible effects and reactive coeffects, then combines both into a component lifecycle model and develops system-level results for dynamic composition.(Cordiverse 2026)
This series explains those ideas for engineers building LLM systems, agent runtimes and extensible infrastructure.
We will use the same small Python runtime throughout:
Agent Runtime
│
├── LLM Provider
├── Tool Registry
├── MCP Plugin
├── Memory
├── Sandbox
└── Agent Loop
The Python code is pedagogical. It is not a Python port of Cordis.
Each part also uses operating-system concepts as comparison points. These analogies are useful because most engineers already understand process lifetime, resource ownership, device dependencies and lifecycle races. However, the analogies are not equivalences. Wherever an OS comparison stops matching the paper, we will say so explicitly.
The two dimensions at a glance
The shortest mental model is: temporal composability asks whether a component leaves residue; spatial composability asks whether it should be running at all.
Installing a Plugin Is Easy. Removing It Correctly Is Not.
Suppose you are building a small agent runtime.
The first version may look completely reasonable:
class Runtime:
def __init__(self):
self.services = {}
self.tools = {}
self.hooks = []
def register_service(self, name, service):
self.services[name] = service
def register_tool(self, name, tool):
self.tools[name] = tool
def register_hook(self, hook):
self.hooks.append(hook)Now we add an LLM provider:
class DeepSeekProvider:
async def complete(self, messages):
...and install it:
runtime.register_service("llm", DeepSeekProvider())Then we add a search plugin:
def install_search_plugin(runtime):
runtime.register_tool("web_search", web_search)
runtime.register_hook(log_search_result)Everything works.
The agent can call the model and use search.
The interesting problem begins when we ask:
uninstall_search_plugin(runtime)What exactly should that operation do?
It has to remove:
web_search from the tool registry
log_search_result from the hook registry
any timers created by the plugin
any event listeners created by the plugin
any child components started by the plugin
any services exposed by the plugin
If the plugin changed ten runtime structures, its cleanup path needs to remember all ten.
That is already uncomfortable.
Now add another requirement:
Replace the LLM provider without restarting the agent process.
Our runtime currently has:
agent_loop.llm = runtime.services["llm"]Suppose the agent loop stores that reference during startup.
Then we replace:
runtime.services["llm"] = AnotherProvider()The runtime says the provider changed.
The agent loop may still be using the old provider.
We now have two different categories of failure.
Problem 1: What Did the Component Change?
Imagine that installing plugin search creates this state:
Before
Tools:
read_file
Hooks:
on_message
After search plugin
Tools:
read_file
web_search
Hooks:
on_message
log_search_result
Removing search should ideally produce exactly:
Tools:
read_file
Hooks:
on_message
This is a time problem.
The component existed during a particular interval:
─────────┬──────────────────────────┬─────────>
load unload
During that interval, it modified the environment.
Once its lifetime ends, we want its contribution removed.
The Cordis paper calls this dimension temporal composability: the ability to completely revert a component’s side effects upon removal.(Cordiverse 2026)
Problem 2: What Does the Component Need?
Now consider our agent loop:
class AgentLoop:
requires = {
"llm",
"tools",
"sessions",
}It should not become active when only this exists:
LLM ✓
Tools ✓
Sessions ✗
More importantly, if sessions disappears later, the runtime must react.
This is a space problem.
The component occupies a position in a dependency graph:
LLM
│
▼
Tools ──► Agent Loop ◄── Sessions
The paper calls this spatial composability: dependencies are declared and reactively managed rather than hidden inside arbitrary startup code.(Cordiverse 2026)
Why These Problems Are Orthogonal
A system can solve one without solving the other.
Perfect cleanup, bad dependencies
Imagine every plugin has perfect undo logic:
dispose = install_plugin()
dispose()After disposal, nothing leaks.
Great.
But the agent loop still starts before its LLM provider exists.
Temporal composability is good.
Spatial composability is bad.
Perfect dependency injection, bad cleanup
Now imagine a sophisticated dependency-injection framework:
agent = AgentLoop(
llm=container.resolve("llm"),
tools=container.resolve("tools")
)Dependencies are clear.
But unloading AgentLoop leaves behind:
event listeners
registered tools
callbacks
timers
child services
Spatial composition may be structured.
Temporal composition is still broken.
Separating these two dimensions is one of the most useful ideas in the paper.
Example: Breaking an Agent Runtime
Let us intentionally build a bad plugin system.
class Runtime:
def __init__(self):
self.services = {}
self.tools = {}
self.hooks = []
def emit(self, event):
for hook in self.hooks:
hook(event)Our search plugin:
class SearchPlugin:
def load(self, runtime):
runtime.tools["web_search"] = self.search
runtime.hooks.append(self.on_event)
def search(self, query):
return f"results for {query}"
def on_event(self, event):
print("search event:", event)Install it:
plugin = SearchPlugin()
plugin.load(runtime)Now suppose we remove our Python reference:
del pluginDid the plugin disappear?
No.
The runtime still contains references in:
runtime.tools["web_search"]
runtime.hooksThe module’s logical lifetime has ended, but its effects have not.
Dynamic composition needs a stronger relationship between lifetime and state.
Relatable Example: Process Cleanup
Operating systems already teach engineers an important lesson:
Lifetime and resources should be connected.
When a process terminates, the operating system closes resources such as file descriptors associated with that process.(Michael Kerrisk n.d.)
Conceptually:
Process
│
├── fd 3
├── fd 4
└── fd 5
Process exits
│
▼
Kernel releases process-owned descriptors
The process does not need to leave behind an application-level list saying:
please close fd 3
please close fd 4
please close fd 5
Ownership gives the system enough structure to perform some cleanup.
That is a useful intuition for Cordis:
Runtime resources created during a component’s lifetime should be associated with that lifetime.
Limits of the Comparison
Suppose a process does this:
with open("config.txt", "w") as f:
f.write("new configuration")When the process exits, the file descriptor is closed.
But the filesystem does not restore the old file contents.
So:
resource release
≠
state reversal
The operating system can release resources whose ownership it knows.
The Cordis paper asks for something stronger inside the managed context:
Can context transformations themselves carry enough information to be reversed?
That leads to revertible effects.
Relatable Example: Driver Dependencies
Linux device links provide a useful analogy for the spatial side.
The Linux driver core can represent relationships between a supplier device and a consumer device, including ordering constraints around probing and unbinding.(The Linux kernel developers n.d.)
Conceptually:
Supplier
│
▼
Consumer
Compare this to an AI runtime:
LLM Provider
│
▼
Agent Loop
or:
MCP Transport
│
▼
MCP Tool Adapter
A consumer should not pretend to be active when the capability it requires does not exist.
That is a useful mental model for reactive dependencies.
Limits of the Comparison
Linux device-link machinery solves particular kernel lifecycle problems.
It does not automatically provide the paper’s general reversible-effect model, effect independence or system-level composability results.
Why This Matters More for AI Runtimes
Traditional applications often assume their architecture is fixed between process start and exit.
Agent runtimes increasingly challenge that assumption.
Consider a long-running session:
09:00 Agent starts with Model A
09:07 MCP filesystem server appears
09:14 Search plugin is installed
09:31 Model A is replaced by Model B
09:44 Search plugin is upgraded
10:02 MCP filesystem server disappears
10:08 MCP filesystem server reconnects
Restarting the entire process after every change is possible.
But it gives up dynamic composition.
A runtime that genuinely supports change needs answers to four questions:
1. What did this component change?
2. How do I undo only those changes?
3. What dependencies does this component require?
4. What happens when those dependencies change while it is alive?
The first two belong primarily to the temporal dimension.
The last two belong primarily to the spatial dimension.
The paper’s basic thesis is that both should become part of the programming model.(Cordiverse 2026)
DeepSeek Harness Makes the Problem Concrete
DeepSeek Harness uses Cordis as its runtime composition substrate. The official repository describes the harness as a plugin-based architecture powered by Cordis.(DeepSeek AI 2026)
The mental model is closer to:
Cordis Context
│
┌────────────┼─────────────┐
│ │ │
▼ ▼ ▼
Model Tools Sessions
│ │ │
└────────────┼─────────────┘
▼
Agent Loop
than:
Permanent Agent Core
│
├── optional plugin
├── optional plugin
└── optional plugin
If central capabilities are dynamically composable, lifecycle correctness stops being an edge case.
It becomes architectural.
The Mental Model to Keep
Temporal composability
Component A enters
│
▼
changes shared context
│
▼
Component A leaves
│
▼
A's contribution disappears
Spatial composability
A requires B
B absent
│
▼
A inactive
B appears
│
▼
A activates
B disappears
│
▼
A deactivates
The next four parts make these diagrams progressively more precise.