Liking cljdoc? Tell your friends :D

One algebra for worlds, savepoints and budgets

Status: in progress (2026-09-27; agreed in direction: "unify our stack and FRP algebra"). Done: steps 1 (spindel#74, released in 0.1.67), 2 (#195), 3 (resume through hydrate-into!), 4 (#196). Next: 5, 6.

dvergr grew its own versions of things spindel now provides: Run worlds beside spindel's savepoint worlds, a resource ledger beside spindel's resource authority, a resume path beside hydrate!, world-level effect handler tables beside spindel's handler tables. Each pair does the same thing with different names and different guarantees. This document maps them and says how each dvergr concept becomes an instance of the spindel one, so that the laws spindel states (and tests) hold for dvergr too.

The map

dvergr todayspindel primitiveGap
Run world: world/open! → d/fork-room → a ygg/fork! handle the Room owns and settles (merge, discard, review)a world of a savepoint session: world-scope/fork!, handle owned by the scopethe scope's handle is private: a scope world cannot be a settleable Room
Run budget: dvergr.resource wallets (Kontor), conserved transfers keyed by Runworld.scope/PResourceAuthority: grant! on fork, return! on discard, escrow!/claim! across a process boundary, keyed by worldnone in kind: the same four moves; the key differs (Run vs world)
per-Run model budget (budget-dollars on the chat)a resource in the authoritythe chat budget is not in the ledger, so it is neither conserved nor escrowed
turn savepoints: :conversation/turn, persisted on the Run (#194)savepoint, portable/persistdone; persist needs a session even without escrow (spindel bug)
resume!: a new Run in a Run world forked at the savepoint's snapshotsportable/hydrate!: fork a session root pinned at the snapshots, prepare it, run the named functionhydrate! forks through the scope (see row 1); continue-llm-run is a label, not the continuation (Law 5 not exercised)
effect handler configuration in world state ([:dvergr/effects :handlers], inherited by forks, composed)savepoint handler tables in world state ([:savepoint/handlers], inherited by forks, replaceable)two tables, one pattern; dvergr's composes algebraically, spindel's merges
Attempt worlds: one forked world per cell, discarded after scoringa world-scope over the experiment's worlds, with quiescence and discardexperiments manage forks and cleanup themselves

The plan

  1. spindel (PRs for review):
    • persist without a session (the NPE: @(:scope (sp/session world)) with no session).
    • portable/hydrate-into!: hydrate data into a world the embedder forked (pinned at (:world/systems data)), in a session: clear the inherited savepoint bookkeeping, write the declared state and seed, repin components, claim the escrow, start-in! the named function. hydrate! becomes fork + hydrate-into!. The embedder keeps its handle, and the law is unchanged.
  2. dvergr authority: dvergr.resource/authority, a PResourceAuthority over the ledger, mapping a world to the Run that owns it (:run-id in the Room's meta): grant! = allocate from the parent Run's wallet, return! = return the remainder, escrow! = move the remainder into an escrow account named by the savepoint id, claim! = move it into the claiming Run's wallet, once. Run worlds' savepoint sessions carry it; turn savepoints persist with {:escrow? true}.
  3. Resume through the law (done, spindel 0.1.70): resume! opens a Run world forked at the savepoint's snapshots (dvergr owns the handle and settles it), and portable/hydrate-into! makes that world the savepoint's continuation: seed and sequence, bookkeeping cleared, pinned components repinned, and continue-llm-run started as the session's computation, before the Run's first step. The Run's supervisor drives the loop from the recorded step; the continuation resolves with the resumed Run's result, so the session's end is the Run's end. The budget moves as step 2 says.
  4. Model budget in the ledger (done): a Run allocated microdollars pays its model spend from its wallet: account-usage! charges each cost through resource/*spend-wallet* (bound by the LLM loop; unlike *model-scope* it restricts no provider), a cost beyond what is left takes the rest and exhausts the budget, and the Run's budget is at most its wallet. Model spend is thereby conserved, escrowed and resumed with everything else. A resumed Run without a wallet starts with its budget minus what the stopped Run's chat spent, not a fresh one.
  5. One handler table: dvergr's effect configuration and spindel's savepoint handlers are both "handlers in world state, inherited by forks". Keep dvergr's algebra (normalize, compose, narrowing) and store both under spindel's world-state conventions; a later spindel step may give handler tables the composition dvergr has.
  6. Experiments as scopes: an experiment's Attempt worlds as one world-scope (quiescence, discard, descriptors for audit, the authority for their budgets).

Steps 1 and 2 are independent; 3 needs both; 4 needs 2; 5 and 6 are refactors without new behaviour and come last.

What does not change

SCI effects stay tail-resumptive handlers (doc/effects.md): SCI code is not CPS-transformed, so its effects cannot be spindel effects. The effect configuration unifies (step 5); the effect mechanism in the sandbox stays dvergr's.

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