Usage Overview¶
This guide covers the complete workflow for running SiliSocS simulations: from configuration to output analysis.
How It Works¶
The simulation runs in four phases:
sequenceDiagram
participant Config as Hydra Config
participant Runner as Runner
participant Builder as Agent Builder
participant Init as Initializers
participant GM as Game Master
participant Backend as Environment Backend
Config->>Runner: Load & compose YAML configs
Runner->>Builder: Build agent configs
Runner->>Backend: Construct backend
Runner->>GM: Construct Game Master + components
Runner->>Init: Agents, Game Masters, simulation setup
Init->>GM: Initialize backend state and seed content
Runner->>GM: Start Engine loop
loop Each step
GM->>Backend: Observe environment state
GM->>Backend: Execute @app_action
GM->>Runner: Log events + deploy probes
end
Phase 1, Config composition: Hydra merges the base simulation config, environment config, and world config into a single resolved config tree.
Phase 2, Agent construction: The agent builder reads the persona pipeline (or custom builder logic) and creates agent configs with personas, memories, and goals. Runtime construction then creates live agents.
Phase 3, Runtime initialization: The Engine runs agent initialization, Game Master initialization, then simulation initialization before the main loop.
Phase 4, Simulation loop: Each step, agents observe environment state,
decide on an action, and the game master executes it against the configured
backend. Each backend provides its own observations through the GM component
slots; app_observation delegates directly to BackendApp.observe(...).
Probes are deployed on schedule.
Running a Simulation¶
For multi-condition research orchestration (hypothesis trees, seed sweeps, and built-in evaluators), use Experiment Studies.
CLI (Recommended)¶
The primary entry point is the silisocs CLI command:
# Check your environment first (Python, optional extras, API keys, writability)
uv run silisocs doctor
# First time? Run a guided deterministic demo (no API key needed) — it runs a
# small scripted simulation, tours the artifacts it produced, and prints the
# next commands to try
uv run silisocs tutorial
# Run with defaults
uv run silisocs
# Override parameters via Hydra
uv run silisocs num_agents=10 num_steps=5 sim.llm.name=gpt-4o
# Use a different backend
uv run silisocs env=reddit_like
# Run the packaged resource-market preset
uv run silisocs world=resource_market agents=resource_market env=resource_market
# Run the packaged virtual-space preset
uv run silisocs world=virtual_space agents=virtual_space env=virtual_space
# Run curated external backend examples
uv run silisocs --config-path scenarios/resource_market/conf world=resource_market agents=resource_market env=resource_market
uv run silisocs --config-path scenarios/virtual_space/conf world=virtual_space agents=virtual_space env=virtual_space
# Run an external scenario
uv run silisocs --config-path scenarios/election/conf
Hydra CLI Overrides¶
Any config value can be overridden from the command line using dot notation:
uv run silisocs \
num_agents=100 \
num_steps=50 \
seed=42 \
env.gm.components.initialize.params.graph.network_type=random
# Switch GM resolve mode to tool-calling
uv run silisocs \
env.gm.components.resolve.built_in=tool_calling \
sim.tool_calling.mode=single
# Override only who can act next
uv run silisocs env.gm.components.next_acting.built_in=all_agents
Studio¶
For a visual interface:
Install the silisocs[studio] extra when using a lean package install. Studio
provides scenario and study composers, launch/watch controls, platform viewers,
artifact analysis, and report export in one product.
See Studio for details.
Configuration System¶
The project uses Hydra for hierarchical YAML configuration
with composition. The top-level package config is
src/silisocs/conf/experiment.yaml and composes these groups:
# experiment.yaml
defaults:
- world: default # Root run params, setting, event, and world data
- agents: default # Agent construction and personas
- sim: base # Simulation parameters
- env: twitter_like # Backend and GM wiring
- eval: base # Probes and evaluation config
Config Hierarchy¶
src/silisocs/conf/
├── experiment.yaml # Top-level composition
├── agents/
│ └── default.yaml # Persona pipeline defaults
├── world/
│ └── default.yaml # Root run params and default world
├── sim/
│ └── base.yaml # LLM, engine, tool-calling, memory, checkpoints
├── env/
│ ├── twitter_like.yaml # Local Twitter-like backend
│ ├── reddit_like.yaml # Local Reddit-like backend
│ └── mastodon.yaml # Remote Mastodon server
└── eval/
└── base.yaml # Probe and logging defaults
See Configuration Reference for all options.
For environment-level customization (Engine + GM components + backends), see Environment Layer.
External Scenarios¶
Scenarios can live outside the package in scenarios/<name>/conf/:
scenarios/election/
├── conf/
│ ├── world/default.yaml # @package _global_
│ ├── agents/default.yaml # @package agents
│ ├── sim.yaml # Optional partial sim override
│ ├── env.yaml # Optional partial env override
│ └── eval.yaml # Optional partial eval override
├── builders.py # Optional importable custom agent builder
└── outputs/ # Simulation output (auto-created)
Run with:
The runner reads scenario_name from the world config automatically, so you
usually do not need a manual world= override.
Agent Pipeline¶
Agents are defined in the persona_pipeline section of your scenario config.
There are two methods:
Method 1: YAML Pipeline (Declarative)¶
Define agent classes with data sources and field mappings:
builder:
class_path: null
params: {}
persona_pipeline:
classes:
user:
count: 100
class_path: silisocs.agents.native.NativeAgent
data:
source: inline
records:
- name: Alex
persona: Alex is a local organizer who posts about public services.
field_map:
name: name
context: persona
Hugging Face datasets are available with the hf extra:
Method 2: Custom Builder (Programmatic)¶
Set agents.builder.class_path when you need programmatic agent-spec logic:
from silisocs.runtime.construction.agent_builders import AgentBuilder
from silisocs.runtime.construction.specs import AgentConfig
class MyScenarioAgentBuilder(AgentBuilder):
def build_agent_configs(self):
return [AgentConfig(...) for _ in range(3)]
See Building Agents for the full guide.
Per-Agent LLM Models¶
You can assign different LLM models at three levels:
Global default, in sim/base.yaml:
Per-class, in the persona pipeline:
classes:
voter:
count: 100
model: gpt-4o-mini # Cheaper model for voters
candidate:
count: 2
model: gpt-4o # Better model for key agents
model may also be a full LLM block that overrides sim.llm per-field (unset
fields fall back to global); extra_kwargs replaces rather than merges:
classes:
candidate:
count: 2
model:
name: gpt-4o
temperature: 0.2
provider: openai
# api_base, api_key, extra_kwargs, disabled also accepted
Per-agent, via field mapping:
classes:
user:
count: 50
data:
source: local_json
path: agents.json # Must have a "model" field per record
field_map:
name: name
context: persona
model: model_name # Maps to per-agent model assignment
Priority: per-agent field_map > per-class config > global default. This priority
applies to the model name; for a per-class block, each other field overrides
the matching global sim.llm field and falls back to global when unset.
Social Graph And Activity¶
Graph fields feed the GM initialize component. Activity rates feed the GM
next_acting slot.
gm:
components:
initialize:
params:
graph:
network_type: barabasi_albert
barabasi_albert_m: 10
base_followership_probability: 0.3
fully_connected_targets:
- news_account
Activity selection is sim-level participation config (conf/sim.yaml):
engine:
participation:
built_in: activity_probability
params:
activity_transition_rates:
user:
inactive_to_active: 0.3
active_to_inactive: 0.3
The activity model uses a two-state Markov process: each step, an agent transitions between active and inactive states. Only active agents take actions.
Fixed-Action Agents¶
Use silisocs.agents.fixed.FixedAgent when you want deterministic,
episode-aware behavior without LLM action generation.
Minimal pattern:
persona_pipeline:
classes:
broadcaster:
count: 1
class_path: silisocs.agents.fixed.FixedAgent
sim_role_name: broadcaster
data:
source: inline
records:
- context: Official broadcaster account
name: Town Bulletin
field_map:
name: name
context: context
params:
flow_tag: fixed_pre
fixed_action_plan:
0:
- action_type: POST
target_id: ""
content: "Daily bulletin from {name}: please stay informed."
reasoning: "Scheduled bulletin at simulation start."
5:
- action_type: POST
target_id: ""
content: "Emergency update from {name}: check local advisories."
reasoning: "Scheduled follow-up bulletin."
sim:
engine:
step:
built_in: flow
params:
flow_order: [fixed_pre, default]
gm:
components:
observe:
built_in: timeline_every_turn
params:
episode_observation_flows: [fixed_pre]
Behavior notes:
- Fixed agents parse episode index from observation text (for example
EPISODE: 12). - If no episode number is parseable, the fixed agent increments its internal counter by 1.
- The action text emitted by fixed agents is compatible with existing resolve components.
fixed_action_planis strict dict-only (episode -> list[action]), not list-based.- You can load the same structure from a file using
params.fixed_action_plan_file(.json/.yaml/.yml).
Compatibility notes:
- Fixed-action items use backend action names (or selectable aliases).
env.gm.backend.enabled_actionsandenv.gm.backend.excluded_actionsapply globally and can restrict fixed-action items.
Memory Initialization¶
Before the simulation loop starts, the Engine runs agent initialization and populates each agent's memory:
- Shared memories (from config) are broadcast to all agents
- Generated memories (from
generate_memories()) are per-agent - Specific memories (per-agent, from config) are injected last
Two built-in modes:
| Mode | Config | Behavior |
|---|---|---|
| Raw | sim.initialization.agents.built_in: raw_memory |
No LLM calls, only config memories |
| Formative | sim.initialization.agents.built_in: formative_memory |
LLM-generated multi-episode backstories |
See Memory Initialization for custom initializers.
Evaluation Probes¶
Probes are periodic surveys deployed to agents during the simulation:
probes:
deployment:
enabled: true
start_step: 1
every_n_steps: 5
probes:
satisfaction:
probe_name: satisfaction
probe_type: NumericRatingProbe
probe_data:
name: Satisfaction
question: "On a scale of {lo} to {hi}, how satisfied are you?"
lo: 1
hi: 10
Built-in probe types: NumericRatingProbe, BinaryProbe, ChoiceProbe,
FreeTextProbe.
See Evaluation Probes for details.
Output¶
Each simulation run produces output under the Hydra-managed directory:
Output Files¶
| File | Format | Description |
|---|---|---|
run_manifest.json |
JSON | Self-describing run index: status, run params, GM/backend layout, health counters, LLM usage, artifact paths (including per-GM event logs), and provenance (git commit, package version, uv.lock hash). Load a run through this instead of guessing the file layout |
action_events.jsonl |
JSONL | Every backend action that COMMITTED a state change (or performed a deliberate logged read), with episode index, Game Master name, backend type, source user, and action data. Rejected, failed, and idempotent calls are not recorded |
exposure_events.jsonl |
JSONL | What each agent SAW per turn: post ids + per-post source (follower/recsys:<type>). On by default; disable via env.gm.components.observe.params.log_exposures: false |
probe_events.jsonl |
JSONL | Probe/survey responses per agent per deployment step |
prompts_and_responses.jsonl |
JSONL | Every LLM call: prompt, response, episode index, and agent name |
run_stats.log |
Text | Per-episode timing, worker counts, retry telemetry, and startup phase durations |
sim_metrics.json |
JSON | Structured metrics summary: system info, per-episode durations, worker limits, resource snapshots (CPU/memory), and aggregate statistics |
<platform>.db |
SQLite | Full social media state (users, posts, replies, likes, follows). Use with the built-in visualizers to browse |
.hydra/config.yaml |
YAML | Fully resolved Hydra config snapshot |
.hydra/overrides.yaml |
YAML | CLI overrides used for this run |
effective_config.yaml (and its configs/ snapshot) is written with every
api_key masked as **redacted**, so a run directory stays shareable even when a
key was set in config rather than the environment.
Run Health¶
A run records what it survived. Each counter below is incremented during the
run, printed as a ⚠ DEGRADED RUN line at the end, written to
run_manifest.json under health, and rendered by Studio's Run health panel
(and RunArtifact.health). Anything a run cannot legitimately continue past
raises and fails the run instead of being counted.
| Counter | Meaning |
|---|---|
agent_turn_failures |
Agent turns that raised an exception (isolated: the step continued) |
action_parse_failures |
Agent actions dropped as unparseable |
action_invalid_targets |
Agent actions that referenced invalid target ids |
backend_action_errors |
Backend actions that raised unexpectedly |
harness_tool_failures |
Harness tool calls that failed inside a harness turn |
routing_fallbacks |
Branch routing calls that fell back to a default choice |
silent_backends |
Backends that committed no action events (names in the manifest) |
Zero on every counter is the only clean run; a non-zero count means results are
partial in a specific, named way. The counter set lives in one registry
(silisocs.evaluations.vocabulary.HEALTH_COUNTERS), so a new counter reaches the
warning, the manifest, and the artifact loader together.
Action Events Format¶
Each line in action_events.jsonl is a JSON object:
{
"episode": 3,
"event_type": "action",
"event_index": 42,
"gm_name": "social_gm",
"backend_type": "twitter_like",
"source_user": "Alice Smith",
"label": "post",
"data": {
"content": "Beautiful morning in the neighborhood!",
"post_id": 127
}
}
Probe Events Format¶
Each line in probe_events.jsonl:
{
"episode": 5,
"event_type": "probe",
"event_index": 0,
"data": {
"agent": "Alice Smith",
"probe_type": "NumericRatingProbe",
"question": "Rate your satisfaction 1-10",
"raw_response": "I'd say about a 7",
"probe_return": "7"
}
}
Simulation Metrics¶
sim_metrics.json provides structured data for analysis:
{
"metadata": {
"num_agents": 10,
"num_steps": 5,
"world": "election",
"llm": {"name": "gpt-4o"},
"agent_names": ["Alice Smith", "Bob Jones", "..."]
},
"total_duration_s": 1234.5,
"episodes": [
{
"episode": 1,
"duration_s": 6.2,
"active_agents": 145,
"worker_limit": 200,
"retry_count": 3
}
],
"resources": {
"start": {"cpu_percent": 12.5, "memory_mb": 1024},
"end": {"cpu_percent": 45.2, "memory_mb": 3072}
}
}
Visualizing Output¶
Open a run's backend state in the unified Studio workspace:
Select Runs, open a run, then select Platform. Studio discovers viewer capabilities declared by each backend and starts the matching read-only view; custom backends can participate without Studio-specific code.
See Environment Backends for full details.
Loading Runs Programmatically¶
Tools and notebooks should load runs through the Run Artifact Module instead of re-discovering the file layout:
from silisocs.evaluations.run_artifact import load_run, load_study
run = load_run("outputs/my_world/run_dir")
run.status, run.scenario, run.seed # from run_manifest.json, which the
run.health, run.llm_usage, run.provenance # loader requires (see Run Health)
for event in run.iter_actions(): # streams flat or per-GM event logs
...
study = load_study("experiments/studies/my_study")
study.plan, study.summary, study.provenance
Studio loads runs through this same interface, so a run that loads here renders in the visual analysis views.
End-to-End Workflow¶
Here is the complete workflow for creating and running a custom world:
1. Create the Scenario Directory¶
2. Write the Scenario Configs¶
# scenarios/my_world/conf/world/default.yaml
# @package _global_
scenario_name: my_world
num_agents: 2
num_steps: 5
setting:
name: My Community
background:
- A small online community focused on technology discussions.
event:
name: Product launch
context: |
A new product has been announced and community members
are discussing its merits and drawbacks.
# scenarios/my_world/conf/agents/default.yaml
# @package agents
persona_pipeline:
defaults:
params:
world_context: ${event.context}
shared_memories:
- They are active on a tech discussion forum.
- ${event.context}
classes:
user:
count: ${num_agents}
class_path: silisocs.agents.native.NativeAgent
sim_role_name: user
data:
source: inline
records:
- name: Alex
persona: Alex follows product launches and asks practical questions.
- name: Blair
persona: Blair studies developer tools and compares alternatives.
field_map:
name: name
context: persona
shared_memories:
- They are active on a tech discussion forum.
- ${event.context}
initial_observations:
- "{name} is browsing the forum."
- "{name} sees the latest announcement about the product launch."
# scenarios/my_world/conf/env.yaml
gm:
components:
initialize:
params:
graph:
network_type: barabasi_albert
barabasi_albert_m: 10
base_followership_probability: 0.3
Activity selection is sim-level participation config (conf/sim.yaml):
engine:
participation:
built_in: activity_probability
params:
activity_transition_rates:
user:
inactive_to_active: 0.3
active_to_inactive: 0.3
3. Run It¶
4. Analyze Output¶
Output appears in outputs/my_world/.
5. (Optional) Add a Custom Builder¶
If you need programmatic control over agent construction:
# scenarios/my_world/builders.py
from silisocs.runtime.construction.agent_builders import AgentBuilder
class MyScenarioAgentBuilder(AgentBuilder):
def build_agent_configs(self):
# Custom logic here
...
6. (Optional) Use Studio¶
Create the world visually, preflight it, launch, watch artifacts grow, and inspect the completed run without changing tools.
To replay simulation state from a previous run, use sim.checkpoint.source_run
in the launch overrides.
7. Replay from a Checkpoint¶
Enable checkpointing during a run:
uv run silisocs \
--config-path scenarios/my_world/conf \
num_steps=200 \
sim.checkpoint.every_n_steps=10
Then restore from the previous output directory:
uv run silisocs \
--config-path scenarios/my_world/conf \
num_steps=200 \
sim.checkpoint.source_run=outputs/my_world/run1 \
sim.checkpoint.restore.built_in=social_action_event_replay
Current checkpoints store default agent state, game-master component state, and
the state of built-in local backends. For SQLite-backed backends such as
twitter_like and reddit_like, the checkpoint stores a database snapshot. The
restore strategy is only used when checkpointed backend state is absent and a
domain-specific replay is needed.
For multi-GM source runs, replay is metadata-driven and strict. The runtime uses the restored Agent flow and the configured flow chain, then requires exactly one GM in that chain to expose the replayed backend action.
Developer Customization Guide¶
This section maps key runtime tasks to concrete extension points for developer users.
Engine Responsibilities¶
The runtime now includes direct engine step strategies:
sim.engine.step.built_in: base(default): simple execution path, one GM active per episode.sim.engine.step.built_in: sequential: one GM, selected agents executed one by one.sim.engine.step.built_in: flow: flow-aware execution.sim.engine.step.built_in: multi_gm/multi_gm_serial/multi_gm_staged: flow-aware execution with multi-GM routing; the three variants select the flow-chain traversal mode (see below).
For the multi_gm* strategies, all configured Game Masters update once at the
start of each episode step, before flow routing and actor selection. Flow chains
then route selected agent turns through the configured GMs; gm.update(...) is
not called again inside each flow chain.
Chain traversal is selected by sim.engine.step.built_in (the former
sim.engine.step.params.chain_execution knob has been removed; a config that
still sets it raises a ValueError with a migration hint):
multi_gm(default, concurrent): flows run as independent pipelines through their GM chains. Distinct flows advance concurrently and a flow's next hop starts as soon as its own previous hop completes — turns serialize only when two flows touch the same GM at the same time (the engine's existing per-GM lock). Flows listed inflow_orderrun first as a strict serial prefix, preserving declared precedence such as seed-then-act (fixed_prebeforedefault); every other flow runs as the concurrent group. A single agent's own chain hops always stay serial, since each hop observes the prior hop's resolution. This is the unchanged default behavior.multi_gm_serial: legacy row-major — each flow runs its full GM chain to completion before the next flow, one batch at a time, in a deterministic flow-by-flow order.multi_gm_staged: column-major with a global per-stage barrier —flow_orderflows run first as a serial prefix, then every remaining flow advances one stage at a time, with all flows' stage-N hops running concurrently and stage N+1 blocked until all of stage N finishes. Anullchain entry (empty slot) idles a flow at that stage so differently-shaped chains stay stage-aligned.
Checkpoint replay is per-agent-flow-chain regardless of the traversal mode.
The engine is responsible for:
- Episode loop orchestration (
run_loop) - Probe scheduling and deployment timing
- Selecting acting agents and action specs for each episode
- Running agent actions concurrently and resolving them through the GM (under
the default
multi_gmstrategy, this also covers cross-flow/cross-chain concurrency, gated by shared-GM overlap) - Worker throttling based on retry telemetry
Key implementation: src/silisocs/simulation_engines/base_engines.py.
Current Action Semantics¶
Action semantics are policy-driven through sim.engine.turn_policy.
single_action: one resolved action per acting agent per episode.fixed_count: a fixed number of resolved actions per acting agent.open_ended: continue until stop token or max action budget.
Built-in turn policies also accept observe_before_act: first | always | never.
The default first preserves existing behavior by observing before the first
action in a repeated-action turn.
Turn scheduling is orthogonal to the policy: sim.engine.executor selects
threads (default) or asyncio (turns run as coroutines on one event loop —
thousands of LLM calls in flight on a few threads). Behavior is identical across
executors; see the configuration reference.
Probe timing is policy-driven through eval.probes.schedule.
Defining New Agent Behavior Flows¶
To introduce new class-level behavior phases without engine/resolve bloat:
- Set
flow_tagper class inpersona_pipeline.classes. - Define phase order in
sim.engine.step.params.flow_order. - Optionally add per-agent overrides with
sim.engine.step.params.agent_to_flow. - Add any flow names that require episode-style observations to
env.gm.components.observe.params.episode_observation_flows.
agent_to_flow keys must match final Agent names. Overrides are applied during
runtime construction and passed to the Engine and every Game Master as the
final flow map.
This pattern is how fixed agents run before default LLM-driven agents today, and it generalizes to any future specialized class.
Game Master Responsibilities¶
The environment GM (GameMaster + native components) is responsible for:
- Initializing the active backend app through its initialize component
- Building generic or timeline observations for each acting agent
- Parsing and dispatching actions (
custom,generic) and tool calls (none,single,multi) - Applying action effects through the backend app contract
- Managing activity-state-based actor gating
Key implementations:
src/silisocs/environments/gm/game_master.pysrc/silisocs/environments/gm/components/
What Developers Commonly Customize¶
Engine-side tasks¶
- Multi-action-per-episode turn policy (
sim.engine.turn_policy) - Alternative actor scheduling policies
- Probe timing policy (
eval.probes.schedule) - Concurrency and retry throttling strategy
GM-side tasks¶
- Action grammar and parsing through resolve components
- Action dispatch strategy by resolve mode (
parsed_action,generic_action,tool_calling) - Observation shaping (
app_observation,timeline_every_turn, or custom component) - Activity transition behavior through next-acting components
- Seed posts through simulation initialization, then normal
resolve_action
Backend Contract Tasks¶
For platform extensions, backend classes implement BackendApp and are
selected by env.gm.backend.type or env.gm.backend.class_path through the backend factory.
Typical developer tasks include adding new @app_action methods, observations,
optional timeline semantics, and storage/query behavior.
Further Reading¶
- Configuration Reference: Every config option explained
- Building Agents: YAML pipeline and custom builders
- Memory Initialization: Raw, formative, and custom modes
- Environment Backends: Generic apps, Twitter-like, Reddit-like, Mastodon
- Evaluation Probes: Probe types and deployment
- Studio: unified visual workflow and extension guide
- Election Walkthrough: Complex real-world example