Skip to content

Overview — two loops, one infrastructure

SocioVerse expresses a social simulation as one abstraction, B = f(P, E), and runs it as two loops over one infrastructure:

  • the longitudinal simulation loop moves a run forward one step at a time, E_{t+1} = g(E_t, B_t);
  • the controllable research loop moves the study forward one edit at a time, σ_{k+1} = h(a_k, σ_k);
  • the social science agentic infrastructure carries both — the skills that author a study, the services that ground P and E in real data, and the records that make a finished study auditable and re-runnable.

This page gives the formulas and the map. Each loop then has its own page with the mechanism, the functions that implement it, and what it writes to disk.

flowchart LR
    subgraph INFRA["Agentic infrastructure — workflow · services · governance"]
        SIGMA["study state<br/>σ<sub>k</sub> = (P, E<sub>0:T</sub>, f, Θ)"]
    end
    SIGMA --> S1
    subgraph LOOP["Longitudinal simulation loop — one step t"]
        S1["① exogenous update<br/>advance_to(t)"] --> S2["② observe<br/>observe_batch"]
        S2 --> S3["③ decide<br/>decide_batch = f"]
        S3 --> S4["④ apply<br/>apply = g"]
        S4 --> S5["⑤ record<br/>panel + metrics"]
    end
    S5 -.->|"t + 1"| S1
    S5 --> T[("trajectory<br/>study.duckdb")]
    T -->|"edit a<sub>k</sub> — branch or version"| SIGMA

1. B = f(P, E)

The abstraction is borrowed from Kurt Lewin's field theory: behaviour is a function of the person and their environment. Its three terms are independently replaceable, which is what lets one runtime host Schelling segregation and macro-economic nowcasting alike:

  • P may be synthesized from census marginals, drawn from a real-platform user pool, or constructed by the study itself;
  • E may be grounded in real event streams or designed as a stylized experiment;
  • f may be a classical ABM rule, an LLM prompt chain, a trained RL policy, or a hybrid that assigns different implementations to different agent groups.

Longitudinal, not cross-sectional

Frameworks like AgentSociety or OneSim excel at cross-sectional studies: many single-round experiments, often over freshly sampled crowds. SocioVerse targets the complementary question — how do the same people change over time?

That inverts two design choices:

cross-sectional SocioVerse (longitudinal)
population resampled per experiment fixed pool, persistent ids
environment mostly static per round dynamic — the thing that changes
output outcome distributions panel data: one row per agent per step

The persistent agent id is the longitudinal key: it is what lets you ask "show me household x-042 across all twelve steps" instead of only "what did the aggregate do".

The two-axis environment

E is dynamic along two independent axes, giving each agent a four-quadrant view of the world at every step:

macro local
physical the world's global state the agent's neighbourhood state
information broadcasts everyone receives feeds & messages scoped to this agent

Two kinds of change drive E forward, and they are two separate calls in the step: exogenous change is the schedule you declared in the environment bundle (advance_to(t)), endogenous change is the feedback of the agents' own actions (apply(actions)).

2. The longitudinal simulation loop

Inside one run, the environment carries the consequences of behaviour forward:

e_t^i  = ω(E_t, p_i)                 # the agent's four-quadrant observation
b_{i,t} = f(p_i, e_t^i, m_{i,t})     # its typed action, given a bounded memory window
B_t     = { b_{i,t} }_{i=1..N}
E_{t+1} = g(E_t, B_t)

Step 0 materializes P under the declared seed and builds E_0, records the initial panel rows, and computes no decision. Every later step runs the same five phases in a fixed order, implemented by LongitudinalSimulator.run() in socioverse/engine/loop.py:

phase call in run() what it does
① exogenous update env.advance_to(t) fires the events and broadcasts scheduled for t
② observe env.observe_batch(ids, t, r) projects E_t onto each agent (modality × scope)
③ decide decision.decide_batch(obs, memories) computes B_t = f(P, E_t, memory), one typed action per agent
④ apply env.apply(actions) folds the behaviours back in: E_{t+1} = g(E_t, B_t)
⑤ record store.record(...) + memory push one panel row per agent, one metrics row, the fired events

The main loop: E_t → B_t → E_{t+1}, with the exogenous channel and the per-step panel

Three properties of this ordering are deliberate:

  1. An intervention enters at phase ①, before anyone observes, so every agent of a round decides against the same state.
  2. Decisions are taken per step. decide_batch receives the whole step's observations at once and returns one action per agent, so every agent of a step decides against the same E_t. How the LLM is called inside is the model's choice: from-scratch studies send one call per agent, concurrently; a legacy adapter keeps its engine's own pattern (the call pattern).
  3. Every step is recorded as it is produced. Because apply is a function of the recorded actions, a run can be re-executed from its stored rows with no decision model at all — which is what a branch inherits.

→ The Simulation Loop — the phases in detail, the typed hand-offs (Persona[] → Observation[] → Action[] → metrics, all Pydantic models in socioverse/schemas/), the memory window, and the DuckDB tables.

3. The controllable research loop

A longitudinal study is rarely a single run. The study itself is an object you edit. Let the study state be

σ = (P, E_{0:T}, f, Θ)        # Θ = metrics, resource manifest, seed and run settings

An edit a_k = (c, u) names the components c ⊆ {P, E, f, Θ} it touches and the update u applied to them. Either the researcher or the agent may raise it, and h applies it to give the next version. Three cases exhaust h:

c outcome what runs in the code
∅ unchanged nothing the current version stays current
{E} after a fork step t* branch (分支) steps 0..t*-1 are replayed from the parent's stored actions (no LLM, no spend), only t*..T are computed create_branch(study_dir, note, fork_step=…)
anything else version (版本) a cold run from step 0 create_version(study_dir, note, …)

Both live in skills/sv_workspace.py, and both append an entry to the study's versions.json manifest with id, parent, note, kind ("branch" or "version"), fork_step, and the re-entry stage. Creating a version first freezes the whole live workspace into versions/v<k>/, so every earlier version stays inspectable and re-runnable.

A branch only earns its replay if nothing but the post-fork environment differs. check_branch_invariant() verifies that before the run, and on a violation it downgrades the branch to a version rather than failing: the run goes cold from step 0, the results stay correct, and the reason is recorded on the manifest entry.

fork means something else here

In this documentation fork is reserved for the study level: /sv-init forks a matching reference study into a new study of your own (Path A). Within a study you create branches and versions, never forks.

→ The Research Loop — the version gate, the manifest fields, and how to compare versions. → Intervention & Branching — what a ScheduledEvent or Broadcast declares, how replay reproduces the parent exactly, and how to read Δ_t(δ) off two branches.

4. The social science agentic infrastructure

Both loops run on one infrastructure, organized in three layers.

layer what it does where it lives
workflow decomposes the research lifecycle into skills; each reads earlier artifacts, writes exactly one validated artifact, and pauses at a checkpoint .claude/skills/ — sv-init, sv-build-model, sv-build-environment, sv-build-population, sv-run, sv-report (with sv-lit / sv-paper), sv-iterate
services resolve P and E from real-world data at build time, as typed MCP tools rather than datasets copied into the study the population service (sv-user-pool) and the event service (sv-event-tool)
governance records what each stage did, which data it used, and what you approved grounding/grounding.json, resources.json, versions.json, the events table in trajectory/study.duckdb

The agentic pipeline: one validated artifact per stage, checkpoints between stages, a spend gate before the run

Two consequences are worth stating plainly:

  • The runtime fetches nothing. Services are called while a study is being built; their results are materialized into the environment and population bundles. A run therefore is self-contained and repeatable.
  • Load-bearing numbers cite a source. Every study carries a grounding sidecar (grounding/grounding.json) holding facts with sources, modeling references, and declared assumptions. What cannot be sourced is declared rather than silently invented.

→ Data Services — the two MCP services and what they materialize. → Grounding & Provenance — the sidecar's fields and how the build stages extend it.

Where each concept lives

in the formula in the code
P — the fixed population socioverse/abc/population.py (PopulationProvider.build(seed)); bundle population/population.json + population/roster.jsonl
E — the dynamic environment socioverse/abc/environment.py, reusable layers in socioverse/env_layers/; bundle environment/environment.json
f — the behaviour function socioverse/abc/decision.py (DecisionModel.decide_batch)
g — the environment update EnvironmentProvider.apply(actions)
ω — the observation operator EnvironmentProvider.observe_batch(ids, t, round)
m — the memory window socioverse/engine/memory.py (AgentMemory, 8 steps by default)
B_t — the step's behaviours Action in socioverse/schemas/runtime.py; the panel table's action_kind / action_payload
Θ — the run settings simulation/simulation.json (seed, n_steps, interaction_rounds, the *_ref / *_args bindings) + resources.json
the simulation loop socioverse/engine/loop.py (LongitudinalSimulator.run)
the trajectory socioverse/io/duckdb_store.py → trajectory/study.duckdb (panel / metrics / events / messages)
σ and its versions versions.json + skills/sv_workspace.py (create_version, create_branch, check_branch_invariant)
the typed contracts socioverse/schemas/ — validated on write and on read at every stage boundary

Next: Anatomy of a Study — what's inside studies/<id>/.