cordispy
A Python realization of the runtime described in A Programming Paradigm for Spatiotemporal
Composability (Shi, Zhang, Cui -- DeepSeek-AI / Peking University). The paper's reference
implementation is the TypeScript project Cordis; the paper
itself is at github.com/cordiverse/paper. This port targets
Python 3.11+ and asyncio, follows the paper's algorithms directly, and is documented against the
paper's section numbers throughout, so a reader of either source can cross-navigate to the other.
pip install cordispy # or: uv add cordispy
import cordispy
Distribution and import package are both cordispy. The plain name cordis is taken on PyPI by an
unrelated 0.0.0 placeholder published in 2024, which ships a top-level cordis package of its own -- do
not install that one.
The problem
Uninstalling a VS Code extension does not uninstall it. VS Code runs every extension in one shared
host process, and once an extension's activate function has run there is no way to unload just its
code -- disabling or removing it requires restarting the whole host, taking every other loaded
extension down with it. Among the top 100 extensions by install count, 87 contain executable code and
therefore force that restart on removal (paper section 1.2.1). The deactivate hook VS Code does
provide only runs at process shutdown, and because it is written separately from activate it can
only clean up what its author remembered to mirror there.
The same install base shows the second half of the problem. VS Code lets an extension declare a dependency on another extension, but almost nothing does: only 7 of those top 100 extensions declare a dependency on a non-built-in extension (paper section 1.2.1, same source). Extensions default to depending on nothing because the platform gives them no structural way to depend on something safely -- the mechanism that exists hands back an untyped value with no contract and no notion of "this went away."
Both gaps have the same shape: nothing in the platform tracks what a piece of code did to the running
system, so nothing can undo it precisely, and nothing tracks what it needs from the rest of the
system, so nothing can react when that need stops being met. This runtime is built around closing
both gaps at the level of a single Python object -- a Context -- rather than at the level of a whole
process.
Two dimensions
Every component in this runtime is described along two independent axes, matched to two paper sections:
- Temporal (paper section 5.1.1, Algorithm 1). A component never mutates anything directly; it
performs an effect through
ctx.effect(callback), and the callback hands back the inverse that undoes it. Unloading a component runs every inverse it accumulated, most-recent-first, so the system returns to exactly the state it was in before the component was composed in. - Spatial (paper section 5.1.2, Algorithms 2, 3 and 6). A component declares what it needs
(
inject) and what it offers (ctx.set, after declaringprovide). It never looks anything up by hand: the runtime activates it the moment its declared dependencies are satisfied, deactivates it the moment they stop being satisfied, and keeps a withdrawn dependency readable for exactly as long as the component's own teardown needs to see it.
The diagram below is not the runtime's class diagram; it is the shortest path from a paper mechanism to the call that realizes it. Read left to right: a temporal fact about a component (an effect happened, and it has an inverse) turns into a runtime call on the left, and a spatial fact (a value is published, or needed) turns into one on the right.
graph LR
subgraph Temporal["temporal dimension: WHEN (section 5.1.1)"]
T1["ctx.effect(callback)"] --> T2["inverse composed LIFO"]
T2 --> T3["fiber.dispose() runs it, once, on unload"]
end
subgraph Spatial["spatial dimension: WHERE (section 5.1.2)"]
S1["ctx.set(key, value)"] --> S2["binding installed in the store"]
S2 --> S3["notify() reloads every dependent whose inject names the key"]
end
What to take from it: the two dimensions share one primitive. ctx.set is itself implemented as a
ctx.effect call (its inverse withdraws the binding), and ctx.use -- instantiating a component -- is
also a ctx.effect call whose inverse retires the fiber. Nothing in this runtime mutates the world
outside that one tracked operation, which is what makes "unload" a well-defined, total operation
instead of a best-effort convention. docs/paradigm.md develops both dimensions in full.
The three tiers
The code is layered the way the paper lays out Section 5: a core library that implements the effect and coeffect systems directly, a declarative loader built on top of it without touching it, and application code -- including the demo used throughout this repository's docs -- built on both. Read the diagram bottom to top: everything above a tier is built using only that tier's public calls, never its internals.
graph TD
subgraph Application["application tier: examples/"]
H_echo["tool_echo"]
H_kv["tool_kv"]
H_server["server (provides server)"]
H_store["store_memory / store_sqlite (provide store)"]
H_bus["events (provides bus)"]
H_timer["timer (provides timer)"]
H_echo --> H_server
H_echo --> H_timer
H_kv --> H_server
H_kv --> H_store
H_kv --> H_timer
H_kv --> H_bus
H_server --> H_bus
end
subgraph Loader["loader tier: cordispy.loader (paper section 5.2)"]
L_loader["Loader / Entry -- declarative reconciliation"]
L_group["group component -- keyed diff of children"]
L_hmr["Hmr -- transactional module reload"]
L_loader --> L_group
L_loader --> L_hmr
end
subgraph Core["core tier: cordispy (paper section 5.1)"]
C_ctx["Context"]
C_fiber["Fiber"]
C_registry["Registry"]
C_effect["effect / execute"]
C_ctx --> C_fiber
C_fiber --> C_registry
C_ctx --> C_effect
end
Application --> Loader
Loader --> Core
Application -. "or straight onto the core, as examples/harness does" .-> Core
What to take from it: examples/harness/, the demo application used by docs/benefits.md and
docs/plugin-authoring.md, is composed straight on the core tier with ctx.use -- it does not go
through the loader at all. examples/run_loader.py is the one example that exercises the loader tier,
reading a declarative YAML/JSON document instead of calling ctx.use by hand. Both are legitimate: the
loader is an optional convenience over the core, never a requirement of it.
Quickstart
This runs as shown -- copy it into a file and run it with uv run python, or paste it into python -m asyncio.
import asyncio
from cordispy import Context, plugin
@plugin(name="store", provide=["store"])
def store(ctx, config):
ctx.set("store", {})
@plugin(name="counter", inject=["store"])
def counter(ctx, config):
ctx.store["hits"] = 0
return lambda: ctx.store.pop("hits", None)
async def main():
root = Context()
provider = root.use(store)
consumer = root.use(counter)
# One activation can start another, so wait for the whole runtime to
# settle rather than for one fiber.
await root.registry.settle()
assert consumer.state.name == "ACTIVE"
assert root.get("store") == {"hits": 0}
await provider.retire() # the consumer deactivates by itself
assert consumer.state.name == "INACTIVE"
assert root.get("store") is None
asyncio.run(main())
Nothing here names an order. counter could have been composed before store -- it would simply have
stayed PENDING until store arrived. Retiring the provider does not touch counter directly; it
recomputes its own target, finds store gone, and unloads itself. docs/paradigm.md and
docs/architecture.md walk through exactly how.
Work from a checkout
pip install cordispy is enough to use the runtime. The examples, the guided demos and the test suite
below live in the repository rather than the wheel, so clone it to run them. From the repo root:
uv venv
uv sync --all-extras
uv run pytest -q # 139 passed
uv run python examples/run_benefit.py --scenario all
uv run ruff check .
uv run mypy src/cordispy
The only runtime dependency is pyyaml, used by the loader for .yaml configuration files; a .json
configuration loads through the standard library alone, so the loader works even where PyYAML is not
installed.
The examples
| File | What it shows |
|---|---|
examples/run_effects.py |
The five accepted effect-callback forms, LIFO recovery, the guard halting an in-flight iterator at a step boundary, and the built-in timer/bus plugins leaving zero residue. |
examples/run_services.py |
Activation independent of composition order, the ordering guarantee (a withdrawn dependency stays readable through the dependent's own teardown), the two property-access rejections, and realm isolation. |
examples/run_hotswap.py |
Swapping the store provider under a running consumer: the consumer deactivates, the old connection closes, the consumer reactivates against the new binding, and requests keep being served throughout. |
examples/run_benefit.py |
The centerpiece: the same application built on this runtime and on a conventional plugin registry, with the residue after unload, a hot-swap, a late-arriving dependency, and a mid-load failure measured on both. See docs/benefits.md. |
examples/run_calculator.py |
A calculator whose every arithmetic operation is a plugin. Removing one, composing a derived one before its dependencies, and one that fails mid-load -- measured by whether the calculator still offers operations it can no longer perform. |
examples/run_loader.py |
The declarative loader reconciling four revisions of a YAML/JSON configuration incrementally, then hot-replacing a plugin module on disk -- once successfully, once into a transactional rollback. |
Run any of them with --help for its options; every CLI in this repository uses argparse with named
flags only.
Guided demos
tests/UAT/ pairs each example with a copy-paste walkthrough guide and a script
that runs the guide's own commands and asserts every expectation it states. Use them to demo the runtime
to someone, or to check a change by hand:
./tests/UAT/benefit.sh --auto # the centerpiece comparison, asserted step by step
./tests/UAT/run-all.sh # all five guides, in order
The demo pages
Two self-contained pages run the comparisons as interactive models, for showing the paradigm to someone without a Python environment in front of them. Open either straight from a clone in any browser -- they need no server and load nothing from the network.
docs/demo.html is the residue comparison: retire a component and count what survives.
docs/demo-calculator.html is the calculator -- toggle arithmetic operations
as plugins, type an expression, and watch which of the two keeps advertising an operation it can no longer
perform.
Each has a ten-step guided walkthrough shaped like the matching tests/UAT/*.sh guide -- every step states
why it exists and what to expect, applies one action to both runtimes, and asserts the outcome against the
live model -- plus a free-play mode for driving the two side by side yourself. The numbers they report are
the ones the demos print: 13 resources acquired on both sides against 0 residue and 9, and a calculator that
goes on offering % after the division its remainder is defined in terms of has been removed.
Documentation
docs/paradigm.md-- the theory-to-runtime mapping: effects, coeffects, and the five callback formsctx.effectaccepts.docs/architecture.md-- internals: the effect engine, the fiber state machine and inertial chaining, two-layer coeffect resolution, and a provider hot-swap traced step by step.docs/plugin-authoring.md-- how to write a component:@plugin,inject/provide,ctx.keyvsctx.optionalvsctx.get, the built-intimerandbusservices, and the demo harness's dependency topology.docs/benefits.md-- the measured naive-vs-cordis comparison, with the literal output ofrun_benefit.py.docs/source-review.md-- the vocabulary map against the shipped TypeScript, and seven defects the source review found and this port fixes, each with a file:line.docs/limitations.md-- what this port deliberately does not implement, and why.
Acknowledgements
This port contributes no ideas of its own. The paradigm, the algorithms and the evidence that they hold up in production all come from the three projects below, and I am grateful to their authors for publishing the work in a form complete enough to reimplement from.
- github.com/cordiverse/paper -- A Programming Paradigm for Spatiotemporal Composability, by Shi, Zhang and Cui (DeepSeek-AI / Peking University). Thank you for the formal model: effects and their inverses, coeffects and inertial deactivation, and the numbered algorithms that this port implements line for line. The section numbers cited throughout this repository are theirs, and a paper precise enough to be read as a specification is a rare gift.
- github.com/cordiverse/cordis -- the reference TypeScript
implementation. Thank you for a running system to check the reading against; every semantic decision
here was settled by consulting
packages/corev4.0.0-rc.8 alongside the paper, and much of what looks obvious in this codebase only looks that way because that implementation had already resolved it. Wheredocs/source-review.mdrecords a divergence, it is this port choosing the paper's vocabulary for a port whose purpose is demonstrating the paper -- not a criticism of a project that owes the paper no such duty. - github.com/deepseek-ai/deepseek-harness -- a production agent harness built on this design (paper section 1.2.2). Thank you for carrying the paradigm into real workloads and shipping the result publicly; it is the reason this can be described as a proven design rather than an elegant proposal.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cordispy-0.0.1.tar.gz.
File metadata
- Download URL: cordispy-0.0.1.tar.gz
- Upload date:
- Size: 210.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e12cae7fc97b5f4812b78ed6d8cbc09c0f6a7e40405480c09ba0a207babae049
|
|
| MD5 |
f2bac22d084b871059a5fc6f7f354751
|
|
| BLAKE2b-256 |
e95dc1cfe22e54e32c62a93cfba48aa10a48ff26f10174bbbd230e061e13b6e2
|
Provenance
The following attestation bundles were made for cordispy-0.0.1.tar.gz:
Publisher:
publish.yml on s2005/cordispy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cordispy-0.0.1.tar.gz -
Subject digest:
e12cae7fc97b5f4812b78ed6d8cbc09c0f6a7e40405480c09ba0a207babae049 - Sigstore transparency entry: 2480079758
- Sigstore integration time:
-
Permalink:
s2005/cordispy@237b40e31a796e805bae36d6ec8c6e534745ebe4 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/s2005
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@237b40e31a796e805bae36d6ec8c6e534745ebe4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cordispy-0.0.1-py3-none-any.whl.
File metadata
- Download URL: cordispy-0.0.1-py3-none-any.whl
- Upload date:
- Size: 59.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29db32e225115d7e6b3b43caaa2dc6c2c2a29953bd74c03bf11dcee56dcb6667
|
|
| MD5 |
633f4963175f5a0e848252f49f4d5059
|
|
| BLAKE2b-256 |
a3eed5bf881a0ba0d86452beadd9786f565e20aad9b43e70d5006a4d81e046a6
|
Provenance
The following attestation bundles were made for cordispy-0.0.1-py3-none-any.whl:
Publisher:
publish.yml on s2005/cordispy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cordispy-0.0.1-py3-none-any.whl -
Subject digest:
29db32e225115d7e6b3b43caaa2dc6c2c2a29953bd74c03bf11dcee56dcb6667 - Sigstore transparency entry: 2480080001
- Sigstore integration time:
-
Permalink:
s2005/cordispy@237b40e31a796e805bae36d6ec8c6e534745ebe4 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/s2005
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@237b40e31a796e805bae36d6ec8c6e534745ebe4 -
Trigger Event:
push
-
Statement type: