Yifan Shi · Wei Zhang · Tianyi Cui — Peking University / DeepSeek-AI

Spatio-
temporal
Composability.

A programming paradigm for software that keeps changing while it runs — without leaving stale effects behind, and without breaking the components that depend on it.

An 88-page formal paper, reconstructed. 65 numbered equations · 18 theorems · 5 contributions · one runtime: Cordis.
Production case study: Koishi, 4000+ community plugins.
01 / 12
01 — THE CLAIM

Two independent guarantees.

Modern dynamically composed software needs two separate properties that neither implies the other.

Temporal composability
Leave cleanly.

When a component leaves, its contribution is removed without residue — and without destroying unrelated work performed meanwhile.

Spatial composability
Rebind safely.

When dependencies appear, disappear, or are replaced, consumers react coherently: activate only when satisfied, hold a stable resolution while running, deactivate before providers withdraw.

The two runtime ideas
Core constructions
revertible effect = forward context transformation + explicit inverse
reactive coeffect = declared requirement + runtime re-resolution + lifecycle reaction
The component model, in plain words
Component = Requirements × Provisions × Effects-with-undo

Symbolically written DΓ × PΓ × E*Γ. The paper connects these to the classical duals of effects and coeffects — but moves them from compile-time type reasoning into runtime mechanisms.

  1. The five contributions — revertible effects.
  2. The five contributions — reactive coeffects.
  3. The five contributions — a unified context paradigm.
  4. The five contributions — a dynamic composition calculus with system-level metatheory.
  5. The five contributions — a practical Cordis implementation with reconciliation & HMR.
02 — COMPOSABILITY

Static is easy. Dynamic is the hard part.

Static systems resolve calls, imports, and inheritance before or at startup. Dynamic systems load, unload, and reconfigure pieces while continuing to run.

VS Code plugin systems

Supports dynamic extension install, but true fine-grained live unloading of arbitrary executable code is absent — a process restart is still the coarse boundary. Teardown lives in a separate place from setup, making cleanup a discipline, not a structure.

Self-evolving agent harnesses

Directly relevant to ACRYL. Future harnesses may continuously generate and deploy modifications to themselves while still serving requests: tool suites, execution envs, permissions, session persistence, memory, subagent orchestration, UI.

Coarse-grained workaround

OSes give temporal composability at process granularity; orchestrators give spatial composability at service granularity. But that is too coarse when composition happens inside one process — restarts destroy caches and partial work; services add network overhead.

Granularity match. Effects and dependency coordination should live at the same level as the components themselves. That is the paper's thesis.
03 — EFFECTS & COEFFECTS

Two halves of one duality.

Classical effect / coeffect theory supplies the vocabulary, but it is largely static. The paper reifies it into runtime-operable context objects.

Effects
What it does.

Effects characterize what a computation does to the environment: resource allocation, event registration, timers, state mutation, service registration, dependency publication.

Coeffects
What it needs.

Coeffects characterize what a computation needs from the environment: permissions, resources, services, contextual data.

The conceptual move
SOURCEStatic annotation

Type-level effect / coeffect metadata.

MOVEReification

Turn it into an explicit, first-class object.

RESULTRuntime context

Operable when the dependency graph is live.

04 — REVERTIBLE EFFECTS

Every mutation pairs with its inverse.

Ordinary impure code is re-expressed as a pure transformation of an explicit context, then paired with a left inverse that recovers the prior state.

Setup order is reversed at teardown

Because teardown must undo setup, the inverse of a composed operation is the reverse composition of its inverses.

/* twisted composition (4) */
(f₁, g₁) ∘ (f₂, g₂) := (f₁∘f₂,  g₂∘g₁)

/* effect context (5): state + recovery accumulator */
∂Γ := Γ × (Γ → Γ)
   (γ, φ)  —  current state + prior-state recoverer

/* tracking (6): extend the accumulator with the inverse */
track(f, g)(γ, φ) = ( f(γ),  φ∘g )

/* recovery (9): run the accumulator, reset */
recover(γ, φ) = ( φ(γ), id )
The soundness invariant

A correctly tracked effect does not change what full recovery returns. For any sequence of tracked effects, recovery still returns the original state.

/* sequence recovery (11) */
recover( track(fₙ,gₙ) ∘ … ∘ track(f₁,g₁)(γ,φ) )
  = recover(γ, φ)

/* inverse stability — the deepest requirement */
φ(γ) = γ₀
   or, later, observational:
φ(γ) ≈ γ₀
The hard case: interleaved effects

The real difficulty is when two components interleave — an inverse may run after unrelated effects have moved the state. The paper requires independence between effects: their forward maps and every potential inverse must commute (18) and not change which inverse the other yields (19).

  1. Theorem 20 — under pairwise independence, one effect can be removed from an interleaved sequence, leaving exactly the state the sequence would have reached without it.
  2. Corollary 21 — under independence, inverses may be applied in any permutation and still recover the original state.
05 — REACTIVE COEFFECTS

Declare needs; react coherently.

Dependency injection is formalized as a typed partial map, and publishing a dependency is itself a tracked revertible effect.

(20)Σ := (k : K) ⇁ V_k

Dependency context: a typed partial map from keys to values.

(24)σ ⊨ d ⟺ ∀k ∈ d : k ∈ dom(σ)

Satisfaction — a component's requirement set is satisfied when every declared key is present.

(26)notify_d(σ, σ′) := activating | deactivating | neutral

Every context transition is classified against the requirement set, driving activation or deactivation.

Isolation — ad-hoc polymorphism

A logical key resolves to a realm, then the realm resolves to a value (27). Isolate the same key differently per subtree — per workspace, session, account, or sandbox.

/* (28) get through realm indirection */
get(k)(ρ, σ) = σ( ρ(k) )
isolate(k, r)(ρ, σ) = ( ρ[k↦r], σ )
Interception — attenuate a capability

Metadata changes how a capability may be used rather than which provider satisfies it (29) — the basis of a capability-based access model.

/* (30) metadata-merged get */
get(k, μ)(ι, σ) = σ(k)( μ ⊕ₖ ι(k) )
intercept(k, ν)(ι, σ) = (ι[k↦ι(k)⊕ₖν], σ)
Local guarantee. Activate only in a state satisfying the declared requirements; check every transition; loss of satisfaction drives deactivation. But this alone does not pin down provider withdrawal order — the global calculus in Section 4 handles that.
06 — CONTEXT PARADIGM

The context is the mediated carrier.

Effects and coeffects unify into a recursive context that mediates both mutation and dependency access — and it makes recovery a derived global behavior, not per-component discipline.

(31)Γ∞ := μΓ. Γ × (Γ → Γ) × Σ

Unified context = recursive state × inverse accumulator × coeffect environment. The recursion lets a parent own child contexts and aggregate their removal.

Observational equivalence (32)
σ ≈ σ′ ⟺ same domain ∧ every key observably equal

Exact physical equality after rollback is often impossible or unnecessary. Freed memory need not return to the same layout; a fresh ID need not be numerically identical. What matters is that the recovered state is observationally indistinguishable through the context's operations.

Functional explicit-state

Easy to reason about, but invasive — threading state through every call.

Imperative ambient-state

Ergonomic, but dependencies and effects are hidden.

Context paradigm

An explicit first-class context mediates both mutation and dependency access.

Locality of concern. The author writes local facts — an effect and its inverse together, a requirement declared once. The runtime composes cleanup and rewiring globally. Local input; global behavior.
07 — THE CALCULUS

A whole-system model of many interleaving components.

Section 3 proves local properties. Section 4 builds an operational semantics where fibers interleave, drain, reload, and fail.

Component = (requirements, provisions, effect) — (37)
CΓ := DΓ × PΓ × E*Γ

A fiber is a live instantiation: ⟨d, p, e, π, σ, τ, θ⟩ — requirements, provisions, effect, parent, provided table, retirement flag, lifecycle state.

Lifecycle states
INACTIVE

Not mounted, or failed.

LOADING

Activation and setup in progress.

ACTIVE

Current valid episode, holding a committed view.

UNLOADING

Draining dependents, running inverses.

DISPOSED

Runtime identity permanently gone.

Target = provider identity, not value

(41) The target records exactly which provider identity should satisfy each requirement. Two providers may expose equal values yet be different lifecycles. Replacing a provider with a new instance therefore triggers a dependents' reload.

Desired-state reconciliation

(42) Quiescence means: actual lifecycle state matches the resolved target state. The orchestrator only requests existence / retirement — it never directly forces a component active. Desired composition is input; activation state is derived.

The strongest practical idea
Dependency-aware drain (46) — relied-upon
1ACTIVE

Provider is serving consumers.

2L-Leave

Enters UNLOADING — no longer advertised.

3Wait relied = false

Dependents see new targets and begin their own teardown.

4Run accumulator

Only once consumers are done does the provider execute its inverse.

5INACTIVE

Gone, cleanly.

The provider's binding stays readable through each consumer's committed view while dependents drain — the implementation of Theorem 63.

Iteration

Multi-step activation yielding an inverse per step (47), enabling partial rollback if interrupted mid-activation.

Asynchrony

In-flight work is inertial — it must land, record its inverse, then chain into unload if the target became stale.

Failure

(49) A failure adds no new inverse, but earlier steps still recover — transactional activation, not a try/catch.

08 — METATHEORY

From runtime rules to formal guarantees.

The metatheory is where the paper converts plausible mechanisms into system-level guarantees that hold under interleaving.

Safety floorPreservation

Every rule preserves well-formedness (59): parents exist, provisions disjoint, committed views point to installed fibers.

TemporalRecovery

(56–57) Removing one fiber leaves other fibers' independent work intact, and a closed episode leaves no observable residue.

SpatialOrdering

(58–59) Provider activates before consumer; consumer drains before provider's inverse; no activation mixes provider versions.

ClimaxConfluence

(73) Same orchestration inputs yield the same quiescent observable state as a canonical from-scratch assembly.

Main results, and what each buys
ResultEstablishesRuntime consequence
T20independent effect removable after interleavingunrelated effects survive component removal
T40distinct-key operations are independentfine-grained capability keys naturally commute
T59rules preserve well-formednessruntime does not corrupt invariants
T61 / C62recovery exactness + terminal recoveryfiber removal leaves no observable residue
T63 / T64provider-consumer ordering + resolution coherencedependents drain before provider inverse; no mixing
T66progress + terminationreconciliation reaches quiescence under acyclicity
T73confluence / canonical formfinal state depends on final composition, not the valid schedule
A history of experimentation should not permanently contaminate the final state.
09 — CORDIS

The theory, mapped almost literally to code.

Cordis turns the formalism into a TypeScript runtime: context, fibers, effects, injection, isolation, interception, a loader, and transactional HMR.

Theory → Cordis
TheoryRuntime
Γ∞ unified contextctx
effect liftingctx.effect(callback)
Σ dependency mapctx.get · ctx.set
isolation / interceptionctx.isolate · ctx.intercept
component instancefiber
requirements dfiber.inject
provisions pcomponent provide
activation effect efiber.apply
accumulator gfiber.dispose
committed view ωfiber.committed
targetfiber.target
Effect tracking
inverse = identity
for each successful effect step:
    inverse = yieldedInverse ∘ inverse
return inverse

The armed guard makes disposal idempotent; child disposers compose into parents. Crucially, Cordis does not mechanically prove the inverse is correct — TypeScript trusts the author for that local obligation.

Lifecycle = mutual chaining

A provider in UNLOADING stops counting as available before its bindings are removed — dependents start teardown while still reading the old binding through their committed view.

reload may chain → unload
unload may chain → reload
Declarative loader

Config becomes the authoritative description of desired composition; reconciliation maps each change to the least disruptive fiber op.

urlchange → rebuild

Rebuild the component.

disabled→ unload / reload

Operational toggle.

isolate→ realm reassignment

Move scope safely.

config→ update / diff

Component-specific update.

HMR is transactional
CLASSIFYAccepted

Changed modules; external ones declined.

STALEDetect

Walk dependency closure to declined boundaries.

RELOADRestore

Invalidate caches, dispose old, import new — and roll back on failure.

Unlike conventional HMR acceptance boundaries written by developers, Cordis can use fiber boundaries because every component's effects are already bounded and reversible.

Case study
ExpressivenessKoishi

Full applications built from these primitives, server-side and browser-console alike.

Generality4000+

Community plugins; disabling/reloading preserves unrelated live state; unavailable dependencies leave consumers inactive rather than crashing.

10 — LIMITS

What is not magic.

The discussion contains the most practically important caveats — and they matter most for an agent runtime.

System boundary: acquisition vs emission

The theory's Γ is bounded by what the runtime can exclusively control and restore.

Acquisitionopen · malloc · fork

Trackable — the handle can usually be released.

Emissionwrite · send · charge · email

Once crossed the boundary, a true inverse may not exist.

Strategies: withholding (delay emission until local state commits) or compensation (delete / refund to restore application-level equivalence).

Access control is not sandboxing

Dependency declarations form a capability-like model; interception attenuates it. But the paper is explicit: language-level mediation is not a sandbox against malicious code. If untrusted code can reach host objects directly it bypasses the context. Real isolation needs SFI, a separate runtime, a sandboxed process, or a VM/container/WebAssembly boundary.

Two layers. Capability context = policy & authority model. Sandbox boundary = enforcement against hostile code.
Service multiplexing

Exclusive binding perturbs dependents on every switch. A stable broker stands the capability to consumers while providers come and go behind it — for rolling updates and load balancing.

Dependency typing

Bare keys break under interface drift and key collision. Mitigate by namespacing, peer dependencies, or structural compatibility.

Component granularity

Dependency cycles leave components permanently inactive — detectable from declarations. Factor into smaller core + integration components, though bindings can grow quadratically.

Co-design
A language co-designed for the paradigm

Make context implicit while preserving semantics; compile effect iterators into state machines; make coeffect declarations part of the type system — enabling compile-time cycle detection and structural checks.

An OS co-designed for the paradigm

Make declared dependencies the component's entire reachable authority (as a WebAssembly module receives explicit imports), and make more resources natively revertible: transactional storage, copy-on-write, kernel-tracked acquisition.

11 — ACRYL EXTRACTION

A composition kernel for a live, self-modifying runtime.

The paper is not an agent runtime — it is a possible composition kernel. Translated, it argues for a much cleaner ACRYL boundary than wiring agent integrations into the product core.

The component contract
# from Eq. (37), literally
component:
  id: memory.hindsight
  requires: [ workspace.fs@1, persistence.sqlite@1 ]
  provides: [ memory.semantic@1 ]
  apply:
    - open database
    - start indexer
    - register memory provider
  # each atomic mutation yields its own disposer

The runtime should not accept a component as merely start() / stop(). It should mediate environmental effects so teardown is derived from setup.

Context = the persistent scene

The strongest translation of Eq. (31): ACRYL Context holds current hierarchical context, rollback accumulators, capability environment, session/task state, workspace identity, permissions, and child fiber contexts.

The scene persists. The actors / providers can change.
Agent handoff = provider replacement
BEFOREClaude active

Session consumer holds committed view to Claude.

UNLOADINGClaude drains

New targets stop resolving Claude.

ACTIVECodex loads

Consumer target switches to Codex.

AFTERReload

Consumer re-activates against one coherent new view.

Define capabilities like agent.execution@1, agent.streaming@1, agent.tool-use@1. A session component depends on them; the committed view ω records which live agent fiber provides each.

Replaceable providersmemory.semantic@1

Hindsight · Supermemory · Mem0 · Honcho. Consumers depend only on the capability.

Replaceable providersgraph.code@1

OmniGraph · lat.md. Isolation scopes the same logical key per workspace.

Self-extensionTransactional

Agent synthesizes a component → LOADING fiber → each setup step yields an inverse → ACTIVE or rollback.

Classify every effect
Reversible

register command · enable tool · start subprocess · add watcher · mount UI

Compensatable

create remote issue → close. create cloud resource → destroy.

Irreversible

send email · post message · publish package · transfer money

Prefer to withhold irreversible emissions until the local composition has committed.

Capabilities + interception = policy plane
capability: workspace.fs@1
policy:
  read:  [ /repo/** ]
  write: [ /repo/src/** ]
  deny:  [ /repo/.env ]

Policy is supplied by the context, not the provider. But per Section 6.3 this is not a sandbox for hostile code.

Capability IDs > strings
acryl://core/memory.semantic/v1
acryl://core/agent.execution/v2
acryl://omnigraph/code.graph/v1

A public capability protocol should not rely on a bare string key. Encode namespace + version + contract, and ultimately want a structural compatibility check.

Dependency cycles = first-class diagnostics

Build the precedence graph (60) before activation and detect cycles — so two plugins aren't silently inactive. The UI can explain the cycle and suggest a decomposition into session-core + bindings.

Service brokers

Not every capability needs exclusive binding. A stable broker survives backing-provider rolling updates without reloading every consumer each time one worker changes.

12 — ADOPT

Adopt directly. Do not copy blindly.

The useful ideas are separable from the exact TypeScript implementation. Some adapt cleanly; several warnings are non-negotiable.

Adopt directly
  1. Effect ledger / inverse-first mutation API — every mutation returns its disposer.
  2. Capability declarations — every component gets explicit requires and provides.
  3. Provider identity in target state — track which instance a consumer committed against.
  4. Four-state lifecycle — inactive / loading / active / unloading, plus failure.
  5. Drain dependents before provider recovery — a kernel-level rule.
  6. Iterated setup with rollback checkpoints — especially for self-generated extensions.
  7. Desired-state reconciliation — the orchestrator changes config; the runtime derives transitions.
  8. Transactional HMR / self-update — keep old code until the new version instantiates.
  9. Context realms — for workspaces, rooms, accounts, sessions, sandboxes.
  10. Interception metadata — as a generic policy / capability attenuation.
Do not copy blindly
  1. Do not assume every effect is invertible — the paper itself does not.
  2. Do not make bare key equality the public compatibility protocol.
  3. Do not treat context-level capability access as a security sandbox.
  4. Do not force the whole application into Cordis before prototyping.
  5. Do not ignore independence assumptions — prefer fine-grained capability ownership over one global bag.
The proposed experiment
Persistent context + hot-swappable provider classes
A minimal acryl-compose kernel
  workspace.fs
  agent.execution   → OpenCodeProvider · PiProvider
  memory.semantic   → HindsightProvider · SupermemoryProvider
  graph.code        → OmniGraphProvider · LatProvider
Test scenarios
  1. Load a provider, start a consumer.
  2. Replace the provider while the consumer is active.
  3. Verify the consumer drains before the old provider disappears.
  4. Inject a failure mid-activation; verify restore.
  5. Mutate two independent providers concurrently.
  6. Unload one; verify the other's state remains.
  7. Isolate the same capability into two workspaces.
  8. Apply permission interception to one realm only.
Success criteria
temporal:  remove(A) ≈ history-with-A-omitted
spatial:   consumer sees one stable provider view
progress:  reconciliation reaches quiescence
confluence: same desired output ⇒ same observable state

Each criterion mirrors a formal guarantee from the metatheory.

The one-paragraph memory

Every component declares what it needs, what it provides, and how every environmental mutation can be withdrawn; the runtime owns the composition semantics. That moves a system from a collection of adapters and lifecycle callbacks toward a genuine live composition runtime.

Compressed
Component = Requirements × Provisions × Revertible Effects
Runtime State = Context + Fiber Registry + Committed Views
Continuous Evolution = Reconciliation + Rollback + Dependency Drain
Correctness = Recovery + Ordering + Progress + Confluence

Persist the scene / context; make every actor, provider, tool, memory system, graph system and extension a reconciled fiber whose effects are bounded and whose dependencies are explicit.