Skip to content
On this page

    DeepSeek Harness, Cordis, and the Case for Spatiotemporal Agent Architectures

    DeepSeek AI and Peking University formalized agent modularity in a recent paper on spatiotemporal composability. Here is an engineering look at the Cordis kernel, revertible effects, and what this pattern means for self-modifying agents.

    6 min read

    Most agent frameworks claim they are modular. In practice, they offer surface-level customization.

    They give you a tool registry, a prompt template, and a model router. Then they hardcode the execution loop, the session state, the sandbox runtime, and the user interface into a fixed core. If you want to change how the loop handles retries, swap the session storage engine, or replace the sandbox environment, you end up forking the repository or hacking internal classes.

    When DeepSeek released DeepSeek Harness (dsh), they took a different approach. Alongside the codebase, DeepSeek AI and Peking University published a research paper: A Programming Paradigm for Spatiotemporal Composability.

    The paper formalizes the architecture behind Cordis, the open-source TypeScript micro-kernel underneath DeepSeek Harness. Instead of building a monolithic agent runtime with hooks, they built an engine where models, tools, sandboxes, session stores, and even the agent loop itself are hot-swappable plugins.

    flowchart TB
        subgraph Monolith["Typical Agent Framework (Fixed Core)"]
            direction TB
            M1["Hardcoded Agent Loop"] --> M2["Built-in Session State"]
            M1 --> M3["Built-in Terminal UI"]
            M1 --> M4["Hardwired Sandbox"]
            M1 -.->|"Only customizable layer"| M5["Custom Tools & Prompts"]
        end
    
        subgraph CordisHarness["DeepSeek Harness on Cordis (Micro-Kernel)"]
            direction TB
            K["Cordis Kernel\n(Context + Events + Lifecycle)"]
            
            K --- P1["ctx.llm\nModel Adapter"]
            K --- P2["ctx.agentLoop\nAgent Driver"]
            K --- P3["ctx.sessions\nAppend-Only Log"]
            K --- P4["ctx.sandbox\nContainer / MicroVM"]
            K --- P5["ctx.tools\nTool Registry & MCP"]
            K --- P6["ctx.agents\nLive Agent Registry"]
        end
    
        Monolith ~~~ CordisHarness

    This design did not start in the AI lab. Cordis ran in production for four years powering the Koishi chatbot ecosystem across more than 4,000 community plugins before DeepSeek applied it to agent harnesses.

    I am still reading through the paper myself, working through the formal theory of effects and coeffects. Rather than declaring this pattern superior to simpler agent loops, let us examine how the mechanics work, what trade-offs they introduce, and what we can learn from this direction.

    The Theory: Spatial and Temporal Composability

    The core argument of the DeepSeek and Peking University paper is that dynamic software systems fail at composability in two distinct dimensions: time and space.

    flowchart LR
        subgraph Spatial["Spatial Composability (Coeffects)"]
            A[Plugin declares required services] --> B[Cordis resolves DAG]
            B --> C[Plugin activates only when context is ready]
        end
    
        subgraph Temporal["Temporal Composability (Revertible Effects)"]
            D[Plugin executes side effect] --> E[Runtime tracks inverse action]
            E --> F[Plugin unloads: effect unwinds with zero leftovers]
        end

    1. Temporal Composability via Revertible Effects

    In standard software, installing a capability is easy; removing it cleanly is hard. When you disable a plugin, it often leaves active interval timers, dangling event listeners, open network sockets, or stale prompt injections in memory.

    The paper formalizes revertible effects: every transformation applied to the shared execution context must have a mathematically defined inverse that the runtime tracks. When a plugin unloads, the runtime executes that inverse.

    import { Context, Service } from 'cordis'
    
    export class MonitoringService extends Service {
      constructor(ctx: Context) {
        super(ctx, 'monitoring', true)
      }
    
      protected start() {
        // Revertible effect: the returned closure is the tracked inverse
        this.ctx.effect(() => {
          const timer = setInterval(() => this.collectMetrics(), 10000)
          return () => clearInterval(timer)
        })
    
        // Event listener: automatically unbound on plugin unload
        this.ctx.on('tool/execute', (event) => {
          this.recordLatency(event)
        })
      }
    }

    When this plugin unloads, Cordis runs the cleanup closure and unbinds the event listener. The process returns to its exact prior state.

    2. Spatial Composability via Reactive Coeffects

    In type theory, effects describe what a program produces (e.g. logs, network calls, state mutations). Coeffects describe what a program requires from its environment to execute (e.g. specific services, configurations, credentials).

    Cordis treats plugin dependencies as reactive coeffects. A plugin declares what it demands:

    export class ToolExecutionPlugin extends Service {
      // Coeffect requirements: requires both ctx.tools and ctx.sandbox
      static inject = ['tools', 'sandbox']
    
      constructor(ctx: Context) {
        super(ctx, 'toolExecution', true)
      }
    }

    Cordis monitors the context tree. When ctx.tools and ctx.sandbox become available, the plugin activates automatically. If the sandbox plugin crashes or unloads, downstream plugins pause or deactivate until the dependency returns. There is no manual boot order to configure.

    How DeepSeek Harness Implements the Model

    In DeepSeek Harness, the Cordis kernel contains zero AI logic. It does not know what an LLM token is. Instead, it exposes a typed Context where packages register services and listen to events.

    PackageResponsibilityContext Key
    core/sessionAppend-only SessionEvent log and storectx.sessions
    core/system-promptDynamic prompt-section and tool-schema assemblyctx.systemPrompt
    core/toolsScoped tool registry and guarded execution pipelinectx.tools
    core/agentLive agent registry and lifecycle eventsctx.agents
    core/agent-loopDefault execution driver implementing step turnsctx.agentLoop
    llm/llmModel stream abstraction and provider adaptersctx.llm
    core/sandboxProcess isolation, container, and microVM boundariesctx.sandbox

    Four Typed Dispatch Modes

    Communication across plugins relies on an event bus with four explicit dispatch modes:

    ModeAwaited?Execution OrderReturn Value?Behavioral Semantic
    emitNoRegistration orderNoFire-and-forget notifications (telemetry, background logging).
    waterfallNoAround-middleware chainYesInterception middleware ((...args, next)). Can mutate, delegate, or short-circuit.
    parallelYesConcurrent (Promise.all)NoAwaited broadcast across independent listeners.
    serialYesRegistration orderYesDecisive sequential gates (e.g. agent/turn-stopping).

    The waterfall mode allows plugins to wrap core decisions:

    // Intercepting prompt messages before the model sees them
    ctx.waterfall('agent/pre-step', async (session, messages, next) => {
      if (violatesPolicy(messages)) {
        // Short-circuit: stop the turn before calling the model
        return { status: 'rejected', reason: 'Blocked by policy' }
      }
      const sanitized = sanitize(messages)
      return next(session, sanitized)
    })

    The Turn and Step Lifecycle

    DeepSeek Harness defines a structured execution pipeline:

    turn/start
    
      ├─ Claim queued input from inbox
      ├─ Assemble prompt sections + tool schemas (ctx.systemPrompt)
    
      ├─► agent/pre-step (waterfall) ──► [reject / enter(messages)]
      │     └─ If rejected: close turn immediately (0 steps spent, logged)
    
      ├─► step/start
      │     ├─ Append entered messages to session log as user/message
      │     ├─ deriveMessages(): Project model history from the immutable log
      │     ├─ agent/request (waterfall)
      │     ├─ llm/stream (waterfall) ──► assistant/chunk* ──► assistant/message
      │     ├─ tool/call* ──► tools/pre-execute ──► tools/execute ──► tools/post-execute ──► tool/result*
      │     └─ step/end
    
      ├─ Check continuation: tools owe another request OR queued input arrived?
      │     ├─ If YES: jump back to step/start (next step)
      │     └─ If NO: proceed
    
      ├─► agent/turn-stopping (serial gate)
    turn/end

    Capability Seams and Append-Only Logs

    Two additional engineering choices stand out in the architecture:

    1. Capability Seams

    A seam separates a Service Definition (interface), a Service Provider (implementation), and Consumers (tools).

    Because file access (ctx.fs), subprocess execution (ctx.subprocess), and terminals (ctx.terminals) share a common seam, changing the provider from local Node.js to a remote microVM moves all tools, file reads, and language servers into the container simultaneously. Consumer code remains untouched.

    2. “Model-Visible Means Logged”

    The harness enforces an invariant: anything that reaches the LLM must be reconstructable from the append-only event log.

    • The runtime does not mutate an in-memory chat array. The deriveMessages() function computes context directly from immutable SessionEvent records.
    • Sub-agents branch cleanly via ctx.sessions.fork(parentSessionId, boundaryEventId) without cloning process heaps.
    • Deterministic replay allows developers to step through historical execution runs event by event.

    Why This Pattern Matters: The Self-Modifying Agent

    Why go through the trouble of building formal spatiotemporal composability into an AI agent harness?

    The most compelling answer is runtime self-modification.

    In a traditional agent framework, if an agent writes a new tool or scripts an integration during a long-running task, it cannot mount that tool without restarting its process. Restarting wipes ephemeral memory, drops open network sessions, and resets execution state.

    Under Cordis:

    1. The agent writes a new TypeScript tool plugin.
    2. The harness loads the plugin into the live context at runtime.
    3. Cordis checks the plugin’s coeffects, mounts the tool, and automatically updates the system prompt assembly for the next step.
    4. If the tool fails or finishes its purpose, the agent unloads the plugin. Cordis unwinds the effect tree with zero leftover memory or socket leaks.

    The agent modifies its own runtime in-flight while preserving active session history.

    Open Questions and Engineering Trade-Offs

    While the architecture is elegant on paper, it introduces real trade-offs that teams should weigh:

    DimensionMonolithic Loop (e.g. Pi / Minimalist Harness)Spatiotemporal Micro-Kernel (Cordis / dsh)
    Conceptual overheadLow: read one linear loop fileHigh: understand contexts, seams, and dispatch modes
    DebuggabilitySimple stack traces and breakpointsNon-linear event graphs across waterfall chains
    Dynamic safetyCompile-time static guaranteesRuntime dependency resolution in TypeScript
    ExtensibilityFork or subclass internal codeDeclarative plugin mounting via config patches
    Self-modificationDifficult without process restartNative support for in-flight tool mounting and unwinding

    Three practical questions remain as this pattern encounters production adoption:

    1. Debugging Indirection: When behavior is distributed across multiple waterfall listeners, tracing why a prompt was altered or a tool call was rejected requires dedicated event-graph tooling.
    2. Language Boundaries: Cordis is written in TypeScript. Bringing this level of dynamic effect unwinding to environments like Python or Rust requires different runtime primitives (e.g. explicit RAII guards or actor systems).
    3. Complexity Budget: For focused coding agents with fixed tool sets, a 200-line linear loop like HuggingFace’s Tau remains significantly easier to audit and reason about.

    The Bottom Line

    DeepSeek Harness and the Cordis paper represent a deliberate shift in agent system design: treating agent harnesses not as static scripts around an LLM API, but as dynamic operating systems for hot-swappable capabilities.

    I am still working my way through the mathematical details and operational calculus in the paper. We do not need to rush to declare this architecture superior or inferior to simpler, linear agent loops. Instead, we should wait and watch where these patterns head: whether dynamic plugin trees become the standard foundation for self-modifying agents, or if linear simplicity remains the preferred choice for production stability.

    For now, the paper gives us a clear vocabulary for understanding what true modularity in agent runtimes requires.


    Observing how agent architectures evolve, or experimenting with plugin runtimes for autonomous systems? I would love to hear your perspective. Reach out on LinkedIn.