跳转至

迭代、版本与报告

一个已经跑完的研究是一份结果——迭代绝不能悄悄毁掉它。 SocioVerse 用一条规则加一套机制来保证这一点。

规则:所有修改都走 /sv-iterate

对既有研究的任何修改——加一条政策广播、加几步、换人群规模、调一个 阈值——都经过 /sv-iterate

不要手改产物再重跑

原地编辑产物再重跑,会把上一条轨迹无记录地覆盖掉。 「再加一个政策跑一轮」是一次迭代,不是一次编辑。

版本门之前:reference 守卫

study.yamlreference: true 的研究——内置的示例模板与适配基线——是 只能 fork 的。这道守卫跑在版本门之前,并且直接绕过版本门:新版本、 原地、分支三个选项都会改动同一个 study_id,而这恰恰是共享基线最不能承受的。

所以对 reference 研究说「改一下这个」,结果是 fork:

  • fork 得到新的 study_id(且 reference: false),模板本身分毫未动;
  • 新 fork 没有历史可版本化,因此跳过版本门——它从 v1 开始自己的版本线, 直接进入流水线。

原地编辑基线模板是一个罕见而慎重的选择,必须先给出明确、说全的警告:此后 所有由该模板派生的研究都会继承这次改动。非 reference 的工作研究不经这道守卫, 直接进版本门。

版本门

/sv-iterate 总是开口问(从不擅自决定)这次修改如何版本化:

选项 发生什么
新版本(推荐默认) 现行目录快照到 versions/v<N>/——产物、代码、结果——然后以 v<N+1> 的身份迭代
原地修改 不做快照;当前输出丢失(经典的覆盖流,仅限主动选择)
v<X> 分支 新版本以某个版本的产物为起点;versions.json 记录父子树,跨版本探索保持可导航

快照是不可变的本地历史(versions/ 与运行输出一样被 gitignore): 旧版本永不自动删除,凡是跑过的东西永远可查。

模式与 provider 的切换只能走新版本

决策 / 运行模式或 provider 的切换——scripted ↔ LLM、本地 ↔ 真 LLM、 确定性 ↔ 随机——根本不提供「原地」选项。之前那次运行就是科学对照基线: 做 scripted 干跑或本地模型跑一遍,意义正在于它要摆在真实运行旁边比较。 这类改动只提供「新版本」和「分支」。

复核门(待复核)

创建版本时必须说明流水线从哪里恢复: create_version(..., resume_from_stage=...) 接收——

改动类型 resume_from_stage
从当前状态开新版本 受影响的最早阶段——新广播 → sv-build-environment;人群变化 → sv-build-population;决策逻辑变化 → sv-build-model
从历史版本分支 sv-init——分支是一次大的语境切换,一切都要重新复核

从该阶段往后,每一个沿用(carried)的阶段在 dashboard 上都显示为 待复核,直到你处理它。处理方式只有两种:

  • 受影响的阶段 → 照常用它的 sv-* skill 重新撰写。
  • 未受影响的阶段 → 用 schema 校验沿用下来的产物,确认它对新问题依然成立, 然后贴出它的叙述正是这段叙述清掉「待复核」标记。

验证并叙述——不要重写未受影响的产物

「保险起见」重写一份沿用产物,会搅动它的 mtime,破坏 dashboard 对 沿用 / 陈旧的判定。校验它、说清它为什么依然成立,字节原样别动。

既没重写、也没验证的沿用阶段会一直保持待复核扣住指针——所以流水线 永远不可能在还带着未复核阶段的情况下看起来已经完成。/sv-run 照旧在花钱前 确认。

热启动

当修改只影响第 J 步之后的轨迹——延长了时间跨度,或新干预落在第 J 步——完整重跑会为不可能改变的步数重付 LLM 成本。热启动(warm start)避免这笔浪费:

warm_start = { source: <父版本>, resume_from: K }

/sv-run 会从父版本存储的行为里回放 0..K 步——精确复现、零 LLM—— 只为 K+1..n_steps 花钱。

适用条件

热启动适用于 interaction_rounds == 1 的从零构建(Path B)研究 ——它们的 apply 是行为的纯函数。包装遗留引擎的研究和多轮交互研究 从第 0 步重跑;引擎宁可报错也不会错误地热启动。

多轮之所以被排除,不是回放代码做不到,而是存下来的东西不够:父版本的 面板里只持久化了最后一轮的行为,中间那些促成它的轮次无法重建。见 多轮交互

报告

/sv-reporttrajectory/study.duckdbpanelmetricsevents, 可选 messages),写出 reports/report.mdreports/figures/*.png。 报告是分析性的,不是数据堆砌,而且结构是被规定死的——一条 总–分–总 的弧线, 顺序如下:

# 章节 必须做到什么
总起 · 结论速览 全程 2–4 条头条结论,每条一行,写成断言——「X 发生了,因为 Y」,而不是「我们模拟了 X」。这是正文接下来要证明的论点。
轨迹表格与图 后面一切据以解读的描述性骨架
分 · 发现与解读 大致一个关键结果一条发现。每条断言紧挨着它所依据的具体数字或图并且给出机制——那个「为什么」,从面板与分群里读出来
深层洞察 原始表格没有直说的、跨指标 / 跨分群的非显然结论——谁被隔离、谁被暴露,某个临界翻转,两个指标的背离——每条仍然扣在揭示它的数据上
总结 · 回扣与深挖 回扣那几条(现在已被正文挣到的)头条结论,并再深一层:它们对研究问题意味着什么、统摄它们的机制是什么、什么仍不确定。不引入无支撑的新论断。
改进建议与下一步 可操作的建议,以及下一轮 /sv-iterate 该测什么——试哪条政策、扫哪个参数、探哪个分群
数据基础与参考来源 放最后,由 grounding 侧车渲染

③ 的硬规则

没有数字的断言不写,没有断言撑着的数字也不写。 一条发现读起来是「满意度从 0.74 跌到 0.61(step 2 政策后,见 figures/satisfaction.png)」再接机制——而不是一段解读旁边扔一张表, 让读者自己去对。

最后一节由 grounding/grounding.json 渲染成一张管道表:

事实 依据 来源
实证 / 代理 / 假设 title (url)

外加 implementation_refs(建模参考)与 assumptions(声明的假设)。 method_notes"stylized" 的研究只得到一行诚实的 「风格化模型,无真实世界锚点。」;fork 出来的研究会在这一节开头说明自己继承了 哪些锚点。见真实世界锚定与溯源

用 SQL 问你自己的问题

报告是一种渲染;存储才是结果本身。study.duckdb 永远可查:

-- 单个 agent 的纵向视图
SELECT step, state, action_kind
FROM panel WHERE agent_id = '…' ORDER BY step;

-- 聚合轨迹
SELECT * FROM metrics ORDER BY step;

-- 什么在何时触发
SELECT step, note FROM events ORDER BY step;

-- 如果研究用了消息总线:谁说了什么
SELECT step, author_id, content FROM messages ORDER BY step;

收尾时还会在旁边导出 Parquet(panel.parquetmetrics.parquet), 方便 pandas / R 工作流。更多查询示例见 FAQ