Problem
SCI contexts can be copied at the host level, but interpreter-owned mutable state is represented by a mixture of context maps, stable Var handles, dynamic binding frames, atoms, namespace objects, multimethod tables, type/protocol state, and host resources. A shallow context copy can therefore let two apparently independent evaluations affect one another.
This matters for simulations, speculative evaluation, branching REPLs, and other applications that need to evolve several interpreter worlds from one accepted state.
The desired operation is a quiescent world snapshot:
(def parent (sci/init {}))
(sci/eval-string* parent "(def counter (atom 0))")
(def child (sci/fork parent))
(sci/eval-string* child "(swap! counter inc)")
(sci/eval-string* parent "@counter") ;=> 0
(sci/eval-string* child "@counter") ;=> 1
Dynamic bindings and asynchronous callbacks must select the correct world as well. Mutable or affine host resources must not be silently presented as independent when they are actually shared.
Design space
I considered two primary representations:
- Keep every world in persistent maps, making a fork approximately O(1), at the cost of persistent lookup indirection on every Var and reference access.
- Stop evaluation briefly and copy a dense table of logical state slots. Forking becomes O(number of live registered slots), while common reads remain array accesses.
The prototype takes the second approach because Var/reference access is expected to be orders of magnitude more frequent than forking, and the number of mutable interpreter objects in a context is expected to remain bounded. It uses a lineage registry, stable SCI handles, dense world-local cells, and a read/write permit that makes snapshots quiescent.
State is handled according to its semantics:
- SCI-owned mutable values receive world-local slots.
- Dynamic binding frames remain persistent control state and are scoped to their active world.
- Async continuations convey both bindings and the SCI world.
- Cooperative host objects can implement
Forkable or use a host-supplied fallback.
- Known mutable or affine host resources are rejected by default during a fork.
This is intentionally a world snapshot, not a snapshot of a currently running continuation. A future execution-fork API would need an explicit suspension point and resource-capability model.
Questions for maintainers
- Is a world-fork operation within SCI's intended scope?
- Is the quiescent O(n) snapshot / fast-read tradeoff appropriate?
- Which parts should remain in SCI versus an embedding library?
- Would a smaller sequence of PRs be preferable to reviewing the complete prototype?
- Are the documented Clojure parity and host-resource boundaries acceptable?
The implementation prototype includes a state inventory, benchmarks, JVM/CLJS tests, and explicit documentation of remaining limits.
Problem
SCI contexts can be copied at the host level, but interpreter-owned mutable state is represented by a mixture of context maps, stable Var handles, dynamic binding frames, atoms, namespace objects, multimethod tables, type/protocol state, and host resources. A shallow context copy can therefore let two apparently independent evaluations affect one another.
This matters for simulations, speculative evaluation, branching REPLs, and other applications that need to evolve several interpreter worlds from one accepted state.
The desired operation is a quiescent world snapshot:
Dynamic bindings and asynchronous callbacks must select the correct world as well. Mutable or affine host resources must not be silently presented as independent when they are actually shared.
Design space
I considered two primary representations:
The prototype takes the second approach because Var/reference access is expected to be orders of magnitude more frequent than forking, and the number of mutable interpreter objects in a context is expected to remain bounded. It uses a lineage registry, stable SCI handles, dense world-local cells, and a read/write permit that makes snapshots quiescent.
State is handled according to its semantics:
Forkableor use a host-supplied fallback.This is intentionally a world snapshot, not a snapshot of a currently running continuation. A future execution-fork API would need an explicit suspension point and resource-capability model.
Questions for maintainers
The implementation prototype includes a state inventory, benchmarks, JVM/CLJS tests, and explicit documentation of remaining limits.