Skip to content

Scenario reference ​

A scenario is one YAML or JSON document. ScenarioSchema in src/core/schema.ts validates it, and every type and default on this page is read from that schema: scenario files, POST /api/runs bodies and the MCP run_scenario tool all pass through the same definition. The parser drops keys it does not know, so a misspelt key does not fail validation; scenario.json in the run directory holds the effective scenario after defaults and parameter resolution.

Top-level fields ​

fieldtypedefaultnotes
scenarioIdnon-empty stringrequiredNames the run. A run id is <scenarioId>:<replicationId>.
seedintegerrequiredRoot of every random stream in the run.
replicationIdinteger, at least 00Set by the audit harness for each replication.
seedPathinteger[][]Derivation path below the root seed; the harness appends the replication index.
paramsobject{}Free-form values. Plugin options reference them with $param; audit axes target params.<name>.
populationobjectrequiredSize, persona fields and sampling. See Population.
modulesPluginSpec[][]World modules, stepped in order after every executor has acted.
executorsPluginSpec[][]Executors; each names the provider it decides with in options.provider.
providersmap of name to PluginSpec{}Decision providers, keyed by the name executors and composite providers use.
policyPluginSpec{ kind: allAgents }Activation policy: which agents act each tick and in which mode.
instrumentsInstrumentSpec[][]Metrics and questionnaires. See Instruments.
stepsStep[][]The schedule. See Steps.
llmobjectevery field defaultedGateway settings. See LLM.
promptobjectevery field defaultedHow persona, instructions and memory are written for the model. See Prompt.
pluginsstring[]omittedModules exporting register(registry), resolved relative to the scenario file.
hypothesisobjectomittedArms and outcomes for interventions and audits. See Hypothesis.

Plugin specs ​

modules, executors, providers, policy and instruments all use the same reference shape.

fieldtypedefaultnotes
kindnon-empty stringrequiredA kind registered in the plugin registry. See Built-in kinds.
namenon-empty stringthe kindInstance name. Metrics report under it; modules and providers are addressed by it.
optionsobject{}Validated by the plugin's own zod schema; an invalid option fails assembly with its path.

Executors, policies and providers do not accept extra fields. The focal executor's options are provider (required), components (the component list), entity (default agent), actions (a subset of the registered actions) and where (a predicate map that limits the executor to matching agents, for example where: { persona.role: player }). The cohort executor takes provider, features, transition, virtualActions, fallbackAction, neighborMean, where and recordFeatures; examples/echo_chamber/cohort.yaml shows all of them in use.

Parameter references ​

Anywhere a plugin option expects a value, and in population.n, an object of the form { $param: <name> } is replaced by params.<name> before the plugin is constructed. An optional map translates the parameter's value, as a string key, into the option value, so one enum or numeric parameter can drive options of another type:

yaml
params:
    framing: canonical
executors:
    - kind: focal
      options:
          components:
              - kind: instructions
                options:
                    text:
                        $param: framing
                        map:
                            canonical: You are playing a repeated prisoner's dilemma.
                            moralized: Cooperating is the honest choice.

An unknown parameter, or a value missing from map, fails validation at the option's path. population.n is resolved while the document is parsed and must give a positive integer; overriding params.n afterwards does not re-derive it. Because audit axes override dotted paths such as params.framing, one axis reaches every option that references the parameter.

Population ​

fieldtypedefaultnotes
npositive integer or { $param }requiredPopulation size.
fieldsPersonaField[][]Persona columns, materialised as persona.<name>.
source{ kind: synthetic }, { kind: csv, path } or { kind: json, path }{ kind: synthetic }Where the rows come from.
provenancedemographic, survey, interview or syntheticsyntheticRecorded provenance of the population.
stratifymap of field to map of value to weightomittedTarget proportions per value, for example role: { player: 1, opponent: 1 }.

A persona field:

fieldtypedefaultnotes
namenon-empty stringrequired
dtypef64, i32, bool, str or strlistrequiredColumn type.
privatebooleanomittedA private field never enters a prompt and is stripped from API agent rows.
samplingsee belowrequiredHow the value is drawn.

Sampling is one of { kind: value, value } (a constant), { kind: choice, choices, weights? } (at least one choice; optional non-negative weights) or { kind: range, min, max } (numeric range).

Steps ​

Steps run in order. A scenario without steps runs no ticks.

stepfieldseffect
{ kind: run, ticks }ticks: positive integerAdvances the simulation by that many ticks.
{ kind: intervene, arm, instruction? }arm: the name of an arm in hypothesis; instruction: stringApplies the arm's overrides and delivers its instruction to the selected agents through the instructions component.
{ kind: questionnaire, name, targets? }name: an instrument of kind questionnaire; targets: a selectorInterviews the targeted agents; answers become measurement events and never touch the world.
{ kind: checkpoint }Writes checkpoints/<tick>/ with the world snapshot, clock, executor and provider state.

simulacra resume continues from a checkpoint directory and skips intervention and questionnaire steps that follow it; run those from a fresh start.

Selectors ​

targets in a questionnaire step and selection in a hypothesis arm are selectors:

fieldtypedefaultnotes
wheremap of column to predicaterequiredAll predicates must hold.
fractionnumber in [0, 1]omittedRandom share of the matching agents.
npositive integeromittedFixed number of the matching agents.

A predicate is { in: [values] }, { gt: number }, { lt: number } or a literal value for equality. Operator objects are strict: { gt: 1, typo: 2 } is rejected rather than read as an equality test against the object.

LLM ​

The llm block configures the one gateway every LLM-backed plugin shares. Omitting the block, or any field in it, gives the DeepSeek preset.

fieldtypedefaultnotes
baseUrlnon-empty stringhttps://api.deepseek.com/v1Any OpenAI-compatible endpoint.
modelnon-empty stringdeepseek-v4-flashAlso the target of the model_substrate audit axis and of a plan's models list.
apiKeyEnvnon-empty stringSIMULACRA_LLM_API_KEYName of the environment variable that holds the key; the key itself is never in YAML.
modelive, record or replayliverecord writes every response under recordDir; replay answers from it, offline.
recordDirnon-empty stringomittedRecording directory, relative to the scenario file.
concurrency.initialpositive integer4Starting in-flight limit of the gateway's AIMD controller.
concurrency.maxpositive integer16Ceiling of the same controller.
structuredauto, json_schema or promptautoStructured-output mode; auto probes the endpoint for json_schema support.
budget.maxCallsinteger, at least 01000Calls per run before the gateway refuses further requests.
budget.maxCompletionTokenspositive integer512Per-call completion cap; LLM providers cannot ask for more.
timeoutMspositive integer60000Per-request timeout.
sendSeedbooleantrueWhether the request carries a seed field; the mlx-lm preset turns it off.
extraobjectomittedExtra request-body fields, such as { thinking: { type: disabled } } for DeepSeek. Keys the gateway owns cannot be overridden.

Prompt ​

The prompt block decides how the same persona, instructions and memory are written for the model. Each field is the default target of one representation axis in the audit catalogue.

fieldvaluesdefault
personaFormatplain, bullets, tableplain
instructionOrderfirst, lastfirst
rolePlacementsystem, usersystem
namingid, name, anonymousid
memoryRepresentationtranscript, json, bulletstranscript
contextWindowpositive integer4000

Instruments ​

An instrument entry is a plugin spec plus every, a positive integer giving the tick interval (default every tick). A kind registered as a metric computes a number, or a list of numbers, from the world and the event log and writes a measurement event; a kind registered as an instrument is a questionnaire that questionnaire steps refer to by name. The questionnaire kind takes questions (at least one of { id, prompt, responseType, choices? } with responseType one of text, integer, float or choice, and choices required for choice) and entersMemory (default true).

Hypothesis ​

fieldtypenotes
idnon-empty string
claimstringThe claim in words.
claimTypeexploratory, mechanism or policyPolicy claims need axes at all three levels for a strong grade.
armsArm[]{ name, role: treatment or control, overrides (default {}), selection? }.
outcomesOutcome[]{ name, metric, direction: increase, decrease or any (default any), targetDistribution?: number[] }.

An intervention step names an arm; its overrides are applied to the scenario and its selection chooses the agents. In an audit plan, the outcomes give each metric its expected direction and, with targetDistribution, the target for the distribution tests.

Built-in kinds ​

Each kind validates its own options; the file named holds the schema.

slotkinds
modulessocialGraph (directed edge table, follow and unfollow, generated once before tick 0; src/modules/socialGraph.ts), feed (post table, post, repost, reply, like and silent, a ranked feed per user; recommenders random, recency, followingFirst, homophily; src/modules/feed.ts), calendar (src/modules/calendar.ts)
executorsfocal (src/agents/focal.ts), cohort (src/agents/cohort.ts)
components, in focalpersona, instructions, recentMemory, summaryMemory, feedObservation, neighborhoodObservation (src/agents/components/)
transitions, in cohortopinionDynamics (src/agents/transitions/opinionDynamics.ts)
providersllm, mock, rule, cohortRule, archetype, surrogate, cache, topo, aps (src/providers/); rule needs a rule function registered in code, so plugins usually register their own kind instead
policiesallAgents, bernoulli (p), profileHourly (column, ticksPerHour), maskTimer (maskColumn, timerColumn), explicit (schedule); all accept entity and mode (src/policies/index.ts)
metrics, as instrumentscolumnMean, cooperationRate, averagePayoff, stanceAssortativity, sameGroupRatio, actionShare, tvdToTarget (src/metrics/index.ts)
instrumentsquestionnaire (src/instruments/questionnaire.ts)
adaptersscript, oasis (src/adapters/)

The shipped scenarios in examples/ use most of these; the shortest complete scenario in the repository is the YAML string in examples/programmatic/02-custom-plugin.ts, shown on the plugins page.