Living Worlds

API

Build on Living Worlds.

Worlds, populations, lives and agents with a life, through one API. Everything below is generated from the core's own contract, which the server checks itself against on every start: 153 operations, contract version 1.

Connect

A key, a base address, and the contract.

The API is at https://api.livingworldslab.com; your keys come with your access. Send the key as a bearer token; every response names the contract version in its lw-contract header. Keys are server-side only: never in a browser or an app.

Roles: a member key does the work, an admin key designs and manages, a checker key reports verdicts on work it did not do.

curl -H "Authorization: Bearer $LW_KEY" "$LW_URL/contract"

curl -H "Authorization: Bearer $LW_KEY" "$LW_URL/worlds"

The SDK

A turn that carries a self's whole life.

The server-side SDK wraps the same contract: a turn brings the self's own record beside the task, and what happened is written back to it.

import { LivingWorlds } from "@livingworlds/sdk";

const lw = new LivingWorlds();          // LW_KEY; the base address defaults to https://api.livingworldslab.com
const turn = await lw.turn({ self: "concierge", task: question, person: userId });
const answer = await yourModel(turn.wrap(question));
await turn.done(answer);

Calibration

How a world is held to account by the real one: answers corrected by a few real ones, intervals that keep their coverage, regions, kin, subgroups, and a world corrected as observations arrive. Stateless; nothing is stored.

POST/calibrate/rectifymember

Correct a model's answers with a small real sample (prediction-powered inference, PPI++): an estimate and interval that stay valid however wrong the model is, beside the real-sample-only answer, and the effective sample size the model adds. Per cell and poststratified with cells and shares. Nothing is stored.

Body

labelled[{y, f}]: real answers y beside the model's f for the same units
unlabelled[f]: the model's answers where no real one exists
sharetrue for a 0/1 outcome (interval kept in [0, 1])
alphadefault 0.05
lambdaweight on the model in [0, 1]; tuned by default
cellsinstead: [{cell, labelled, unlabelled}]
shareswith cells: {cell: population share}
POST/calibrate/allocatemember

Where to spend the next real answers: labels per cell, more where the model is hardest to correct, every cell at least 2, summing to the budget.

Body

cells[{cell, sdResidual, sdClassical?, share?, cost?}]
budgettotal labels
POST/calibrate/intervalsmember

Intervals that keep their coverage (conformal): split; by subgroup, so no cell is covered only on average; or weighted to recent years, so a change of regime widens them instead of hiding.

Body

methodsplit | group | weighted
alphamiscoverage, default 0.1
scoressplit and weighted: past |error| values
predictionssplit and weighted: points to put intervals round
calibrationgroup: [{groups, score}]
pointsgroup: [{groups, prediction}]
timesweighted: when each score was made
nowweighted
halfLifeweighted: years for a score's weight to halve
POST/calibrate/regionsmember

Regions forecast as the nation plus an offset that fades (AR(1)), so a region inherits a national stall instead of its own old slope; gains that taper as life expectancy rises, fitted over many populations; and a score against the truth.

Body

national[{year, e0}]
regions{region: [{year, e0}]}
nationalForecast[{year, e0}]
horizonyears ahead
nationalSdsd of the national forecast
pooled{population: [{year, e0}]} to fit tapered gains
starte0 to project from with the fitted gains
yearsdefault 10
predicted{region: value}
truth{region: value}
POST/calibrate/kinmember

Expected siblings (older, younger, alive) from fertility and survival alone, with their variance, per cell; beside a simulation's counts and a survey's, it says whether the rates or the simulation's linking of births to parents is at fault.

Body

schedules{fertility, survival} by single year of age, or {year: {fertility, survival}}
cells[{cell, birthYear, egoAge}]
birthYearwithout cells
egoAgewithout cells; omit for lifetime
alivetrue to count siblings still alive at egoAge
simulated{cell: count}
survey{cell: count}
tolerancedefault 0.1
POST/calibrate/cellsmember

Subgroups held to the truth: multicalibration over overlapping groups (patches learned and applied to new units), small cells smoothed across ordered age bands, or each cell checked against its own sampling-noise floor.

Body

methodmulticalibrate | smooth | floor
unitsmulticalibrate: [{p, y, groups}]
applymulticalibrate: [{p, groups}] to correct
alphamulticalibrate: tolerance, default 0.02
cellssmooth: [{party, ageIndex, n, mean}]; floor: [{cell, predicted, observed, n}]
priorSdRWsmooth: step between age bands, default 0.05
sharessmooth: {party|age: share}
qfloor: default 0.95
POST/calibrate/assimilatemember

A running world corrected by what it observes (ensemble Kalman filter), or parameter space ruled out before expensive runs (history matching on Gaussian-process emulators). Seeded: the same body gives the same answer.

Body

methodenkf | history
ensembleenkf: members × parameters
predictedenkf: members × observations
observedenkf
obsSdenkf: one or per observation
inflationenkf: default 1
Xhistory: design points
outputshistory: simulator outputs per design point
targetshistory: [{value, obsSd, modelDiscrepancySd}]
candidateshistory: points to test, or bounds and nCandidates
cutoffhistory: default 3
seeddefault 1

Universe

The laws every world in a project lives under, and the kinds of world it may hold.

GET/universemember

This project's universe: its laws (the weakest gate any self here may run), its kinds of world, and how many worlds and residents it has.

PATCH/universeadmin

Set the laws. The floor may be raised; it is never looser than the core's own.

Body

gateFloor{trialsNeeded?, alpha?, maxUnchecked?}
PUT/universe/kinds/:idadmin

Declare a kind of world as data: what it is made of, where selves work in it, the checks that can fail there (none makes it belief-only), a stricter local gate, and what an arriving self must re-earn. A kind worlds were made under cannot be changed.

Body

description
places[...]
hosts[worker|studio|body]
taskClasses[...]
checks[check names]
gateoptional {trialsNeeded, alpha, maxUnchecked}, never looser than the laws
seedoptional first state of a world of this kind
quarantineoptional {beliefs?: true, baseline?: procedure text an arriving self must beat}
dayMsoptional wall milliseconds per world-day

Worlds

Make a world, populate it, run it, step it, jump it, branch it, replay it, and open any life in it.

GET/worlds/:id/residentsmember

The selves who live in this world.

POST/worldsadmin

Make a world in this project's universe. With no pack it is a place selves live in (kind optional: its seed is the world's first state). With a pack it also holds a living population: members drawn from a pack go on over simulated time (households, ties, circles, followers, life events from event_process dimensions, word of mouth, fatigue, bounded memory). Created paused at tick 0; the reply carries the plan (see /worlds/:id/plan).

Body

kinda kind declared at PUT /universe/kinds/:id (optional)
nametext
packthe pack
population{simulate: 2..20000 members, represents?, seed?, segments?, mixing?}
stimulus{text, kind?}: what members meet and are voiced about
time{start: YYYY-MM-DD, horizon: "30d", step?: "1d", resolution?: [{until, step}] (finer steps early), scale?: realtime|1h=1min|1h=1s|1d=1min|1d=10s|1d=1s|1w=1s|{sim, real}}
feed{channels: {name: rate/day}, imitation?, across?, fatigue?, fatigue_shape?}
social{ties?, household?, influencers?, follows?, circles?: {by, size}, homophily?, pull?}
life{processes?: [event_process dims], budget?: scalar dim, age?: scalar dim}
memory{cap?: 1..64, reflect_days?, consolidate_days?}
budget{calls_per_day?: members voiced by the model per simulated day (the realism knob)}
interventions[{at, kind, label?, params?}]
GET/worldsmember

Worlds, newest first: status (paused|running|done|failed), tick, parent and branch tick.

Query

statuspaused|running|done|failed
limit1..200 (50)
GET/worlds/:idmember

One world: spec, clock (tick, sim time, target), run (scale and whether it was capped), interventions, pending voiced members.

DELETE/worlds/:idadmin

Delete a world with its member states and log. Its branches stay (they no longer name a parent).

POST/worlds/:id/runadmin

Run the world in real time at its scale, capped to what the model and the arithmetic sustain (the reply says so). At most 3 running per tenant (429).

Body

scaleoptional: a preset or {sim, real}
calls_per_dayoptional: members voiced per simulated day
POST/worlds/:id/pauseadmin

Pause a running world; idempotent.

POST/worlds/:id/stepadmin

Advance a paused world by ticks (default 1) as fast as the arithmetic goes; voiced members still answer.

Body

ticks1..10000
POST/worlds/:id/jumpadmin

Advance a paused world to a later time.

Body

toa duration from the start ("14d"), seconds or a date
POST/worlds/:id/branchadmin

Branch at a tick the world has reached (default now): the new world shares everything before it (re-simulated, with the recorded voices, no model calls) and goes on with its own interventions. The parent is untouched.

Body

attick
nametext
GET/worlds/:id/planmember

Feasibility: the requested and effective (capped) scale, what limits it (model or arithmetic), model calls per second needed, estimated real time to the horizon, model calls to the horizon, and a plain sentence.

Query

scalea preset or sim=real
calls_per_daymembers voiced per simulated day
GET/worlds/:id/replaymember

The world's log, tick by tick: aggregates (have_it, aware, opinion, new adopters, exposures, life events, daily circle tiles), up to 25 sampled events per tick, group leanings.

Query

fromtick
totick
limit1..2000 (500)
eventsfalse to leave events out
GET/worlds/:id/membersmember

Members, paged, at the current or an earlier tick: public traits (aggregate-only dimensions withheld), household, circle, awareness, opinion.

Query

aftermember index
limit1..500 (100)
ticka reached tick
GET/worlds/:id/members/:imember

One member: as in the list, plus ties and memory (private life events withheld).

Query

ticka reached tick
GET/worlds/:id/interventionsmember

The world's interventions.

POST/worlds/:id/interventionsadmin

Add an intervention while paused, at or after the current time (409 otherwise).

Body

atduration from the start, seconds or a date
kinddrop|campaign|outside|price|influencer|life
labeltext
params{exposure?, duration?, half_life?, who?, factor? (price), count? (influencer), member?, event? (life)}
POST/worlds/:id/advanceadmin

A world of lives (made with POST /worlds {lives: {count, hazards, seed?, places?, year?}}): advance it {months: 1..1200} by its hazard table. Returns the world in aggregate: lives held and alive, the year, births, deaths and partnerships so far, and which hazards are measured and which are placeholders.

Body

months1..1200
GET/worlds/:id/lives/:lifemember

Meet a life in a world of lives: its own record as it knows it (age, partner and for how long, children and their ages, whether it lives with its mother) and the same in its own words.

POST/worlds/:id/lives/:life/voiceadmin

A met life's voiced decision, written back. decision: birth (a woman, the next months, default 12). The life answers through this tenant's model with its story as memory; the world host anchors the answer to the population's hazard (a voice moves one life around the level, never sets it) and draws the outcome as a real event. An answer with no chance in it is did-not-run and changes nothing.

Body

decisionbirth
months1..60
POST/worlds/:id/saveadmin

Save a world of lives to object storage, so it outlives the host's memory and comes back exactly.

PATCH/worlds/:id/interventions/:iidadmin

Change an intervention that has not happened yet, while paused.

DELETE/worlds/:id/interventions/:iidadmin

Remove an intervention that has not happened yet, while paused.

Simulation

Populations over time: lives stepped month by month, and what came of them.

POST/sim/runsadmin

Start a simulation run: a population drawn from a pack (or a selection of personas), a stimulus, measures, a voiced tier (free text rated by SSR, with collapse checks) and dynamics (adoption over mixing groups, or opinion over an explicit graph). Runs as `simulate-voice` then `simulate` jobs; watch sim.progress / sim.done in /events. At most 3 queued or running per tenant (429).

Body

packthe pack whose dimensions the population is drawn from
population{simulate: rows, represents?: people (each row stands for represents/simulate), seed?, segments?: [{name, share, values: {dimension: value | {shares} | {range: [lo, hi]} | {distribution}}}], rake?: [category dims] | {dim: {label: share}}, mixing?: {by: [dims or a mixing_group edge], edges?: {dim: [cut points]}}} or {personas: {where?, limit?}, represents?, mixing?}
stimulus{text, kind?, structure?}
measures[purchase_intent | appeal | share_intent | attend_intent | {key, anchors: [5 statements low..high] | [[..], [..]], ask?}] (default purchase_intent)
voice{members: 0..500 voiced by the tenant's model, rated by SSR; retest?: 0..20 (2); prior?: true; expected_sd?: 1.1; temperature?; strata?: [category dims or "segment"] for a weighted stratum sample}
dynamics{model: adoption, channels: {name: rate/day | {default, general, <segment>, "dim=value", "scalar<25", "scalar>=65", "scalar=18..24": rate} (every key is checked: an unknown one is refused by name)}, imitation?, across?: 0..1, fatigue?, fatigue_shape?, appeal?: scalar dim, appeal_measure?, events?: [{at, exposure, half_life?, duration?, who?}], event_process?: {rate: {mean, per}, exposure, half_life?, who?}} | {model: opinion, graph?: {edges: [[listener, speaker, w?]]}, homophily?: [dims], stubbornness?: 0..1, rate?}
stepany duration ("10min", "1h", "1d")
horizona duration ("40d")
report_everya duration
chunkrows per chunk
seeds1..20 (spread across seeds)
reference_dateYYYY-MM-DD the run's time starts at (default today): durations map onto the calendar from it, and derived dimensions (a cohort from age) count years from it
holdtrue: created but not started until POST /sim/runs/:id/observations {release: true}
observe{tool, args?, members?: voiced|all, max?: 1..5000}: pull per-member observations from one of the tenant's read-only tools first
calibration{targets: [{measure?, metric: mean|would_act|have_it_end|ever_had_it_end, target, tolerance?}]}: checked in results.calibration
GET/sim/runsmember

Simulation runs, newest first, with status, progress and the design (and version) each was made from.

Query

statusqueued|running|done|failed|cancelled
limit1..100 (20)
designonly this design's runs
GET/sim/runs/:idmember

One run: spec, progress {phase, voiced, voice_of, seed, seeds, step, steps, pct}, and when done its aggregate results (represented vs simulated, reactions per measure with distribution, divides, would_act and collapse checks, adoption curves with the seed spread, opinion) and results.trust (version 1): fidelity tier mix, seed spread, reliability (test-retest), collapse warnings, calibration state with raw/debiased/calibrated signals, and the design's backtest skill against naive baselines. Special-category dimensions only in groups of 10+.

GET/sim/runs/:id/membersmember

A run's sampled members, a page at a time by stable index: segment, weight, public traits (special-category values never), and the persona's handle when members are personas.

Query

afterthe last index of the previous page
limit1..1000 (100)
POST/sim/runs/:id/observationsadmin

External per-member observations: values of the pack's dimensions keyed by member index, before the run starts (a held run) or between its steps. They replace drawn values in profiles, rates and breakdowns. release: true starts a held run.

Body

observations[{i, values: {dimension: value}}] (at most 5000 per call)
sourcewhat produced them
releasetrue starts a held run
GET/sim/comparemember

Two finished runs side by side (an A/B of stimuli or designs): per measure means, would-act and JSD between distributions, adoption at the end, and whether they ran on the same seeds.

Query

arun id
brun id
POST/sim/runs/:id/canceladmin

Stop a queued, running or paused run for good (409 once finished).

POST/sim/runs/:id/pauseadmin

Pause a running run: status pausing until its next chunk boundary (a voiced batch in flight finishes first), then paused. A paused run holds no worker and survives restarts; idempotent.

POST/sim/runs/:id/resumeadmin

Continue a paused run from its checkpoint: resuming, then running. Results equal an uninterrupted run with the same seed. 429 when the tenant already has its maximum active runs.

DELETE/sim/runs/:idadmin

Delete a run with its observations and results, for good (404 when not the tenant's). Event sim.deleted.

POST/sim/runs/deleteadmin

Delete many runs at once; those not found come back under missing.

Body

ids[ids], 1..200
POST/sim/runs/:id/archiveadmin

Archive (archived: true, the default) or bring back (false) a run. Archived runs leave lists unless ?archived=1 (only archived) or all.

Body

archivedtrue | false
POST/sim/runs/archiveadmin

Archive or bring back many runs at once.

Body

ids[ids], 1..200
archivedtrue | false

Selves

Agents with a life: one record each, a character, a charter, beliefs that move only on evidence.

GET/selvesmember

The tenant's selves with counts of what they hold.

Query

subjectonly the selves belonging to this user of yours
POST/selvesadmin

Give birth to a self. Fails if the handle exists.

Body

characteroptional {personality: Big Five 0..1 | genome: {effectsSeed, seed}, axes?: {stance: -1..1}, tastes?: {taste: -1..1}}: who it is beneath its charter
procedureoptional: how it works at birth
handlelowercase id
namewhat people call it
charter{purpose, answersTo?, authority?: {reach?: source ids}, reviewBy?}
persona{default?, surfaces?}
interestsstring[]
routine{mode?: scheduled|on-activity, read?, reflect?, reflectAfter?, maxCalls?, maxTokens?}
identity[{kind, key, value}]
seed{story?, lessons?: [{claim, appliesWhen?, surfaces?}]}
subjectthe user of yours it belongs to
programits own instructions per step, after the tenant's
orientfalse to skip orientation (bulk births)
statusactive|paused
PUT/selves/:handleadmin

Declare a self: born if absent; otherwise what a person sets (name, charter, persona, routine, interests) is brought in line. Identity and history are never overwritten.

Body

...as POST /selves, without handle
GET/selves/:handlemember

A self in full: identity, lessons as the core holds them, its procedure and trials, the faults it carries, what is true now, recent episodes, proposals, usage, jobs, outcomes, record integrity.

PATCH/selves/:handleadmin

Change what a person sets.

Body

name
handlea new handle; the self keeps its whole life
charter
persona
routine
interests
program
statusactive|paused
GET/selves/:handle/proceduremember

How this self does the work: the adopted text, every candidate on trial with the gate's reading of it, and every version with the diff and the items that decided it.

PUT/selves/:handle/procedureadmin

Write the procedure yourself. Ends any trial; recorded as authority, never as evidence.

Body

texthow this self should do the work
whyoptional reason
POST/selves/:handle/procedure/revertadmin

Go back to an earlier procedure.

Body

versionwhich version
whyoptional reason
POST/selves/:handle/intakemember

What a self with a character takes in: news, posts, conversations, literature, media, experiences, meals, purchases. Moves its stances, tastes, temperament, affect and capacities as its personality lets it; never how it works.

Body

items[{kind, source, stance?: {axis: -1..1}, intensity?: 0..1, arousal?: 0..1, valence?: -1..1, demand?: 0..1, taste?: {name: -1..1}, body?: {nutrition?, rest?, strain?}}]
POST/selves/:handle/settlemember

The world settled something a stance bears on: the stance is pulled toward it and sources that pushed the other way lose trust. Grounding, kept apart from drift.

Body

axisthe stance
truthwhere the outcome sits, -1..1
outcomewhat settled it
weightoptional 0..2
POST/selves/:handle/character/eventadmin

A life event moves temperament directly, scaled by the self's sensitivity; part of it fades back toward its set point.

Body

namethe event
delta{openness?, conscientiousness?, extraversion?, agreeableness?, neuroticism?}, each within ±0.3
POST/selves/:handle/identityadmin

A person sets or removes one identity row directly.

Body

kindvalue|trait|commitment|taste|voice|expertise|relationship
key
valuenull removes
POST/selves/:handle/contextmember

The self's context for one call on one surface, and a ticket for its work and outcome.

Body

surfacewhere it is being asked
taskwhat it is being asked
itemthe item this work is on, in your own terms; a trial pairs on it (default: the ticket)
taskClassthe kind of task; faults are kept per class (default: default)
interactivefalse for background calls
personyour own identifier for the person this turn is for; the self is told what they have said is true of them
personLabela name for that person, stored once
personScopeyour word for the part of your product this call is in
personProjectyour project id, when the call is inside one
POST/selves/:handle/runadmin

Queue a routine step now.

Body

joborient|read|reflect|dream|battery|maintain|fetch
GET/selves/:handle/exportmember

Identity, lessons and the whole hash-chained record, without authority.

GET/selves/:handle/recordmember

The self's record, in order, a page at a time: every episode and decision as a hash-chained entry.

Query

afterthe last seq you hold (default: from the start)
limitup to 1000 (default 200)
typeonly entries of this type or its family, e.g. practice or belief.moved
POST/selves/:handle/episodesmember

Record something that happened to a self in your product, in your own type (a session kept, a lived day, a sale). It reflects on it like anything else.

Body

typeyour own lowercase name, e.g. session.kept
briefwhat happened
thoughtoptional
refsoptional object
surfaceoptional
importance0..1
POST/selves/:handle/traveladmin

Move a self into a world: its first arrival, or a crossing. Its record goes with it untouched, a running trial stays behind, and the destination kind's quarantine applies. Refused if its gate is looser than the laws.

Body

tothe world's id
GET/selves/:handle/passagesmember

Where a self lives, every border it crossed, and whether its record shows it arrived whole each time.

PUT/selves/:handle/toolsadmin

Which tenant tools a self may call during work (its charter's authority.tools) and whether it may use tools with effects.

Body

tools[tool names; a trailing * matches a prefix; [] none]
effectsread (default) | act

Personas

Ready characters a self can take on.

GET/personasmember

The tenant's personas (current versions), filtered by any dimension of a pack. Special-category values are withheld and cannot be filtered on here.

Query

packpack key (needed to filter)
whereJSON {dimension key: value | {in: [..]} | {gte|lte|gt|lt: n} (scalars) | {has: item} (sets) | {exists: bool}}
selfvoiced by this self
limit1..1000 (100)
afterhandle to page after
retired1 to include retired
POST/personasadmin

Create a persona: values validated against its pack's dimensions.

Body

handle
pack
values{dimension key: value}
groundingdemographic | persona (default) | evidence (provenance required)
provenance{source, ...}
selfhandle of the self that voices it
GET/personas/aggregatemember

Counts per group of a category, set or mixing-group dimension, with mean/min/max of a scalar measure. When a special-category dimension is involved, groups under 10 are suppressed.

Query

pack
bydimension key
measurescalar dimension key
whereJSON {dimension key: value | {in: [..]} | {gte|lte|gt|lt: n} (scalars) | {has: item} (sets) | {exists: bool}}
GET/personas/exportmember

Export a pack's personas as JSON or CSV text (special-category values withheld).

Query

pack
formatjson | csv
whereJSON {dimension key: value | {in: [..]} | {gte|lte|gt|lt: n} (scalars) | {has: item} (sets) | {exists: bool}}
POST/personas/importadmin

Import personas from CSV (header: handle, grounding?, dimension keys; sets as a|b, JSON cells allowed) or JSON (a list). Good rows are written, bad rows reported per row with their faults; dryRun writes nothing.

Body

pack
formatcsv | json
dataCSV text or a list of personas
groundingdefault for rows
provenancedefault for rows
dryRunboolean
GET/personas/:handlemember

A persona at a version (?version=, default current) and its versions. Special-category values are withheld.

PUT/personas/:handleadmin

Write a persona's next version (re-validated against its pack's latest version). merge keeps values not named; self links (or null unlinks) the self that voices it.

Body

values
mergeboolean
pack
grounding
provenance
selfhandle or null
DELETE/personas/:handleadmin

Retire a persona. Its versions stay readable.

People

The people your selves serve, as your system knows them.

GET/people/:key/memoriesmember

What this person of yours has said is true of them, in the scope you ask for. General memories (no scope) come back everywhere.

Query

scopeyour own word for the part of your product this call is in
projectyour project id, when the call is inside one
history1 to include memories that have been superseded
POST/people/:key/memoriesmember

Keep something this person said. A restatement of something already held writes nothing and names what it matched.

Body

texttheir words, kept verbatim
scopeleave out for a memory true of them everywhere
projectyour project id
sourcestated|agreed|observed
correctsthe id of the memory this replaces; its validity is closed rather than overwritten
labela name for the person, stored once
DELETE/people/:key/memories/:idmember

Forget one. The row is removed, not flagged, and what was removed comes back.

Swarms

Groups of selves working together under a principal.

GET/swarmsmember

The tenant's swarms: its designs whose members are selves, with members, process, protocol and last run.

PUT/swarms/:idadmin

Declare a swarm and its members (replaces membership): the next version of the design of selves called :id, keeping its process and protocol.

Body

name
purpose
members[{handle, weight?, role?}]
GET/swarms/:idmember

A swarm (its design's latest version), its members and recent runs, each with its round.

POST/swarms/:id/runsmember

Put one question to every member: a run of the swarm's design. Rounds (delphi, debate, critique-revise) follow on by themselves.

Body

question
choicesoptional 2..8 strings
briefoptional background
requestedBywho

Swarm runs

A swarm at work, step by step.

GET/swarm-runs/:idmember

A run: answers so far, tally (combined by the design's protocol), status, each answer's ticket, and the chain of rounds it belongs to.

POST/swarm-runs/:id/decidemember

The principal picks: every member that chose is rated on whether its calls match the principal's. Moves belief, never practice.

Body

pickindex of the chosen choice, from 0
bywho decided (default: the key's label)
POST/swarm-runs/:id/resultchecker

What the world chose, measured by a registered checker: one check per member that chose, on the run as the item (the only settlement that reaches a member's practice), and an outcome for the lessons its ticket served.

Body

pickindex of what the world chose, from 0
taskClassoptional (default swarm:<id>)
grounda short stable label for what measured it, e.g. crowd-meter; recurring failures are grouped by it
detailoptional narrative

Work

Standing work a self does on its own schedule.

GET/work/schedulesmember

Standing work: each schedule, its cadence, when it runs next, and its last run.

Query

selfonly this self's
POST/work/schedulesadmin

Give a self standing work. A task answers its instructions; research reads the web for its topics (one per line) into the self's memory. Times are the tenant's timezone.

Body

selfhandle
kindtask|research
title
instructions
every_hours1..672, default 24
at_hour0..23 or null
weekdays[1..7] ISO, or null for every day
review_requiredtrue holds the output for a person
deliveran object your product reads when it is done, e.g. {slack: true}
statusactive|paused
bywho
PATCH/work/schedules/:idadmin

Change a schedule. A new cadence counts from its last run.

Body

...any field of POST /work/schedules except self
DELETE/work/schedules/:idadmin

Remove a schedule. Its items stay.

POST/work/schedules/:id/runadmin

Run it now, unless an item of it is already queued or running. Its cadence is untouched.

Body

bywho
POST/work/itemsmember

Ask a self for one piece of work. It is run as a `work` job; watch for work.done in /events.

Body

selfhandle
titlewhat you would like done
instructions
requested_bywho
kindrequest|handoff
parent_idthe item this was handed on from
GET/work/itemsmember

Items newest first, a page at a time, with counts per local day and status.

Query

selfonly this self's
statusqueued|running|needs_review|approved|rejected|done|failed
dayYYYY-MM-DD, local
beforethe last id of the previous page
limit1..100
dayshow many days of counts, default 14
GET/work/items/:idmember

One item, its output {text, sources, summary}, and its schedule.

POST/work/items/:id/retrymember

Put a failed item back in the queue, as it was asked.

POST/work/items/:id/deliveriesmember

Record what your product did with a finished item's output on one channel (the core delivers nowhere itself). Replaces that channel's record.

Body

channele.g. slack
results[{channel, posted, why?}] or whatever your product reports
POST/work/items/:id/reviewadmin

Decide an item that waited for review.

Body

decisionapproved|rejected
bywho
notewhy
rating-2..5

Tickets

Proof of a turn, bound to the person it was for.

POST/tickets/:id/workmember

What was done with a context.

Body

briefwhat was asked and answered
thoughtoptional
POST/tickets/:id/outcomemember

How it went, credited to the lessons served on the ticket. A person's verdict is a signal; what the world later did is happened. Both move belief, never practice. A check sent here is read as an outcome: only a registered checker, at POST /checks, can move how a self works.

Body

signala person's verdict, -1..1
happenedwhat the world did: 1 or 0
labeluseful, not useful, used...
detailoptional reason
bywho
checkoptional {name, ran?, passed}: read as happened = passed

Runs

Every run announced before it starts, with each step written as it happens.

POST/runsmember

Begin a run, and say so before doing any of it. The row exists from this moment, which is what makes a run that is still happening — or one whose process died holding it — something you can see.

Body

kindwhat sort of work this is
labelwhat it is about, for a person reading a list
selfthe handle of the self doing it
parentthe run that asked, when this is part of a swarm
POST/runs/:id/stepsmember

Append a step. Send the frame you are already streaming: its type, its step name and its rawEvent. The same rows are what a watcher reads and what an auditor reads.

Body

typethe event type, e.g. StepStarted
namethe step name, e.g. recall
datathe rawEvent
costwhat THIS run spent — never what its children spent, so a total is a sum
POST/runs/:id/finishmember

End a run. Appends the closing step and stops the heartbeat mattering.

Body

okfalse if it failed
whywhat went wrong, when it did
GET/runsmember

Runs that are still open, oldest sign of life first. A run whose process died is one that is still open with a heartbeat that stopped — which is the thing a span cannot tell you.

Query

staleseconds without a step before a run is called stale; default 900
GET/runs/:idmember

One run, everything it started, and its steps from the beginning. The same rows a live watcher is reading.

GET/runs/:id/stepsmember

Steps after a sequence number. Watching live is asking with the last seq you saw; replaying is asking with 0.

Query

afterthe last seq you have
limithow many at most; default 500

Checkers

Registered instruments that judge a self's work.

GET/checkersmember

The instruments registered in this project: the only things that can mint a check.

POST/checkersadmin

Register an instrument. Its key is shown once; verdicts sent with it are minted as checks, the only evidence that can change how a self works.

Body

idlowercase id
namewhat the check is, e.g. npm test
authoredBywho wrote its criteria; a check whose criteria a self wrote never moves that self's practice
POST/checkers/:id/revokeadmin

Revoke an instrument's key. Checks it already minted stand in the record.

POST/checkers/:id/auditadmin

Report a checker's verdicts on items known to be bad. One pass on a bad item suspends it, and every adoption it helped decide is named for review.

Body

knownBad[{item, passed}]: what it returned on each known-bad item

Checks

A checker's verdicts, with the checker's own key.

POST/checkschecker

A checker's verdicts on a self's work, with the checker's own key. They close one episode: belief and the fault ledger read them, and a trial's gate when they are for one of its arms.

Body

selfthe self's handle (or ticket)
ticketthe ticket the work was done on; item, task class and version come from it
taskClassthe kind of task
episodeoptional id for the episode these close
results[{item, passed, ran?: complete|partial|inflight|never, ground?: what failed against what, detail?, practice?: the version that did the work}]

Proposals

Changes a self proposes to how it works, held until the gate decides.

GET/proposalsmember

Suggested changes.

Query

statusopen|approved|rejected|superseded|all
POST/proposals/:idadmin

Accept or decline a suggested change.

Body

decisionapproved|rejected
bywho

Lessons

What a self has learned and kept.

POST/lessons/:id/contestmember

A person doubts a lesson; it returns to being a hunch.

Body

bywho
whyoptional reason

Observations

What a self has seen.

POST/observationsmember

Push what happened in your own systems. It is read as from inside the organisation.

Body

sourceid
items[{title, body?, url?, publishedAt?}]
audiencehandles, or empty for all
trust0..1
GET/observationsmember

Recent observations, quarantined ones marked.

Query

limit1..200

Sources

Where a self reads from.

GET/sourcesmember

The tenant's sources and their last read.

PUT/sources/:idadmin

Declare a source.

Body

kindfeed|brave-news|brave-web|mcp|http
label
configconnector settings; secrets by env or file name only
channelnews|social|trade|own
lineageinternal|external
trust0..1
scheduledbool
audiencehandles
every_minutes>=15

Events

What happened, as a feed.

GET/eventsmember

What happened, in order, after an id.

Query

afterlast id seen
limit1..500

Forecasts

Forecasts, scored against what happened.

POST/forecastsadmin

Register a forecast BEFORE the moment it is for (409 once target_at has passed): value and 10–90% band, how it was made, an inputs hash (or inputs to hash), and the naive baselines' values for skill.

Body

subjectwhat is forecast
target_atISO date/time it is for
valuenumber
low10% quantile
high90% quantile
methodname (e.g. swarm+release)
designdesign key
runrun id
inputsanything; hashed (sha256, canonical JSON) when inputs_hash is absent
inputs_hash
baselines{name: value}, e.g. {last: 120, no_change: 0}
GET/forecastsmember

Registered forecasts, by target date, each with its outcome once scored.

Query

subject
method
scoredtrue | false
limit1..500 (50)
DELETE/forecasts/:idadmin

Remove a registered forecast and its outcome.

POST/forecasts/actualsadmin

Post actuals; every open forecast for that subject and day, made before it, is scored at once (absolute and percentage error, sMAPE, pinball, within band, error of each baseline).

Body

actuals[{subject, at, value}] (or one at the top level)
sourcewhere they came from
POST/forecasts/actuals/pulladmin

Pull actuals from one of the tenant's read-only tools (called with {subject, ...args}, answering {actuals: [{at, value}]}) and score them.

Body

tooltool name
subject
args{}
GET/forecasts/ledgermember

The accuracy ledger: per method, design or the whole tenant: scored count, MAE, MAPE, sMAPE, pinball, band coverage, skill against each baseline (1 - MAE/MAE baseline), month by month; per design also its backtests' skill.

Query

bymethod | design | tenant
subject

Backtests

A method tried on the past it never saw.

POST/backtestsadmin

Backtest: fit the release of a design, a run or an inline release on a measured series BEFORE the cut and forecast after it; scored only out of sample at each horizon (MAPE, sMAPE, MAE, pinball, cumulative error) beside naive baselines (last period carried forward, no further change, seasonal-naive when the cadence has a season), with a plain verdict on whether the model beat each. A signal and an outcome make it a calibration pair. All faults come back at once in faults [{path, message}].

Body

subjectwhat the series measures
series{values: [8..1000 numbers >= 0], kind: cumulative|per_period, cadence: hour|day|week|month|quarter, start?: YYYY-MM-DD, measure?: adoption (new adopters per period, default) | attention (level per period)}
cutpoints before the cut (4..n-1), or a date with series.start
horizonsdays, 1..8 (default [7, 30, 90])
designa design key (its release seeds the fit)
runor a run id
releaseor {channels, imitation, fatigue, fatigue_shape}
seedwhole number (1): the same seed, the same fit
seasonperiods per season (default day 7, week 52, month 12, quarter 4)
margin0..1 (0.05): how much lower the MAE must be to count as beating
signal{run, metric: mean|would_act|have_it_end|ever_had_it_end, measure?} (debiased by the run's measured prior) or {value, metric?}
outcomenumber observed (default: the total after the cut to the longest horizon)
GET/backtestsmember

Backtests, newest first, with their verdicts.

Query

designonly this design's
subjectonly this subject's
limit1..100 (20)
GET/backtests/:idmember

One backtest: the series, the fitted parameters (with an in-sample error that is never judged), per-horizon scores and skill against each baseline, the verdict, and the forecast with its 10–90% band beside the actuals and baselines.

DELETE/backtests/:idadmin

Delete a backtest and its calibration pair, for good (404 when not the tenant's). Event backtest.deleted.

POST/backtests/deleteadmin

Delete many backtests at once; those not found come back under missing.

Body

ids[ids], 1..200
POST/backtests/:id/archiveadmin

Archive (archived: true, the default) or bring back (false) a backtest. Archived backtests leave lists unless ?archived=1 (only archived) or all.

Body

archivedtrue | false
POST/backtests/archiveadmin

Archive or bring back many backtests at once.

Body

ids[ids], 1..200
archivedtrue | false

Calibration

How well stated chances matched outcomes.

GET/calibrationmember

The tenant's calibration maps, per design and signal metric: isotonic (5+ backtests) or bounded linear (3+) from the swarm signal to the observed outcome, with the leave-one-out error; uncalibrated below 3. Runs show raw, debiased and calibrated values in results.trust.

Query

designonly this design's

Designs

Everything a self, a population or a world is made of, declared in one place.

GET/designsmember

Swarm designs: the platform's templates (read-only: audience-test, ab-test, panel-delphi, diffusion) and the tenant's own, latest version of each.

Query

whopopulation | personas | selves
statusactive | retired | template
GET/designs/:keymember

A design at a version (?version=, default latest), its versions and its recent runs.

PUT/designs/:keyadmin

Save a design's next version (unchanged writes nothing), validated against its pack or its selves. Template keys are refused: start from one with from.

Body

name
purpose
froma template or design key to start from (spec fields given override it)
statusactive | retired
spec{who: {population: {pack, simulate, represents?, seed?, segments?, rake?} | personas: {pack, where?, limit?} | selves: [{handle, weight?, role?}], tiers?: {voiced?, surrogate?}}, structure?: {mixing?, graph?, homophily?, stubbornness?, seeding?: {share, who?}}, process?: {kind: one-off|rounds|debate|delphi (living: not yet), rounds?}, protocol?: {kind: ssr|stance-dist (populations) | vote|consensus|stance-dist|critique-revise (selves), threshold?}, time?: {step, horizon, report_every?, scale?, reference_date?}, stimulus?, variants?, ab?, measures?, voice?, dynamics?, observe?: {tool, args?, members?, max?}, calibration?: {targets: [{measure?, metric, target, tolerance?}]}, seeds?}
POST/designs/:key/runsmember

Run a design. Selves: a swarm run (any key). Population or personas: a simulation run in /sim/runs (admin key), or one per variant on the same seeds with a compare link.

Body

stimulus{text, kind?} (or the design's own)
variants[{label, text}] 2..3
seedoverride the population's seed
reference_dateYYYY-MM-DD
holdtrue: wait for observations, then release
versionrun an earlier version
questionselves: the question
choicesselves: 2..8
briefselves: background
POST/designs/:key/askmember

Bulk ask: one question to n members in one call. Selves: a weighted sample, each answer weighted by what it stands for. Population or personas (admin key): n voiced members, evenly or a weighted stratum sample, rated against the choices; partial results stream in the run's progress and sim.progress events.

Body

question
choicesoptional 2..11 strings
n1..500
strata[category dimensions or "segment"]
seed
briefselves: background
DELETE/designs/:idadmin

Delete every version of the tenant's own design (templates are never touched; runs keep their results), for good (404 when not the tenant's). Event design.deleted.

POST/designs/deleteadmin

Delete many designs at once; those not found come back under missing.

Body

ids[ids], 1..200
POST/designs/:id/archiveadmin

Archive (archived: true, the default) or bring back (false) a design. Archived designs leave lists unless ?archived=1 (only archived) or all.

Body

archivedtrue | false
POST/designs/archiveadmin

Archive or bring back many designs at once.

Body

ids[ids], 1..200
archivedtrue | false

Packs

Bundles of design to reuse.

GET/packsmember

Persona packs this tenant can use: the platform's (standard; read-only) and its own, latest version of each, with what each extends.

POST/packsadmin

Declare a pack of the tenant's own, or write its next version. It may extend a platform pack or one of its own (pinned to that pack's latest version). A platform pack key is refused (403): platform packs are read-only.

Body

keylower_snake
label
description
extendsa pack key, or null
GET/packs/:keymember

A pack at a version (?version=, default latest), its versions, and every dimension it carries: its own over those it inherits, each at the pinned version.

Dimensions

The forms a world can take.

GET/dimensionsmember

Declared persona dimensions, latest version of each, platform and own; ?pack= narrows to one pack.

POST/dimensionsadmin

Declare a dimension in one of the tenant's packs: its next version (an unchanged declaration writes nothing) and the pack's next version pinning it. Validated per type; every fault comes back at once in faults [{path, message}] (400). A dimension that plausibly duplicates an inherited one (the same key stem, e.g. age and age_band, or spec.refines naming it) is refused (409, faults at inherited.<key>) until the caller chooses: inherited: {<key>: {derive: fn}} makes the inherited dimension the pack's own, DERIVED from this one (never drawn, so members never contradict themselves), or {<key>: "keep"} keeps drawing both (recorded in spec.keeps). Examples per type: scalar {range: {min: 18, max: 90}, distribution: {kind: "normal", params: {mean: 40, sd: 12}}}; category {categories: [{label: "north", share: 0.4}, {label: "south", share: 0.6}]}; derived category {categories: ["under 30", "30+"], derived: {from: ["age"], fn: {kind: "band", of: "age", edges: [30], labels: ["under 30", "30+"]}}}; embedding {dims: 512}; set {items: ["vinyl", "streaming"], max_items: 2}; timeseries {of: "number", dynamics: {kind: "drift", rate: 0.01}}; edge {relation: "friend", structure: "mixing_group", homophily: ["region", "age_band"]}; event_process {process: "poisson", rate: {mean: 2, per: "month"}}.

Body

packyour pack key
keylower_snake
label
typescalar | category | embedding | set | timeseries | edge | event_process
inherited{<inherited key>: {derive: {kind: band|lookup|arith, ...}} | "keep"} when this key duplicates an inherited one
spec{range: {min, max} (scalar) | categories: [label | {label, share}], open? (category) | dims: 1..4096 (embedding) | items?, max_items? (set) | of: number|category|vector|point|object, dims? (timeseries) | relation, structure: explicit|mixing_group, homophily: [dimension keys] (mixing_group edge), to?: persona|self|group (edge) | process: poisson|hawkes|scheduled|renewal, rate: {mean >= 0, per: day|week|month|year} (event_process); distribution?: {kind: normal {mean, sd} | lognormal {mu, sigma} | uniform | beta {a, b} | gamma {shape, rate} | poisson {lambda} | categorical {shares: {label: share}} | empirical | fixed | derived (with from), params, source?: {kind: research|data|hand|derived, ref}}, derived?: {from: [1..8 dimension keys], fn: {kind: band, of, edges: [ascending], labels: [edges+1]} | {kind: lookup, of, map: {value: result}, default?} | {kind: arith, op: add|sub|mul|div|min|max, args: [keys | numbers | "$reference_year"]}}, refines?: <inherited key>, conditional_on?: [{dimension, when?, tilt: {}}], dynamics?: static | {kind: drift, rate: <per day>} | {kind: decay, half_life_days: >0} | {kind: event, on: name | [names]}, effects?: [{measure, direction: up|down|mixed|none, strength?: 0..1}], privacy?: normal|special_aggregate_only, ui_group?: Identity|Life|Money|Taste & style|Social|Shopping|custom}
GET/dimensions/:pack/:keymember

One dimension at a version (?version=, default latest) and its versions.

Project settings

Your project's models, timezone, vocabulary and programs.

PATCH/tenantadmin

Tenant settings.

Body

models{chat?: {rungs?: [rung names], model?, effort?}, embed?: {rung?, model?}} or null: which of this core's rungs and models answer this tenant; never a URL or key
timezoneIANA
vocabulary{organisation: how your selves refer to you, e.g. the house}
program{orient|read|reflect|context|swarm: {instruction}}: your own logic for each step
policy{ratersToPromote, retireAfter, recordBudget, recordsConfirm (its own records confirm lessons), confirmGraceDays, heldBudget, candidateBudget, perPersonCap, procedureTrials, procedureBudget}
identityKindsyour own identity kinds beyond the core's
battery{version, items}
dailyCallstenant-wide ceiling
webhook{url} to set (secret returned once) or null to remove
tools{mcp_url, auth: {tokenEnv: a variable granted to this tenant (LW_SECRET_<TENANT>_*)} | {tokenFile} | null, dialect?: standard|praecise, host?, effects?: [tool names that have effects], timeout_ms? 100..30000 (15000), max_output? 200..20000 chars (4000), budget? 1..12 calls per run (4)} or null: the tenant MCP endpoint its selves may call during work; a raw secret is refused
GET/tenant/toolsmember

The tenant's own tools: its endpoint setting (the credential never shown; auth.via env|file) and the tools it lists, each with effects (bool) and the selves allowed it. ?refresh=1 skips the cache.

GET/tenantmember

This tenant: name, timezone, settings (a webhook secret never), what its model calls resolve to, and its members.

GET/tenant/membersmember

Who may manage this tenant: the owner of record, admins and members, by account id with the email beside it.

Jobs

Long work, followed to the end.

GET/jobsmember

Recent and pending jobs, and the model slot's state.

MCP

The same core over the Model Context Protocol, for agents and tools.

POST/mcpmember

The same capabilities as MCP tools, for agents. Streamable HTTP, both protocol eras.

GET/mcpmember

405. The GET stream was removed in MCP 2026-07-28; named here so the refusal is contractual rather than a gap.

DELETE/mcpmember

405. Sessions were removed in MCP 2026-07-28; there is nothing to delete.

Contract

This document, machine-readable.

GET/contractmember

This contract.