Storage and Result Access¶
New AgentSociety 2 experiments write results to two locations:
Replay directory:
<run_dir>/replay/.ReplaySink/ReplayWriterwrite replay data as append-only JSONL and store the dataset catalog and column metadata in_schema.json.Local working directory: files such as
<run_dir>/artifacts/,<run_dir>/agents/agent_<id>/, and<run_dir>/pid.json, which storeask/interveneartifacts, agent workspaces, and runtime metadata.Workspace checkpoint (for resume): each env module writes dynamic state to
<run_dir>/env/<module>/state/ENV_STATE.json. Society writes the immutable initialization snapshot to<run_dir>/SOCIETY.jsonand per-step scalars to<run_dir>/SOCIETY_STEP.json. These files are atomic JSON writes used only by--resumeand are not added to the replay catalog. Replay is append-only time-series data for analysis; workspace persistence is for recovery and overwrites the latest state.
There are three ways to read results:
Backend REST API
/api/v1/replay/...(recommended, together with the frontend or the VSCode replay webview)agentsociety2.storage.ReplayReader, which reads sharded JSONL with DuckDB.Read
*.json/*.jsonlfiles in the workspace directly for agent behavior analysis and debugging.
Note
ReplayWriter no longer writes the three legacy framework tables agent_profile, agent_status, and agent_dialog for new experiments. They remain in agentsociety2.storage.models only for ORM compatibility when reading historical databases; equivalent data in new experiments is stored as catalog-driven datasets.
Run Directory Layout¶
The CLI entry point python -m agentsociety2.society.cli uses the directory passed with --run-dir as the experiment HOME. ExperimentRunner creates the following structure inside it:
<run_dir>/
├── replay/
│ ├── _schema.json # dataset catalog + table/column metadata
│ ├── core_agent_profile.<shard>.jsonl
│ ├── <prefix>_agent_state.<shard>.jsonl
│ └── <prefix>_env_state.<shard>.jsonl
├── pid.json # 进程信息与终止状态
├── artifacts/ # ask / intervene / questionnaire 产物
└── agents/
└── agent_0001/
├── config.json # 静态配置(create 时写一次)
├── AGENT.json # 动态自描述快照(每步 to_workspace 更新)
├── AGENT_MEMORY.md # 运行时会话摘要
├── memory/episodes.jsonl # event-level memories
├── state/
└── .runtime/logs/
├── thread_messages.jsonl
├── tool_calls.jsonl
└── step_replay.jsonl
When experiment data is read through the backend, run_dir is assembled from workspace_path + hypothesis_<id>/experiment_<id>/run. The backend reads replay/_schema.json and the JSONL shards from that directory.
Replay Write Format¶
ReplaySink is a per-process append-only JSONL writer. ReplayProxy only carries replay_dir and the enabled flag, so it can be serialized across Ray task / actor boundaries; each consumer lazily loads its own ReplaySink inside the current process, without going through a central SQLite actor.
The top-level structure of <run_dir>/replay/_schema.json is:
{
"tables": {
"core_agent_profile": {
"columns": [{"name": "id", "type": "INTEGER"}],
"primary_key": ["id"],
"indexes": [["name"]]
}
},
"datasets": {
"core.agent_profile": {
"dataset_id": "core.agent_profile",
"table_name": "core_agent_profile",
"module_name": "AgentSociety",
"kind": "entity_static",
"entity_key": "id",
"step_key": null,
"time_key": null,
"default_order": ["id"],
"capabilities": ["agent_profile", "entity_static"],
"version": 1,
"columns": [{"name": "id", "type": "INTEGER"}]
}
}
}
Each physical table is written as <table_name>.<shard>.jsonl. The shard is computed from the row content CRC32, so natural ordering across shards is not stable; readers should sort explicitly with the dataset default_order, step_key, or time_key.
Common capabilities:
agent_profile- static agent profiles.agent_snapshot- per-step agent state.env_snapshot- per-step environment state.timeseries- suitable for timeline expansion.geo_point- containslng/latand can be drawn on a map.trajectory- suitable for trajectory replay.entity_static- one-time static entity table.event_stream— event stream
Writing Data¶
Environment-module state data can be written through two paths: declarative (recommended) and manual registration for event streams and irregular shapes. Both paths call the same replay writer; injection is handled by AgentSociety / the environment router.
Method A: declarative _agent_state_columns / _env_state_columns¶
When subclassing EnvBase, declare the columns you want to write to replay on every step as class variables. Before the first write, EnvBase automatically:
Derives the table-name prefix (PascalCase to snake_case, removing
Space/Env/Modulesuffixes).Registers the schema for
{prefix}_agent_stateand / or{prefix}_env_state.Call
register_datasetto write catalog metadata. The default capabilities are["agent_snapshot", "timeseries"]and["env_snapshot", "timeseries"]. If_agent_state_columnsalso declares bothlngandlat,geo_pointandtrajectoryare added automatically.
Inside step(), the module only needs to call EnvBase._write_agent_state(), EnvBase._write_agent_state_batch(), or EnvBase._write_env_state().
from typing import ClassVar
from agentsociety2.env import EnvBase
from agentsociety2.storage import ColumnDef
class EconomySpace(EnvBase):
_agent_state_columns: ClassVar[list[ColumnDef]] = [
ColumnDef("currency", "REAL", analysis_role="measure"),
ColumnDef("income", "REAL", analysis_role="measure"),
ColumnDef("consumption", "REAL", analysis_role="measure"),
]
_env_state_columns: ClassVar[list[ColumnDef]] = [
ColumnDef("bank_interest_rate", "REAL", analysis_role="measure"),
]
async def step(self, tick: int, t):
records = [
{"agent_id": p.id, "currency": p.currency,
"income": p.income, "consumption": p.consumption}
for p in self._persons.values()
]
await self._write_agent_state_batch(self._step, t, records)
await self._write_env_state(self._step, t,
bank_interest_rate=self._bank_interest_rate)
self._step += 1
Method B: manual register_table + register_dataset¶
Event streams with kind="event_stream" and data with irregular shapes can still be registered manually:
from agentsociety2.storage import ColumnDef, ReplayDatasetSpec, TableSchema
schema = TableSchema(
name="social_media_event",
columns=[
ColumnDef("id", "INTEGER", nullable=False),
ColumnDef("step", "INTEGER", nullable=False, logical_type="step"),
ColumnDef("t", "TIMESTAMP", nullable=False, logical_type="timestamp"),
ColumnDef("sender_id", "INTEGER", logical_type="identifier"),
ColumnDef("event_type", "TEXT", logical_type="enum",
enum_values=["post", "follow", "like", "comment", "repost"]),
ColumnDef("payload", "JSON"),
],
primary_key=["id"],
indexes=[["step"], ["sender_id"], ["event_type"]],
)
await self._replay_writer.register_table(schema)
await self._replay_writer.register_dataset(
ReplayDatasetSpec(
dataset_id="social_media.event",
table_name="social_media_event",
module_name=self.name,
kind="event_stream",
title="Social Media Event Stream",
entity_key="sender_id",
step_key="step",
time_key="t",
default_order=["step", "id"],
capabilities=["event_stream", "social_event"],
),
schema.columns,
)
await self._replay_writer.write("social_media_event", {...})
await self._replay_writer.write_batch("social_media_event", [{...}, {...}])
Reading Data¶
Method 1: backend REST API (recommended)¶
After starting python -m agentsociety2.backend.run, the replay API is mounted at /api/v1/replay. Every request carries a workspace_path query parameter, and the backend uses it to locate <workspace_path>/hypothesis_<id>/experiment_<id>/run/replay.
Path |
Role |
|---|---|
|
Experiment summary: |
|
Return all datasets in the current experiment together with column metadata |
|
Details for a single dataset |
|
Paginated row query. |
|
Dataset grouping required by frontend panels / map layouts. |
|
Merged snapshot for one step: all agent states, environment state, and geographic positions |
|
|
|
List of agent profiles, read from |
The full prefix is /api/v1/replay/{hypothesis_id}/{experiment_id}/....
/datasets/{dataset_id}/rows supports page, page_size, order_by, desc_order, step, entity_id, start_step, end_step, max_step, columns, and latest_per_entity.
Method 2: ReplayReader + DuckDB¶
ReplayReader reads _schema.json and registers each dataset’s JSONL shards as DuckDB views. It preserves the metadata-driven query semantics used by the backend:
from agentsociety2.storage import ReplayReader
reader = ReplayReader("hypothesis_1/experiment_1/run/replay")
datasets = reader.load_dataset_catalog()
state = reader.get_dataset_by_id("economy.agent_state")
rows = reader.query_dataset_rows(
state,
page=1,
page_size=200,
step=42,
columns=["agent_id", "step", "currency"],
)
reader.close()
Return-value convention: columns marked as JSON in the catalog are automatically deserialized into objects, and datetime columns are returned as ISO-8601 strings.
Method 3: read workspace files directly¶
<run_dir>/artifacts/ stores Markdown artifacts written by the CLI for ask, intervene, and questionnaire steps, and can be shown to researchers directly.
Common files under <run_dir>/agents/agent_<id>/:
agent_config.json— agent capabilities, initial state, skill visibility overrides, and activated skillsAGENT.json- dynamic self-description snapshot, updated byto_workspaceon each step.AGENT_MEMORY.md- runtime session summary.memory/episodes.jsonl— event-level memories。state/*.json- built-in state and user-defined state files..runtime/logs/step_replay.jsonl- tool history for each step..runtime/logs/tool_calls.jsonl- tool-call log..runtime/logs/thread_messages.jsonl- recent thread messages.
These files are useful for debugging agent behavior, reconstructing thread context, and inspecting skill execution. They are not written into the SQLite database.
Compatibility with Legacy Databases¶
Legacy databases from v1 or early v2 may still contain the three framework tables agent_profile, agent_status, and agent_dialog. The new backend replay API targets the run/replay directory. To read historical sqlite.db files, use the compatibility models retained in agentsociety2.storage.models or a legacy analysis script.