How Cordis Handles Plugin Cleanup: Comparing DSH and Pi Approaches
The article analyzes how Cordis manages plugin lifecycle issues—preventing "ghost" states after removal—by contrasting the Pi model, which delegates cleanup to extensions, with the DSH model that records cross‑plugin relationships in the runtime, detailing the mechanisms, code paths, and trade‑offs of each.
Pi: Short inner loop and clear exit boundary
Pi keeps the core minimal and lets extensions handle their own resources. When reloading, Pi sends session_shutdown, updates the pi‑ai API Provider, reloads extensions, skills, and prompts, then emits a reload ‑tagged session_start. The documentation requires extensions to close connections, file watchers, and other session‑level resources in session_shutdown and recreate them in session_start. The author notes that this works well when extensions are few and reload costs are acceptable.
However, when extensions start sharing resources—e.g., Extension A registers a capability that Extension B caches—simply shutting down A is insufficient. The dependency now spans multiple plugins, and Pi leaves the decision to the extension author.
Cordis records three basic questions
Which plugins should stay alive?
When a plugin exits, who is responsible for cleaning up its resources?
If a capability disappears, which dependent plugins must also stop?
The Loader answers the first by maintaining a target‑plugin tree from YAML, presets, and programmatic mounts. It disposes the corresponding Fiber when a plugin entry is removed, and attempts to restore the old entry if a new instance fails to start. Fiber (the plugin instance’s lifecycle container) together with ctx.effect() records disposers for connections, file watchers, etc. When a Fiber is disposed, Cordis knows which disposers to invoke.
The Service and inject mechanisms answer the third question. A plugin declares required services; if a service disappears, Cordis re‑checks dependent Fiber s, forces unsatisfied consumers to exit, and re‑activates them when the service returns.
Context limits the visibility of services, allowing the same‑named service to be overridden in a nearer scope.
DSH’s two change paths
DSH distinguishes two kinds of provider changes:
For providers that can be re‑selected on each call, DSH updates a Map registry and lets the next call pick the current implementation (light path).
For providers whose change invalidates dependent consumers, DSH follows a service‑dependency convergence path that forces affected consumers to exit, waits for the old relationship to settle, then re‑activates them (heavy path).
Example registration code:
ctx.effect(function* () {
providers.set(provider.id, provider)
yield () => providers.delete(provider.id)
})When Exa (a search provider) unregisters, its effect removes the entry; the runtime does not need a full restart.
For services like file‑system access, DSH must ensure that old consumers release the previous instance before the new one becomes visible. Cordis tracks the caller’s Fiber via ctx.effect(), so when the service is removed, it first forces the old service to disappear, notifies affected consumers, waits for them to exit, then cleans up the provider’s records before re‑activating the new service.
Effect handling of partial failures
When a plugin’s initialization consists of multiple steps—state write, event subscription, message broadcast, handle opening—any step can throw. DSH’s SessionStore creates a session, immediately registers a disposer to remove the session, then broadcasts session/created. If the broadcast fails, Cordis executes the already‑collected disposers, removing the session and preserving consistency. Disposers are run in reverse order of registration.
Only resources that can be cleanly reversed (e.g., registered items, subscriptions, handles) are suitable for ctx.effect(). External side‑effects such as network requests or database writes require additional idempotency or compensation logic.
Key takeaways
Both Pi and DSH solve the plugin‑cleanup problem but place the responsibility differently: Pi gives the extension author full control, while DSH, via Cordis, records cross‑plugin relationships so the runtime can decide who to stop and what to clean. For small systems with infrequent changes, Pi’s simpler model is attractive. For larger systems with frequent hot‑swaps and complex dependency graphs, Cordis‑backed DSH reduces manual bookkeeping and prevents hidden “ghost” states.
However, the added runtime layer introduces its own complexity and requires accurate ownership tracking; otherwise, the system may become harder to debug.
References
DeepSeek Harness official repository
DSH source commit 47f9438 Pi source commit 588915e DSH Loader implementation
Cordis Fiber, effect, and plugin activation
Cordis Service registration and dependency notification
Cordis Context tracking for Service callers
Web Provider reversible registration and runtime selection
Exa Search Provider registration
SessionStore recovery on broadcast failure
Pi extension documentation
Pi reload implementation
Cordis paper: "A Programming Paradigm for Spatiotemporal Composability"
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
