A world scope owns a finite family of canonical forks for a bounded algorithm; Monte Carlo tree search is one such algorithm, and probabilistic inference over canonical worlds — SMC particles, particle MCMC — is another, in foerster.
The lifecycle above is not specific to probabilities. The
org.replikativ.spindel.world.scope namespace owns a finite family of
canonical forks for any bounded algorithm such as MCTS or a simulation
campaign. A scope provides fork construction, composable activity leases,
atomic lease exchange, quiescence, cancellation hand-back, reverse-order
discard, and portable descriptors. Once quiescence is published, admission is
closed permanently.
The algorithm remains responsible for its computations and ends each activity
lease only after that computation has actually stopped. A live ForkHandle
never leaves the scope; fork callers receive a child execution context and a
portable descriptor, but no settlement authority. The scope never interprets
a particle, tree node, Run, reward, or proposal. In particular, a search policy
may share immutable statistics for a transposition, but it must not share a
writable context or affine ForkHandle.
Savepoint sessions own their worlds through a scope, and so do canonical inference (foerster) and MCTS. This is an extraction of the existing ownership protocol, not a second world abstraction: Yggdrasil still owns substrate forks and settlement, while the execution context still owns reactive runtime state.
org.replikativ.spindel.search.mcts/search performs deterministic, finite UCT
search over the same canonical worlds. An environment supplies four functions:
(require '[org.replikativ.spindel.search.mcts :as mcts])
@(mcts/search
{:actions (fn [state] (if (:done? state) [] [:left :right]))
:transition (fn [state action] (assoc state :done? true :choice action))
:terminal? (fn [state] (:done? state))
:reward (fn [state] (if (= :right (:choice state)) 1.0 0.0))}
{:done? false}
{:max-simulations 64
:max-depth 8
:max-nodes 128
:seed :example})
;; => {:search/selected-action :right, ...}
Each expanded tree node owns a canonical child world. A rollout receives a
scratch descendant, so its registered forkable effects cannot mutate the stored
node or the ambient world. Search fails closed before running an environment
callback in a speculative fork if Yggdrasil marks any registered system
:shared; an identity-forked system is not speculative isolation. Ordinary
mutable host objects captured by an environment closure are outside the world
model and must not be used for hypothetical state. :transition may change its
current registered systems, but the system registry itself is frozen before
callbacks so search cannot acquire child-only external resources without a
disposal owner. :actions, :terminal?, and :reward are observational
environment contracts. They may return either plain values or Spins. All
speculative worlds are discarded before the result is delivered, and the
portable result contains node statistics plus settled world descriptors—not
execution contexts or ForkHandles.
The implementation is deliberately sequential in its first version. A pure
:continue? predicate receives {:simulations n :nodes m} before every
simulation and can admit it against a host resource reservation more precisely
than a conversation-turn limit. Effects inside that simulation must still meter
and enforce their actual consumption. :max-simulations, :max-depth, and
:max-nodes are hard structural bounds. With E expanded nodes, at most E
retained node worlds plus one transient rollout world are live during sequential
search.
Selection is deterministic for the same seed, environment, and results. Actions
must be unique within a state. The seed and every action must be immutable,
cross-runtime data: nil, booleans, strings, keywords, symbols, UUIDs, finite
floating-point values, safe integers, and recursively composed vectors, lists,
sets, and non-record maps. Host objects, records, mutable arrays, ratios,
arbitrary-precision decimals, and integers outside the JavaScript safe range are
rejected. A custom pure :rollout-action receives the
action vector and deterministic search coordinates and returns an action index.
Applying :search/selected-action is intentionally a separate application
effect: search never silently commits a hypothetical world to its caller.
This version is a tree, not a transposition DAG. A future transposition table may share immutable statistics, but must never alias writable execution contexts or affine world handles. Multi-player/minimax value semantics, parallel rollouts, progress streaming, and persistent checkpoints likewise belong in explicit combinators rather than implicit behavior in this single-agent maximizing primitive.
SCI code can construct another Spindel+SCI interpreter inside a child world,
provided the host explicitly injects the constructor, evaluator, and world
fork capabilities. The inner interpreter executes Spin values against the
child runtime, and the parent retains settlement authority. Raw sci.core
does not need to be exposed to untrusted Dvergr programs; recursive
self-programming is a curated capability, not ambient reflection. The world
API gives SCI opaque IDs backed by a host registry. Raw ForkHandle records
contain mutable affine authority and must never cross the sandbox boundary.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |