跳转至

架构分层

基础设施的三层

两个循环 —— 一次运行内部的纵向模拟循环、跨版本的可控研究循环 —— 都跑在 同一套基础设施上,这套基础设施分三层。每层占有不同的文件;判断一个问题 属于哪一层,通常是最快的定位代码的办法。

层 做什么 在哪里
工作流层 sv-* 这组 skill。每个 skill 读前面阶段的产物,只写一份属于自己的、经校验的产物,然后停在一个检查点等你决定 .claude/skills/sv-*/SKILL.md;共享辅助库在 skills/sv_workspace.py(study 目录、版本、分支)与 skills/sv_grounding.py
服务层 在构建期把 P 与 E 的外生部分解析成真实数据,以类型化 MCP 工具的形式提供 —— 人群池服务(sv-user-pool)与事件信号服务(sv-event-tool) 服务端部署,经 MCP 访问;结果落盘进 population/ 与 environment/,运行时本身不发起任何外部请求。见数据服务
治理层 记录每个阶段做了什么、用了哪些数据、研究者批准了什么 grounding/grounding.json、resources.json、versions.json,以及 trajectory/study.duckdb 里的 events 表

本页讲的是这三层站立其上的东西:运行时。

Core 运行时与 study 层

运行时分为两层:你复用的 Core 运行时,和你编写的 study 层。 Core 定义契约并驱动循环;study 提供实现。新增研究永远不需要改引擎。

socioverse/                CORE 运行时 —— 复用,不要动
├── schemas/               类型化契约(StudySpec、EnvironmentBundle、PopulationBundle、
│                          SimulationConfig、Observation、Action、TrajectoryRecord……)
├── abc/                   研究要实现的接口(EnvironmentProvider、
│                          PopulationProvider、DecisionModel、MetricCollector)
│                          + Simulator / TrajectoryStore / Reporter / MessageBus
├── engine/                LongitudinalSimulator(主循环)、AgentMemory、registry、
│                          InMemoryMessageBus、build_simulator(装配)
├── env_layers/            信息轴助手(广播投递、邻居信息流)
├── io/duckdb_store.py     可查询的 panel / metrics / events / messages 存储
├── providers.py           通用 File / MCP 人群 provider
└── validation.py          validate_handoff —— 严格的阶段边界校验

studies/<id>/              STUDY 层 —— 你写(或由工作流生成)
└── model.py + 产物         四个接口实现 + study.yaml / env / pop / sim

四个接口

实现 socioverse/abc/ 里的四个抽象基类,一个研究就完整了:

接口 职责 关键方法
EnvironmentProvider 持有世界状态 E reset、advance_to(t)(外生)、observe_batch()(四象限视图)、apply(actions)(内生)
PopulationProvider 构建固定人群 P build() → 带确定性持久 id 的 Persona[] + 交互结构
DecisionModel 计算 B = f(P, E) decide_batch(observations) → Action[] —— 整步输入、类型化
MetricCollector 度量每一步 collect() → 该步的聚合指标

其余由 Core 提供:LongitudinalSimulator 循环、轨迹存储、报告器、 消息总线、agent 记忆。

运行时动线

LongitudinalSimulator.run()(socioverse/engine/loop.py)先建一次 P、重置 E, 随后每一步都跑同样的五个阶段,顺序固定:

build()  → Persona[]                     # P:只建一次,持久 id,此后固定
reset()                                  # E_0 —— 作为第 0 步的面板行落盘
loop t = 1..N:
    ① 外生更新  advance_to(t)                    # 定时事件 + 广播改变 E
    ② 观察      observe_batch() → Observation[]  # 每个 agent 的四象限视图
    ③ 决策      decide_batch()  → Action[]       # = B_t = f(P, E_t, memory)
    ④ 应用      apply(actions)                   # 内生反馈:E_{t+1} = g(E_t, B_t)
    ⑤ 记录      collect() + store.record()       # 面板行 + 指标 + 本步触发的事件
阶段 调用 为什么排在这里
① 外生更新 env.advance_to(t) → 触发的事件 干预在任何人观察之前进入环境,所以同一轮里所有 agent 面对的是同一个状态
② 观察 env.observe_batch(ids, t, round) E 抵达 f 的唯一通道 —— 宏观/局部 × 物理/信息
③ 决策 decision.decide_batch(obs, memories) 整步输入、类型化输出:每个 agent 一条 Action,LLM 或规则都走这里
④ 应用 env.apply(actions) 在从零构建的研究里它是传入 actions 的纯函数,这正是分支重放能精确复现父版本的前提
⑤ 记录 collector.collect()、store.record(...) 先推记忆窗口,再写每个 agent 一行面板、一行指标,以及 ① 中触发的事件

同样五个阶段的两种变体:

  • interaction_rounds = K > 1 时,②–④ 在一步之内重复 K 次;只有最后一轮的 action 进入记忆窗口和面板行。见步内多轮交互。
  • 分支用父版本已存的 action 重放 ①、④、⑤(_replay_inherited), 到分叉步才开始真正调用决策模型。见干预与分支。

这些调用之间的交接是类型化的 (Persona[] → Observation[] → Action[] → metrics),所有类型都定义在 socioverse/schemas/。Schemas 是所有组件共同引用的契约层—— 不是数据「流经」的一个阶段。

Core 内置的场景钩子

三类常见需求已在 Core 解决,研究只需声明、无需实现:

  • 按受众投放信息 —— Broadcast / InformationProgram (env_layers/information.py):audience: "all" 抵达每个 agent 的 宏观信息象限;选择器 dict 命中匹配 agent 的局部信息象限。
  • 大规模 agent 交互 —— 中介化的 MessageBus 加 interaction_rounds (不是 O(N²) 的两两聊天):发言落到总线上,邻居通过研究声明的 InteractionStructure 读取(engine/messaging.py、 env_layers/neighbor_feed.py)。
  • 自带数据 —— ResourceManifest 加通用的 FilePopulationProvider / McpPopulationProvider (socioverse/providers.py),人群可来自文件或外部服务。

一个值得内化的命名区分

  • Core 引擎是被复用的 LongitudinalSimulator——主循环本身。
  • engine-seam(引擎缝合层)是研究内部的胶水,用来包装遗留 模拟器(见包装既有模拟器)。 Core 永远不调用它;只有研究自己的 provider 引用它。

注册与装配

研究用 @register(kind, key) 装饰器(socioverse.engine.registry) 发布实现,产物里按 key 引用它们(provider_ref、decision_ref、 collector_ref……)。此后装配是通用的:

build_simulator(env_bundle=…, pop_bundle=…, sim_config=…, store_path=…)

它把每个 *_ref 在 registry 中解析、实例化 provider、接好 DuckDB 存储, 返回就绪的 LongitudinalSimulator。这就是全部集成面——不改引擎, 也没有要学的插件系统。