MiroFish · 专题 02
← MiroFish 项目分析 专题 02 / 03
Technical deep-dive · 02

多 Agent 模拟运行时机制

运行时并非只有“多个提示词并发调用”:它会把图谱实体转成人设,为每个 Agent 生成活动和立场配置,启动独立子进程,在 Twitter/Reddit 两套 OASIS 环境和 SQLite 数据库中逐轮执行模型行动,并把落地动作增量写入日志。问题在于运行结果没有随机种子、两个平台只是并行的独立世界、界面展示的推荐/传播参数没有被执行脚本读取,以及子进程在轮次结束后默认等待访谈命令,导致报告阶段的终态衔接存在静态可见的断点。

版本 0.1.0 证据基线 117ed3775 分析日期 2026-08-24
Key finding
问题在于运行结果没有随机种子、两个平台只是并行的独立世界、界面展示的推荐/传播参数没有被执行脚本读取,以及子进程在轮次结束后默认等待访谈命令,导致报告阶段的终态衔接存在静态可见的断点。

专题结论

运行时并非只有“多个提示词并发调用”:它会把图谱实体转成人设,为每个 Agent 生成活动和立场配置,启动独立子进程,在 Twitter/Reddit 两套 OASIS 环境和 SQLite 数据库中逐轮执行模型行动,并把落地动作增量写入日志。问题在于运行结果没有随机种子、两个平台只是并行的独立世界、界面展示的推荐/传播参数没有被执行脚本读取,以及子进程在轮次结束后默认等待访谈命令,导致报告阶段的终态衔接存在静态可见的断点。

原始类与方法

原始符号 真实职责 源码定义 固定提交行级证据
SimulationManager.prepare_simulation()(串联实体读取、人设生成和配置生成的准备方法,约第 244 行) 把已完成图谱转成可运行模拟目录 模拟管理文件 固定版本第 244-478 行
OasisProfileGenerator.generate_profiles_from_entities()(并行把图谱实体转成人设文件的方法,约第 895 行) 为 Twitter/Reddit 生成账号、性格、职业和背景字段 人设生成文件 固定版本第 895-1020 行
PlatformConfig(声明平台推荐、传播和回音室字段的数据类,约第 131 行) 保存第二步界面展示的平台参数 配置生成文件 固定版本第 131-145 行
SimulationConfigGenerator.generate_config()(生成时间、事件、Agent 和平台配置的方法,约第 244 行) 汇总运行输入并保存固定的平台参数 配置生成文件 固定版本第 244-375 行
SimulationRunner.start_simulation()(声明运行态并启动平台子进程的方法,约第 371 行) 读取配置、计算轮数、建立终态锁并启动脚本 模拟运行文件 固定版本第 371-617 行
get_active_agents_for_round()(按时段、活动水平和随机抽样选择 Agent 的函数,约第 1040 行) 决定每轮哪些 Agent 执行动作 双平台脚本 固定版本第 1040-1090 行
run_twitter_simulation()(创建并运行 Twitter OASIS 环境的异步函数,约第 1101 行) 管理平台数据库、初始帖子、轮次和行动日志 双平台脚本 固定版本第 1101-1292 行
run_reddit_simulation()(创建并运行 Reddit OASIS 环境的异步函数,约第 1293 行) 管理另一套独立平台数据库、轮次和行动日志 双平台脚本 固定版本第 1293-1490 行
SimulationRunner._monitor_simulation()(增量读取行动并在进程退出后发布终态的方法,约第 620 行) 聚合平台进度、提交图谱记忆并排空写入器 模拟运行文件 固定版本第 620-765 行
ZepGraphMemoryUpdater.add_activity()(把成功行动加入异步图谱写入队列的方法,约第 350 行) 将模拟行为转换为可写入 Zep 的自然语言图谱事件 图谱记忆写入文件 固定版本第 350-409 行
doStartSimulation()(页面挂载后以强制重启和图谱写回参数启动模拟的前端函数,约第 383 行) 标准网页入口固定选择双平台并开启记忆写回 第三步组件 固定版本第 383-438 行

原始调用链

  1. SimulationManager.prepare_simulation()(模拟准备总入口,约第 244 行)从图谱读取实体后调用 OasisProfileGenerator.generate_profiles_from_entities()(批量生成人设的方法,约第 895 行)。
  2. 同一准备入口随后调用 SimulationConfigGenerator.generate_config()(生成时间、事件和 Agent 配置的方法,约第 244 行)并把结果保存为运行配置。
  3. doStartSimulation()(标准网页的启动函数,约第 383 行)调用后端启动接口,最终进入 SimulationRunner.start_simulation()(创建状态并启动子进程的方法,约第 371 行)。
  4. 双平台脚本并发运行 run_twitter_simulation()(Twitter 轮次循环,约第 1101 行)和 run_reddit_simulation()(Reddit 轮次循环,约第 1293 行);两者每轮都调用 get_active_agents_for_round()(随机选择活跃 Agent 的函数,约第 1040 行)。
  5. SimulationRunner._monitor_simulation()(增量监控与终态发布方法,约第 620 行)读取两套行动日志,并在标准前端开启记忆时将动作交给 ZepGraphMemoryUpdater.add_activity()(排队写入模拟活动的方法,约第 350 行)。

源码片段一:平台参数被生成并保存

原项目关键片段。来源:SimulationConfigGenerator.generate_config()(构造固定 Twitter/Reddit 平台参数的方法,约第 339-372 行);固定提交第 339-372 行

原项目关键片段:

python
twitter_config = None
reddit_config = None

if enable_twitter:
    twitter_config = PlatformConfig(
        platform="twitter",
        recency_weight=0.4,
        popularity_weight=0.3,
        relevance_weight=0.3,
        viral_threshold=10,
        echo_chamber_strength=0.5
    )

if enable_reddit:
    reddit_config = PlatformConfig(
        platform="reddit",
        recency_weight=0.3,
        popularity_weight=0.4,
        relevance_weight=0.3,
        viral_threshold=15,
        echo_chamber_strength=0.6
    )

这些参数不是模型推导结果,而是源码常量。对仓库中的字段使用点做静态检索后,它们只出现在配置模型、配置生成、配置摘要和第二步界面展示;两个平台执行函数没有读取这些字段。因此“平台算法差异”目前是界面表达,不是运行机制。

源码片段二:Agent 激活依赖未记录种子的随机过程

原项目关键片段。来源:get_active_agents_for_round()(根据时段和随机数选择活跃 Agent 的函数,约第 1047-1080 行);固定提交第 1047-1080 行

原项目关键片段:

python
time_config = config.get("time_config", {})
agent_configs = config.get("agent_configs", [])

base_min = time_config.get("agents_per_hour_min", 5)
base_max = time_config.get("agents_per_hour_max", 20)

target_count = int(random.uniform(base_min, base_max) * multiplier)

candidates = []
for cfg in agent_configs:
    agent_id = cfg.get("agent_id", 0)
    active_hours = cfg.get("active_hours", list(range(8, 23)))
    activity_level = cfg.get("activity_level", 0.5)
    if current_hour not in active_hours:
        continue
    if random.random() < activity_level:
        candidates.append(agent_id)

selected_ids = random.sample(
    candidates,
    min(target_count, len(candidates))
) if candidates else []

源码证明即使材料、模型和配置相同,每轮参与者也会变化。仓库中未见为该随机模块设置或持久化 seed 的运行路径,所以结果无法严格重放。PoC 应记录随机种子、模型标识、提示版本、依赖锁和运行 id,并用多次运行分布替代单次故事线。

源码片段三:轮次结束后默认保留环境

原项目关键片段 A。来源:SimulationRunner.start_simulation()(构造子进程命令的方法,约第 511-525 行);固定提交第 511-525 行

原项目关键片段:

python
cmd = [
    sys.executable,
    script_path,
    "--config", config_path,
]

if max_rounds is not None and max_rounds > 0:
    cmd.extend(["--max-rounds", str(max_rounds)])

原项目关键片段 B。来源:main()(轮次结束后进入命令等待循环的脚本入口,约第 1591-1624 行);固定提交第 1591-1624 行

原项目关键片段:

python
if wait_for_commands:
    ipc_handler = ParallelIPCHandler(
        simulation_dir=simulation_dir,
        twitter_env=twitter_result.env if twitter_result else None,
        twitter_agent_graph=twitter_result.agent_graph if twitter_result else None,
        reddit_env=reddit_result.env if reddit_result else None,
        reddit_agent_graph=reddit_result.agent_graph if reddit_result else None
    )
    ipc_handler.update_status("alive")

    while not _shutdown_event.is_set():
        should_continue = await ipc_handler.process_commands()
        if not should_continue:
            break

启动命令没有携带“轮次后立即退出”开关,而脚本默认等待采访/关闭命令。这解释了为什么 OASIS 环境可以支持后续采访,也构成报告阶段必须等待终态的冲突前提。这里是静态推断;应通过网页端到端运行确认实际是否会停留在运行态。

双平台边界

观察 源码事实 选型含义
并发方式 main()(使用并发协程同时启动两平台的入口函数,约第 1492 行)并发调用两个平台函数 并行不等于共享状态
数据库 run_twitter_simulation()(创建 Twitter SQLite 的函数,约第 1151 行)与 run_reddit_simulation()(创建 Reddit SQLite 的函数,约第 1342 行)使用不同文件 帖子、评论和推荐环境相互隔离
人设 两个平台读取同一准备阶段生成的人设与 Agent 配置 初始身份可比,但行为历史不共享
行动 两个平台分别写自己的行动日志 可做并排展示,不能声称发生了跨平台传播
图谱记忆 SimulationRunner._read_action_log()(读取平台行动并提交图谱记忆的方法,约第 767 行)可把两个平台活动汇入同一 Zep 图谱 合并方便报告检索,但平台/运行来源必须保留并可过滤

对当前需求的取舍

建议局部引入子进程隔离、行动日志、进度监控和图谱写入终态屏障;不建议直接引入现有随机选择、固定但未接线的平台参数和自动强制重跑页面。最小 PoC 应先完成:随机种子与运行版本记录、平台参数真正接入、双平台是否需要共享传播的产品决策、刷新不重跑、显式关闭环境,以及 10/40/100 Agent 的时间、费用与失败率测试。

返回 MiroFish 项目分析主报告