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 |
share | true for a 0/1 outcome (interval kept in [0, 1]) |
alpha | default 0.05 |
lambda | weight on the model in [0, 1]; tuned by default |
cells | instead: [{cell, labelled, unlabelled}] |
shares | with 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?}] |
budget | total 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
method | split | group | weighted |
alpha | miscoverage, default 0.1 |
scores | split and weighted: past |error| values |
predictions | split and weighted: points to put intervals round |
calibration | group: [{groups, score}] |
points | group: [{groups, prediction}] |
times | weighted: when each score was made |
now | weighted |
halfLife | weighted: 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}] |
horizon | years ahead |
nationalSd | sd of the national forecast |
pooled | {population: [{year, e0}]} to fit tapered gains |
start | e0 to project from with the fitted gains |
years | default 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}] |
birthYear | without cells |
egoAge | without cells; omit for lifetime |
alive | true to count siblings still alive at egoAge |
simulated | {cell: count} |
survey | {cell: count} |
tolerance | default 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
method | multicalibrate | smooth | floor |
units | multicalibrate: [{p, y, groups}] |
apply | multicalibrate: [{p, groups}] to correct |
alpha | multicalibrate: tolerance, default 0.02 |
cells | smooth: [{party, ageIndex, n, mean}]; floor: [{cell, predicted, observed, n}] |
priorSdRW | smooth: step between age bands, default 0.05 |
shares | smooth: {party|age: share} |
q | floor: 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
method | enkf | history |
ensemble | enkf: members × parameters |
predicted | enkf: members × observations |
observed | enkf |
obsSd | enkf: one or per observation |
inflation | enkf: default 1 |
X | history: design points |
outputs | history: simulator outputs per design point |
targets | history: [{value, obsSd, modelDiscrepancySd}] |
candidates | history: points to test, or bounds and nCandidates |
cutoff | history: default 3 |
seed | default 1 |
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
kind | a kind declared at PUT /universe/kinds/:id (optional) |
name | text |
pack | the 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
status | paused|running|done|failed |
limit | 1..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
scale | optional: a preset or {sim, real} |
calls_per_day | optional: 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
POST/worlds/:id/jumpadmin
Advance a paused world to a later time.
Body
to | a 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
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
scale | a preset or sim=real |
calls_per_day | members 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
from | tick |
to | tick |
limit | 1..2000 (500) |
events | false 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
after | member index |
limit | 1..500 (100) |
tick | a reached tick |
GET/worlds/:id/members/:imember
One member: as in the list, plus ties and memory (private life events withheld).
Query
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
at | duration from the start, seconds or a date |
kind | drop|campaign|outside|price|influencer|life |
label | text |
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
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
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
pack | the 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?} |
step | any duration ("10min", "1h", "1d") |
horizon | a duration ("40d") |
report_every | a duration |
chunk | rows per chunk |
seeds | 1..20 (spread across seeds) |
reference_date | YYYY-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 |
hold | true: 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
status | queued|running|done|failed|cancelled |
limit | 1..100 (20) |
design | only 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
after | the last index of the previous page |
limit | 1..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) |
source | what produced them |
release | true 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
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
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
POST/sim/runs/archiveadmin
Archive or bring back many runs at once.
Body
ids | [ids], 1..200 |
archived | true | 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
subject | only the selves belonging to this user of yours |
POST/selvesadmin
Give birth to a self. Fails if the handle exists.
Body
character | optional {personality: Big Five 0..1 | genome: {effectsSeed, seed}, axes?: {stance: -1..1}, tastes?: {taste: -1..1}}: who it is beneath its charter |
procedure | optional: how it works at birth |
handle | lowercase id |
name | what people call it |
charter | {purpose, answersTo?, authority?: {reach?: source ids}, reviewBy?} |
persona | {default?, surfaces?} |
interests | string[] |
routine | {mode?: scheduled|on-activity, read?, reflect?, reflectAfter?, maxCalls?, maxTokens?} |
identity | [{kind, key, value}] |
seed | {story?, lessons?: [{claim, appliesWhen?, surfaces?}]} |
subject | the user of yours it belongs to |
program | its own instructions per step, after the tenant's |
orient | false to skip orientation (bulk births) |
status | active|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 | |
handle | a new handle; the self keeps its whole life |
charter | |
persona | |
routine | |
interests | |
program | |
status | active|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
text | how this self should do the work |
why | optional reason |
POST/selves/:handle/procedure/revertadmin
Go back to an earlier procedure.
Body
version | which version |
why | optional 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
axis | the stance |
truth | where the outcome sits, -1..1 |
outcome | what settled it |
weight | optional 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
name | the 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
kind | value|trait|commitment|taste|voice|expertise|relationship |
key | |
value | null removes |
POST/selves/:handle/contextmember
The self's context for one call on one surface, and a ticket for its work and outcome.
Body
surface | where it is being asked |
task | what it is being asked |
item | the item this work is on, in your own terms; a trial pairs on it (default: the ticket) |
taskClass | the kind of task; faults are kept per class (default: default) |
interactive | false for background calls |
person | your own identifier for the person this turn is for; the self is told what they have said is true of them |
personLabel | a name for that person, stored once |
personScope | your word for the part of your product this call is in |
personProject | your project id, when the call is inside one |
POST/selves/:handle/runadmin
Queue a routine step now.
Body
job | orient|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
after | the last seq you hold (default: from the start) |
limit | up to 1000 (default 200) |
type | only 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
type | your own lowercase name, e.g. session.kept |
brief | what happened |
thought | optional |
refs | optional object |
surface | optional |
importance | 0..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
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] |
effects | read (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
pack | pack key (needed to filter) |
where | JSON {dimension key: value | {in: [..]} | {gte|lte|gt|lt: n} (scalars) | {has: item} (sets) | {exists: bool}} |
self | voiced by this self |
limit | 1..1000 (100) |
after | handle to page after |
retired | 1 to include retired |
POST/personasadmin
Create a persona: values validated against its pack's dimensions.
Body
handle | |
pack | |
values | {dimension key: value} |
grounding | demographic | persona (default) | evidence (provenance required) |
provenance | {source, ...} |
self | handle 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 | |
by | dimension key |
measure | scalar dimension key |
where | JSON {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 | |
format | json | csv |
where | JSON {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 | |
format | csv | json |
data | CSV text or a list of personas |
grounding | default for rows |
provenance | default for rows |
dryRun | boolean |
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 | |
merge | boolean |
pack | |
grounding | |
provenance | |
self | handle or null |
DELETE/personas/:handleadmin
Retire a persona. Its versions stay readable.
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
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
self | handle |
kind | task|research |
title | |
instructions | |
every_hours | 1..672, default 24 |
at_hour | 0..23 or null |
weekdays | [1..7] ISO, or null for every day |
review_required | true holds the output for a person |
deliver | an object your product reads when it is done, e.g. {slack: true} |
status | active|paused |
by | who |
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
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
self | handle |
title | what you would like done |
instructions | |
requested_by | who |
kind | request|handoff |
parent_id | the 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
self | only this self's |
status | queued|running|needs_review|approved|rejected|done|failed |
day | YYYY-MM-DD, local |
before | the last id of the previous page |
limit | 1..100 |
days | how 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
channel | e.g. slack |
results | [{channel, posted, why?}] or whatever your product reports |
POST/work/items/:id/reviewadmin
Decide an item that waited for review.
Body
decision | approved|rejected |
by | who |
note | why |
rating | -2..5 |
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
subject | what is forecast |
target_at | ISO date/time it is for |
value | number |
low | 10% quantile |
high | 90% quantile |
method | name (e.g. swarm+release) |
design | design key |
run | run id |
inputs | anything; 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 | |
scored | true | false |
limit | 1..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) |
source | where 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
tool | tool 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
by | method | 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
subject | what 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)} |
cut | points before the cut (4..n-1), or a date with series.start |
horizons | days, 1..8 (default [7, 30, 90]) |
design | a design key (its release seeds the fit) |
run | or a run id |
release | or {channels, imitation, fatigue, fatigue_shape} |
seed | whole number (1): the same seed, the same fit |
season | periods per season (default day 7, week 52, month 12, quarter 4) |
margin | 0..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?} |
outcome | number observed (default: the total after the cut to the longest horizon) |
GET/backtestsmember
Backtests, newest first, with their verdicts.
Query
design | only this design's |
subject | only this subject's |
limit | 1..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
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
POST/backtests/archiveadmin
Archive or bring back many backtests at once.
Body
ids | [ids], 1..200 |
archived | true | false |
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
who | population | personas | selves |
status | active | 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 | |
from | a template or design key to start from (spec fields given override it) |
status | active | 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 |
seed | override the population's seed |
reference_date | YYYY-MM-DD |
hold | true: wait for observations, then release |
version | run an earlier version |
question | selves: the question |
choices | selves: 2..8 |
brief | selves: 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 | |
choices | optional 2..11 strings |
n | 1..500 |
strata | [category dimensions or "segment"] |
seed | |
brief | selves: 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
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
POST/designs/archiveadmin
Archive or bring back many designs at once.
Body
ids | [ids], 1..200 |
archived | true | false |
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
pack | your pack key |
key | lower_snake |
label | |
type | scalar | 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.