Liking cljdoc? Tell your friends :D

World Scopes and Search

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.

World scopes

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.

Finite Monte Carlo tree search

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.

Recursive SCI interpreters

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

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close