Overview — B = f(P, E)¶
SocioVerse is built around one abstraction, borrowed from Kurt Lewin's field
theory: behaviour is a function of the person and their environment,
B = f(P, E). Everything in the runtime is a concrete answer to "what is P,
what is E, and how does f close the loop over time".
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:
- Exogenous — scheduled interventions and information broadcasts you declare in the environment artifact ("a policy announcement at step 3").
- Endogenous — the feedback of the agents' own actions
(
apply(actions)closesE_t → E_{t+1}).
The loop¶
sequenceDiagram
participant Eng as Engine (loop)
participant E as Environment (E)
participant P as Population (P)
participant D as Decision model (f)
participant S as Panel store
Note over Eng: t = 0 — build P once, reset E
loop each step t = 1..N
Eng->>E: advance_to(t) — exogenous events & broadcasts
Eng->>E: observe_batch() → Observation[] (4-quadrant view)
Eng->>D: decide_batch(observations) → Action[] (= B)
Eng->>E: apply(actions) — endogenous feedback
Eng->>S: collect metrics + record panel rows
end
The same loop, drawn with what actually flows through it — including the exogenous channel and the panel it leaves behind:

Three properties of the loop are deliberate:
- P is built once. Personas get deterministic, persistent ids at build time and the pool never changes mid-run — that is what makes the output panel data.
- Decisions are batched.
decide_batchreceives all observations at once; there is no per-agent LLM call in a hot loop. - Every step is recorded. One panel row per agent per step, plus aggregate metrics and fired events, land in a durable DuckDB store.
Typed hand-offs¶
The data flowing between stages is typed end to end:
Each of these is a Pydantic model in socioverse/schemas/, and every
stage boundary is guarded by strict validation — a malformed artifact fails
at the gate, not three stages later.
Grounded by construction¶
A study is only as good as the numbers it is built from. Every study carries
a grounding sidecar (grounding/grounding.json): facts with sources,
modeling references, and declared assumptions. The build stages consult and
extend it — load-bearing environment and population values cite a fact or an
assumption id, and what cannot be sourced is declared rather than silently
invented. See Grounding & Provenance.
Where each concept lives¶
| concept | in the code |
|---|---|
| the interfaces a study implements | socioverse/abc/ |
| the loop that drives them | socioverse/engine/ |
| the typed contracts | socioverse/schemas/ |
| reusable information-axis layers | socioverse/env_layers/ |
| the durable panel store | socioverse/io/duckdb_store.py |
Next: Anatomy of a Study — what's inside studies/<id>/.