Spindel's inference combinators execute probabilistic programs as Spin
values. Pure models can keep the default :world-policy :fresh. A model that
reads or changes registered room systems can request canonical worlds:
(smc-infer model 32
{:world-policy :fork
:world-opts {:systems #{:knowledge :repository}}
:resample-threshold 0.5})
Each initial particle is a frozen ygg/fork! of the ambient execution
context. At resampling, each selected ancestor is forked again. Selecting one
ancestor three times therefore produces three independently writable child
worlds, not three aliases to one context.
This makes inference a composition of existing Spindel operations:
ambient world
-> fork N frozen particles
-> run until a probabilistic checkpoint
-> score and select ancestors
-> fork each selected ancestor
-> resume
-> project values and traces into an EmpiricalMeasure
-> discard the speculative world tree
The EmpiricalMeasure retains result and trace projections in parentless,
immutable contexts. It therefore retains neither the resampling ancestry nor
writable settlement authority. On successful completion,
all particle ForkHandles are consumed newest-generation first. Construction
and resampling failures are also cleaned up automatically because no affected
particle is running at those boundaries.
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.
SMC now uses this generic scope through compatibility functions in its coordinator. 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.
Model failures are fail-fast. Spindel cooperatively cancels sibling particles, tracks their terminal callbacks, and discards their worlds automatically once they are quiescent. The thrown exception also contains actionable process-local recovery operations:
(:world/recovery (ex-data error))
;; => {:status :open
;; :manager <process-local capability>
;; :await-quiescent <CPS operation>
;; :cancel! <host function>
;; :discard! <host function>
;; :descriptors [<portable fork descriptors> ...]}
Descriptors are safe durable/audit projections. The manager, operations, and its live handles are process-local capabilities. A supervising host can await quiescence and retry cleanup if automatic settlement encountered a recoverable preflight failure. Cleanup is idempotent, concurrent callers share one result, and a read-only preflight failure permits a later retry; failures after mutation remain terminal for explicit substrate recovery.
Particle-local program state belongs in the particle ExecutionContext:
signals, Spindel atoms, continuations, trace, score, and checkpoint state all
fork with the world. The manager contains only host lifecycle capabilities for
the entire inference execution. Durable application records should store fork
descriptors, not contexts or handles.
The optional top-level :executor is used by every particle and forwarded to
its canonical fork. Without it, world-backed inference shares the ambient
world's executor, preserving scheduler ownership. The legacy :fresh policy
keeps its existing inference-local shared executor behavior.
Forking a Kontor ledger or another registered system creates a hypothetical branch of its state. It does not grant duplicate authority to spend external compute, tokens, money, or network capacity. Effects that consume real resources need an explicit host capability and an affine split/reservation policy. Simulations can instead receive stubbed effects, cheaper models, or a forked accounting scenario.
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.
PGAS ancestor scoring still uses legacy snapshot particles, so Spindel rejects
:world-policy :fork together with :pgas-ancestor-sampling?. That combination
can be enabled once auxiliary scoring particles also use canonical worlds.
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 |