Changelog
Unreleased
A World is a place a person steps into, locally and hosted alike (architecture, "Viewing a World"; HTTP reference, "Looking into a world"). Every served World mounts each twin's mirror at
/<org>/<world>/<vendor>/mirror/(the mount moved from the hosted supervisor into the World's doors) and answersmap,timeline(the twins' logs merged newest first, narrowed by twin or by W3C trace id),diff,clock(set and advance, forward only) andhistory?at=. The read token opens a browser session and every mirror: a twin whose manifest namesrequestScopes: ["read"](34 packs: slack, github, stripe, openai, resend, aws; those whose GETs could write for their caller, or took a virgin World's credentials; the OAuth authorize packs; xai; the metered packs; the poll-driven lifecycles and deliveries: architecture, "Viewing a World") gets every request of the read token markedx-volter-read-only, a request to a read-only twin as far as the pack knows, and the kernel refuses its writes (appends, blob writes, git ref moves) while the vendor's own catch-up still lands. A viewer changes nothing the app can see or do: a read-only poll answers the current state without advancing or delivering, a read consumes no one-time result and spends no quota, and an authorize leg is refused. At any other twin the read token'sGETs pass as before and nothing else. Twins record an incomingtraceparenton the entries a request causes and continue it on the webhooks those entries cause (stripe, clerk, github, jira, linear, mailgun, paypal, postmark, qstash, resend, sendblue, sentry, slack, vital). A host makes branches of a World as of an instant (POST …/branches { at, ttl }): cloned from the World's history cut there, its clock frozen at the instant, removed when its time runs out; world-host and the local view make them withLocalBranches, a hosted World's supervisor on its alarm.volter world viewserves one World, its mirrors, its branches and the console, and opens the console, which now has an overview map, a timeline with live updates, a trace waterfall, a wall of mirrors, the changes, the World clock and "View as of…". Mirror clients build on Windows (filePathOf). Verified: world-runtimeserved-worldandworld-viewtests (the doors against stub twins, and branches as of an instant, kept across a restart); world-corerequest-scope,trace-context,twin-fetchandfile-pathtests; slack and openai read-scope tests, stripe and linear traceparent tests; the console's fake-backed and real-host World journeys under Chromium; avolter world viewrun with the real slack and github twins (a read session reads Slack overPOST, itschat.postMessageansweredtwin_read_only; the mirror wall renders both); the cloud bundle and its 28 mirror builds. Typecheck and biome on every touched package. Not run: the full catalog gate, the world-host suite (its fixture symlinks need a privilege this Windows machine lacks). Each World's browser session lives on an origin of its own (<world>--<org>.localhostunder world-host andvolter world view; hosted behindWORLD_ORIGIN_SUFFIX, off until the zone serves them: deploy/RUNBOOK.md "World origins"), and a session's writes needSec-Fetch-Site: same-originon every host. The hosted World was walked under local workerd (wrangler dev): provision, traced writes, read session, mirror, timeline, trace, map, clock, diff, branches as of an instant and a branch removed by its alarm.An app's own production hostnames reach it inside a World:
volter-world app-url <world> --host <name>records them, and the injector and the redirect proxy route them to the app with their Host andx-forwarded-proto.In a World, a Stripe webhook endpoint is signed with the World's own secret (
STRIPE_WEBHOOK_SECRET, andSTRIPE_CONNECT_WEBHOOK_SECRETfor a Connect endpoint): the app verifies its first delivery with the value its env already holds, where it used to be rejected until someone copied the endpoint's minted secret into it.A World no longer sets a repository's empty
.env.examplekeys:initrecords them as declared and leaves them unset, andupleaves an empty configured value unset, as world-machine does, so the app's own.env.localis read on the host as in the machine. A caller's own value for such a key now reachesrun;runtime.environment.stripkeeps it out.The managed platform's people and orgs are the Volter identity service's (id.volter.ai, volter-ai/identity ADR-0002). A person signs in with their Volter account (the authorization code with PKCE) and the platform keeps its own session; orgs, members, roles and invitations go through the service's product door with the platform's own
client_credentialstoken. The platform readsVOLTER_ISSUER,VOLTER_CLIENT_IDandVOLTER_CLIENT_SECRET; the operator registers it at the service (RUNBOOK). The account page links to the Volter account. The journey and the rehearsals boot the Volter identity twin and register the console there.The managed platform provisions onto enrolled hosts only: the machine pool,
POST /-/pools, the Fly deploy (deploy/fly/) and its rehearsal are gone (company decision 0017).POST /-/hosts{ id, base, adminToken }enrolls a host once its admin door answers the token, and the hosted World on Cloudflare answers those doors with a world-host's inventory row (the admin token asx-volter-tokenor a bearer), so the platform provisions onto it.rehearse-managed.tsruns the managed journey againstapps/cloudunder workerd.packages/clerk-workeris removed, and its deployed Worker (no requests in seven days) deleted after a backup. Under Bun, removing one signal listener uninstalled the process's handler while others remained; the runtime now re-registers the rest, so a caller's own SIGINT teardown runs after a World boots.Hosted Worlds on Cloudflare (
apps/cloud, company decision 0017): one Worker, one Durable Object per World running the same kernel and packs as a local World in an isolate of its own, state in SQLite throughSqlWorldStore, blobs in R2, rooted twins sealed under the Worker's key and refreshed on the object's alarm, checks in a network-less sandbox, mirrors, branches, and the admin, session and attach doors (HTTP reference, "Hosted on Cloudflare").bun run deployrehearses the unmodifiedwrangler deployon the cloudflare twin first. Deployed asvolter-world-cloud, it serves the engagements attwins.voltertest.xyz: the v1 Worker's state, its sealed credentials (moved server to server, never read out) and its forwarding links (now World links) moved over, every path checked against v1 before the route moved, and the v1 Worker, its bucket and source branch deleted (backed up in prod-ops custody). Measured there: PeakHealth's Jira World answers its board in 0.2-0.4 s warm, 0.71-0.75 s from a cold isolate; its rooted Jira pulls from the client's Jira. Shipped with it: a read folds only what changed, served Worlds' doors run through the World store, live URLs carry a World's mount path, webhook delivery reads endpoints from the tree (stripe, clerk, jira, linear), and a local id the vendor holds names the vendor's subject. The TCP twins are hosted too, over a stream: smtp and PlanetScale's MySQL wire answer on a WebSocket thatvolter-world attachand@volter/world-core/attachbridge to a loopback listener, sonodemailer,mysql2and Python'ssmtplibconnect unmodified (MySQL atPLANETSCALE_MYSQL_TWIN_URL); tunnel's relay answers its control and visitor WebSockets on the World's vendor API, driven by the real@volter/tunnelclient. The injector carries the World's key on requests an SDK addresses to the World itself, and the manifest names each twin's endpoint variable. Verified by drives through the product; no automated suites were run.World command launchers hide the injector routing banner by default;
--verboseshows it without changing routing or warnings. Existing admitted quiet settings remain valid. Verified manually: default/verbose child inheritance, explicit environment override, command-side flag isolation, public run, disposable-run IPC and environment stripping, and GitHub hostname routing. Targeted runtime/SDK suites: 41 passed, one existing caller-cwd attachment failure also reproduced on unchanged main. Reference/architecture checks: 152 passed; both package typechecks and touched-file lint passed. Independent review found no blockers. Full catalog gate not run.
2.0.0 — Neon for every SaaS
The model is branching storage under every vendor's API, with the vendor as the root (company contract "The model"). What changed for a user:
- Node. The
voltercommand, the host and every twin run under Node 22.3+ as well as Bun:npm install -g @volter/world,npm install -D @volter/twin-<vendor>, and the same commands. The server seam behind every twin is Bun's server under Bun andnode:httpunder Node; smtp and tunnel (raw TCP, WebSocket) stay on Bun and say so in their READMEs. - Installable packages. Every public package and every twin publishes built output (
dist, types included) with its manifest pointing at it; the same tarballs the docs' pages install. - Host worlds for a team. A guide for
@volter/world-hostand the console: many worlds under one URL, provisioned through its HTTP API, opened in the console. - The platform (first slice).
@volter/world-platformis the hosted Volter World service — and an app in a world, dogfooding World: its identity is Clerk, run through Clerk's unmodified SDKs (@clerk/backend, clerk-js) against the clerk twin in rehearsal and real Clerk through the twin's root in production, with no vendor secret held by the platform; an org is one document, a world is provisioned onto an enrolled host through its admin doors and listed with the host that serves it. It never serves a twin — a host does — and it serves the same console as the portal. Proven end to end by a journey that signs in through clerk-js against the clerk twin, makes an org, provisions a world on a realworld-host, and reads it back — no real Clerk, no real cloud. A world now mints Clerk-shaped keys for an app that reads them (the publishable key names the host the boundary resolves to the twin). - The console.
@volter/world-consoleis the one UI of a world: the host mounts it at/-/console/when it is installed, and it reads nothing but the host's and the world's own endpoints with the token you type — the worlds a host serves, a world's twins with their protocol, root, sealed credential and last refresh, its log and tree, its changesets; and, with the admin token, provisioning, rotation and removal. A world answersGET …/twinsfor the console's first read. Its proof is a Playwright journey through a real host, gated like a twin's. - The platform packages carry the product's name. Volter is the company; World is the
product; a twin is the unit.
@volter/twin-coreis@volter/world-coreand@volter/twin-attachis@volter/world-attach;@volter/world(thevoltercommand),world-runtimeandworld-hostalready fit. Twins stay@volter/twin-<vendor>. In the repo the kernel directory is flat (packages/world-*,packages/cli), and the local-infrastructure runner isvolter-world-infra(wasmanaged-infra, a name the hosted product now needs). - The host has the hosted product's endpoints.
@volter/world-hostmints an admin token once and keeps it beside its directory; under it,GET /-/worldsis the inventory (every world, its address, its tokens, its twins),POST /-/worldsprovisions a bare world and mounts it without a restart,…/rotatemints both tokens anew,DELETEremoves. The admin token opens nothing under a world. The v1 shell'svolter remote provision|rotate, its wrangler config and deploy script are gone; the reference describes the host that exists. - One log, branches as pointers, checkpoints. A branch starts where the world stands and
records its own changes; a read is the nearest checkpoint plus the changes since.
volter world logshows the whole history;diffsays what it measures from. - Which pack is at which protocol is visible.
volter world statusprints each twin's protocol; the catalog index header tallies the catalog by protocol;GET …/twins/<vendor>/statusanswers a twin's protocol, root, sealed-credential fingerprint, last refresh, position and last receipt. A pack at protocol 1 is deprecated: served under a warning, its vendor half (sync, push) throws until it moves; its own verification states that asout of date, not as a failure. - The
slack-live-synccookbook is retired. It taught the v1 flow (a local plan, a review, a push from the pack); theon-call-agentcookbook and the sharing and deploying guides are the v2 way. - Under live use, a refused or failed write does not stay in the tree. The head appends the
entry, performs it, and reverts it when the vendor did not take it; the receipt stays on the
original. Every answer of a rooted twin carries
x-volter-observed-at, the instant of the last completed refresh. - A reference follows an adopted id. A twin declares which fields hold another subject's id; when the vendor mints the parent's id, a comment written against the local number is performed against the vendor's, and the world's tree reads the vendor's. An observation is atomic: a branch or a read at a position never falls inside one refresh.
- Slack's state lives under
slack. The twin's log and blobs moved from.volter/world/chatto.volter/world/slack, the same name as the pack, so a root, a credential and a reference declared forslackreach the twin. A world made before this rename keeps its history by renaming that one directory. - The GitHub twin needs no
gitbinary. Its git plane (blobs, trees, commits, tags, refs, clone and push over smart HTTP, compare and merge-base) runs on the kernel's git library: one content-addressed object store per world, refs as world state carried by a snapshot. A fork copies refs, never objects. A world's.volter/world/github/gitlayout changes accordingly. - A remote is a world.
volter world init --bare <org>/<world> --twins a,bmakes a shared world;volter world serveputs it on a URL;volter remote addnames it; clone, fetch and push move changes between worlds and never touch a vendor. - A World saves what it substitutes. Format-2 manifests group identity, discovery, runtime, serving, scenario and provenance explicitly. Init selects application use by default, while saved usage/vendor selectors opt dependency, build or deployment tools in. Legacy manifests remain readable and migrate explicitly with a byte-identical backup. Regeneration preserves authored notes, infrastructure stubs and seed entry points, and refuses new endpoint claims that collide with saved services before writing files.
- A twin's root is the vendor.
volter twin <vendor> root <url> [--scope] --deploy auto|gated|holdandvolter twin <vendor> credential(sealed beside the world under a key in your config directory).volter world deployperforms landed changes;verifyandapproveare the gates. Every receipt lands on the change:log --receipts. - Checks are CI on deployment.
.volter/checks/files run before any change is performed; the shipped no-secrets check refuses credential-shaped strings. A refusal is a receipt. - Packs are protocol 2 plugins. github, jira, slack and openai declare it and pass the two
kernel gates (
scripts/protocol-2.test.ts,scripts/branch-round-trip.test.ts): their serve paths read only the tree, keep no process-level truth, tombstone withdeleted: trueand take history from the log. Every other pack is a protocol 1 plugin served under a deprecation warning until it moves. A jira watcher or vote is now one subject per account (<issue>::<account>); a github delete carries its tombstone; a scripted openai tool call's id is its position in the response. - The head is the write path. A twin whose root says
deploy autoperforms a write the moment it arrives, in the process that serves it: checks, the vendor, the receipt, and the app is answered with the id the vendor minted — or refused in the vendor's own error shape. Point an app at a shared world and it is pointed at the vendor with no key in the app. The push door andvolter world deployperform through the same path; the push ledger is gone from it. - Refresh is the kernel's fold. A pack observes resources; the kernel appends to the root's
log only what changed.
volter twin <v> refresh [--force]is throttled by the root's--at-most(else the pack's); a served world schedules the root's--refresh(else the pack'severy). Two new guides walk live use and reading the vendor through a shared world, with rebase naming a conflict. - Proven against real vendors. A shared world with
api.github.comas its github root performed an app's wire write at the head (GitHub minted the number, the log shows it under the vendor's id, the secret check answered in GitHub's words) and refreshed from the real account; a slack root athttps://slack.comrefreshed a real channel into the held copy. Found and fixed on the way: the slack client now form-encodes every call (Slack's info-style methods refuse JSON), waits out a rate limit asRetry-Afterasks, skips the history of a channel the bot is not in, and a root's--scopenames the channels a refresh reads. The slack root ishttps://slack.com, not/api. - Just like Neon, and nothing else. One branch shape for local branches and clones (a pointer
to the parent at a position; fetch caches,
pullandrebasemove the position); one position per twin;volter world branch <name> --at <instant|twin@n>branches from any point in the history; one solid, discoverable URL per served world (serve.json, a second serve refuses, tokens survive restarts); the host serves a directory of worlds under one URL (volter-host serve --dir). The v1 machinery left the kernel: the push ledger, shadow refs and bases, observed deltas as a row kind, egress intents, plans, leases, reconcile, remote refs, the queue lifecycle, the v1 status, validation and inspector, the poll runner, the v1 apply, the v1 operator CLI, the v1 host and its worker render. The names a protocol 1 pack still imports throw on first call. - Push is fast-forward only; rebase names the field. A push carries the clone's position on
origin and is refused when origin moved past it, with what to do. A changeset records where the
parent log was cut;
rebasecompares the parent's entries since the cut field by field and names each conflict by record and field. A served world keeps its token across restarts; a failed deploy lands afailedreceipt and is retried on the next deploy. - Every pack peers on the workspace core. The seven packs that pinned
@volter/world-core@0.1.0(figma, github, jira, linear, notion, slack, stripe) now declareworkspace:*like the rest, so an install against a 2.0.0 registry resolves instead of waiting on an upstream that never answers. - Retired:
push to reality, backing, link, feed, pending, the confirm row, the separate push ledger, the observed log as a second row kind.@volter/world-remoteis@volter/world-host, the hosting product.
Unreleased
Catalog and fidelity
- A Prisma client constructed without a driver adapter gets the twinned database vendor's HTTP adapter from the injector, declared as
prismaAdapteron the pack; PlanetScale declares@prisma/adapter-planetscaleoverPLANETSCALE_DATABASE_URL. Prisma's client-engine build, the one a browser tab runs, refuses to construct without one. - Moonshot and Together AI join the catalog as protocol-2 twins with vendor-censused surfaces, deterministic inference envelopes, scenario and budget handling, real-SDK integration coverage and completed T0/adversarial review evidence (moonshot
fd070670, moonshot review close-outc8717b70, togetherai8a577347, togetherai T05f3c479e). - Slack delivers Events API messages through native Socket Mode, with transport-local monotonic delivery timestamps and message authors retained as workspace members (
ffd5d919,e5048471,eb2b34fe). - GitHub repository hooks are persisted twin state and drive signed native deliveries while preserving source identity; draft transitions work through the native GraphQL surface (
f0fcb322,a8fe9055). - Jira supports individual comment create, edit and delete through the native path, retaining vendor comment IDs after deployed mutations and accepting mention-only edits (
539b78af,f4734f71,5d85d7e2). - New pack scaffolds start on protocol 2, pack hygiene checks their full package shape, and the capability mutation sweep mechanically proves declared verification seams, including bounded failure of hung probes (
b26e50e0,05419b81,2995a878). - Hosted Worlds preserve twin routing for native HTTP clients instead of losing the injected destination at the redirect proxy (
4a856084).
The app repo is the world
volter world initruns in the app and writes.volter/world.jsonbeside the code, with the twins' default data and handlers, and a.gitignorefor the running state. The config names each twin as a package (@volter/twin-<vendor>), resolved fromnode_modulesat boot, and carries no path, key or timestamp; a service-account credential is$mint, minted per boot into the live env only.initis byte-deterministic with no exception.volter, from@volter/world, is the user's command and acts on the world of the current directory:init,up,run,activate,shell,status,log,diff,seed,reset,down,branch,checkout,clone,fetch,origin,changeset -m,push.upresumes a branch with its state;resetis the way back to the default data.World.open()takes no argument;WorldandRepocarry the same verbs as the command.- Tokens for a remote live in
~/.config/volter/credentials.json, written byclone --token. - A changeset carries the author's message beside its generated summary.
- One tutorial, executed as a test; guides; a drift-checked reference; the user glossary and a sweep that holds the user pages to it.
Journeys, and what they found
voltertakes a noun, then a verb:volter world …,volter twin <vendor> serve|mirror|conformance,volter remote serve|provision|rotate.- A tutorial page is its own journey: the tutorial and every guide are executed exactly as
written by
packages/cli/src/journeys/tutorials.test.ts(bash fences are the steps, text fences the expected output,file=fences the files), and nothing runs that a page does not show.scripts/docs-media.tsrecords each page with vhs and screenshots every command intodocs/media/<page>/, which the page embeds and lists in its Playback gallery. - The remote deploys under the policy's meaning:
autodeploys any changeset,gatedneeds a verified and approved one,holdrefuses. Before, every policy required review. POST …/remote/<vendor>/refreshrefreshes now. The Bun remote resolves performers like its pulls and pushes, and takes--allow-insecure-loopbackfor a twin standing in as reality.- The anthropic twin honors scenario faults (
{ "fault": { "kind": "status", "status": 529 } }) instead of failing with a 500.resetworks on a world whose twins ship no default data.
The pages install from a registry
scripts/registry.tspublishes this checkout's packages to a Verdaccio on the machine. Every tutorial page installs from it,bun add -g @volter/worldthenbun add -d @volter/twin-<vendor>, and is recorded from that install. Theprivateflag left every pack; the support tier lives inpolicy/MAINTAINERS.md. Packs ship theirdefaults/; core shipsattach.cjsand its generated artifacts; the runtime and the remote find core's artifacts and each other by package name; the kernel packages declare their dependencies.volter world clock show|set|advanceandvolter world replay <changeset> --into <branch>.volter world clonetakes the world's own token;--admin-tokencopies the whole tree.
The World runs where no child blocks
volter world run -- <command>and the seed await their child instead of blocking on it; stdio, signals and the exit code are the same. A runtime with no synchronous child, the browser engine, runs them.scripts/publish/pack.mjs <directory> [package…]packs the World and any twin named, closed over their workspace siblings, into a directory of tarballs a registry can serve.
The PlanetScale twin
- A derived table in
FROMis read as a table:SELECT COUNT(*) FROM (SELECT id FROM t WHERE …) AS sub, the shape Prisma'scountemits, answers the count, and a derived table with no alias is MySQL's errno 1248. Measured with Prisma's client through the real PlanetScale adapter and driver against the twin: create, find, update and count round-trip.
Renamed
| was | is |
|---|---|
@volter/twin |
@volter/world |
@volter/twin-world |
@volter/world-runtime |
@volter/twins-host |
@volter/world-remote |
volter-world init --repo <app> into <app>/../<name>-pilot/ |
volter world init into <app>/.volter/; --out still emits a pilot elsewhere |
volter-world env, attach -- <cmd> |
volter world run -- <cmd> |
volter-world changeset push |
volter world push |
| placeholder remote | default data |
| sealed world | sandbox (volter world up --sandbox) |
Retiring after one release
The remote's link, feed and pending endpoints (now remote, mirror, log) and the keys
remote.origin, pushPolicy, sync, syncState, pushState (now deployment.url,
deployPolicy, refresh, refreshState, deployState); the volter-world verbs tail,
fork and changeset apply (now log, branch, changeset push).