# Agent 运行时 v2 设计

> 状态:**v1 已定稿**(2026-06-11 与林动对齐)· 决议见 §5,新增需求见 §6–§9
> 目标:重写 agent 核心,让 7 个 agent **像真人一样独立地活着**——实时感知、自然作息、流畅交谈,
> 而不是现在的"全员齐步走、一拍一小时、轮流发言"。
> 参考:Stanford Generative Agents(记忆/反思/计划)、DeepMind Concordia(组件化认知装配)、
> Altera Project Sid PIANO(并发模块 + 认知瓶颈)。

---

## 0. 现状诊断(为什么要重写)

读完 `agent/` + `world/world.py` + `server/live.py`,问题可以归为五类:

| # | 痛点 | 根因(代码层) |
|---|---|---|
| 1 | **齐步走回合制**:全员同拍感知→并发决策→串行结算,一步推进 1 小时 | `World.step(dt=1.0)`,`_sim_loop` 死循环调它 |
| 2 | **对话不连贯**:A 说一句,B 下一"小时"才听见;一次决策=一句话 | 级联靠 `_recent_prev` 隔轮传递;无对话会话概念 |
| 3 | **动作无时长**:杀人/做饭/跨越半个岛=都是"一拍",移动瞬移 | `Effect` 即时结算,`positions[actor]=dest` 一步到位 |
| 4 | **生理模型失真**:饥饿线性 +0.035/h、prompt 里反复喊"饿了" → 全员刷餐厅 | `tick_needs` 线性累积;`summary()` 把饥饿塞进每拍 prompt |
| 5 | **感知靠轮询快照**:没有事件中断;惨叫和闲聊同权重,都等下一拍才被看到 | `perceive()` 全量拼字符串,无 salience/interrupt 概念 |

保留的资产:记忆流+反思(GA 三件套已实现且可用)、工具系统(Effect 路由)、目击者广播、Director/beats、viz 推流。**重写运行时与认知调度,不推翻记忆与工具。**

---

## 1. 总体架构:从「回合制世界」到「事件驱动的活世界」

```
                    ┌─────────────────────────────────────┐
                    │  SimScheduler(连续模拟时钟 + 事件队列) │
                    │  1 real s = N sim min(可调,默认 30×)│
                    └──────────────┬──────────────────────┘
            唤醒(到点/动作完成/中断) │
   ┌───────────────────────────────┼────────────────────────────┐
   │ Agent Runtime ×7(各自独立的协程,互不等待)                    │
   │  ┌─────────┐  ┌─────────┐  ┌──────────────┐  ┌──────────┐  │
   │  │ Soma     │  │ Attention│  │ Cognition     │  │ Actor    │  │
   │  │ 生理/情绪 │→│ 收件箱/   │→│ L0反射/L1快想/ │→│ 持续动作  │  │
   │  │ (纯函数) │  │ 显著度    │  │ L2慢想/L3反思 │  │ 可被打断  │  │
   │  └─────────┘  └─────────┘  └──────┬───────┘  └────┬─────┘  │
   │        ↑            ↑             记忆流(沿用GA)     │        │
   └────────┼────────────┼──────────────────────────────┼────────┘
            │            │                              ↓ Effect
   ┌────────┴────────────┴──────────────────────────────────────┐
   │ World v2:状态 + EventBus(按地点订阅,带显著度)+ CommonGround │
   └─────────────────────────────────────────────────────────────┘
```

### 1.1 连续时钟与调度器(去回合制)

- **SimClock**:连续模拟时间,真实时间 × 倍率(默认 30×:1 真实秒 = 30 模拟秒;一夜 10 模拟小时 ≈ 20 真实分钟)。倍率运行时可调(造物主加速)。
- **SimScheduler**:`asyncio` 优先队列 `(wake_at, agent_id, reason)`。每个 agent 是独立协程,只在三种时刻醒来:
  1. **动作完成**(走到了/吃完了/写完了);
  2. **预约的思考点**(对话中 5–15 模拟分一拍;独处闲逛 30–60 模拟分一拍;睡觉直接睡到闹钟);
  3. **中断**(高显著度事件:惨叫/停电/有人对我说话/撞见尸体)。
- 没有"轮",没有全局 barrier。A 在和 B 长谈时,C 可以独自在地下室翻箱倒柜,D 在睡觉——**各自的时间线交错前进**。

### 1.2 动作有时长、移动走真实路径(Actor)

- 每个动作 = `(意图, 预计时长, 可中断性)`。做饭 30 分、翻找储藏室 15 分、睡觉到天亮、交谈一轮 1–2 分。
- **移动按 place 图寻路**(BFS over adjacent),逐跳推进:门厅→走廊→楼梯→自己房间,每跳 1–3 模拟分,**途经地点会被该地点的人目击**。
  - 这直接激活地图设计:深夜走过吱呀作响的二层走廊会被邻房听见;去地下室必经厨房后梯或大厅楼梯——"谁在异常时间出现在异常地点"自然涌现,不再需要 prompt 硬灌。
- 执行中的 agent 处于 busy 状态,但**可被中断**:走到一半听见惨叫,可以折返/躲藏(触发 L1 重新决策)。

### 1.3 事件总线 + 注意力(实时感知)

- World 的每个 Effect 发布为 `Event{type, place, actor, content, salience, visibility}`:
  - **同地点**:全量感知(看见+听见);
  - **相邻地点**:衰减感知("隔壁传来摔碎东西的声音"——走廊设计真正起作用);
  - **全岛广播**:停电、钟声、惨叫(带方位:"惨叫来自西边/楼下")。
- 每个 agent 有 **Attention 收件箱**:事件按显著度入箱。
  - `salience ≥ 0.8`(惨叫/尸体/对我说话/火光)→ **立即中断**当前动作,唤醒认知;
  - 中低显著度 → 累积,等下一个自然思考点一起消化(一次 LLM 调用处理一批,省钱)。
- 感知是"推"不是"拉":世界变化主动推给在场者,agent 不再靠下一拍轮询才知道世界变了。

### 1.4 共识层 CommonGround(所有人共享的世界常识)

解决"agent 对地图/时间/规则没有共识"的问题,分三层:

| 层 | 内容 | 注入方式 |
|---|---|---|
| **静态共识**(开局即知,人人相同) | 全岛 36 地点拓扑摘要(分区:鸦堡一/二/地下层+室外,各地一句话)、人物名册(七人姓名/身份)、硬规则(渡船 7:00 抵达、停电时电闸在发电机房、厨房有食物) | system prompt 固定段,**全员一字不差**(可被 LLM 缓存) |
| **动态公告**(实时同步,人人相同) | 当前时刻/天气/电力状态/公开的死讯与集会决议 | 每次思考的 prompt 头部,由 `bulletin()` 生成 |
| **私有知识**(每人不同) | secret/role、个人记忆流、自己见过的线索(密道只有江离开局即知) | persona + 记忆检索 |

共识一致 → 两人对话不会出现"一个以为是白天一个以为是深夜"、"不知道对方说的琴房在哪"。

### 1.5 认知分层(Cognition:省钱 × 流畅的关键)

借 PIANO 的"并发模块 + 认知瓶颈"和 Concordia 的"组件化 prompt 装配":

| 层 | 干什么 | 成本 | 触发 |
|---|---|---|---|
| **L0 反射**(无 LLM) | 固定规则:被点名搭话→进入对话模式;听见惨叫→标记"必须回应"的中断;到饭点且在做低优先级事→把"吃饭"加入候选 | 0 | 事件即时 |
| **L1 快想**(短 prompt,~300 tok) | 对话接一句话、二选一的小决策(跟上去还是留下)、中断后的即时反应 | 低 | 对话节拍/中断 |
| **L2 慢想**(全量 prompt:身份+共识+状态+记忆检索+近期事件) | 重定当前**意图**(intent,如"去书房拿走剪报原稿"),分解为动作序列交给 Actor | 中 | 动作序列完成/强中断/每 1–2 模拟小时 |
| **L3 反思**(沿用 GA) | 重要度累积过阈值 → 综合洞见("温言总在打探,她可能在查我们所有人") | 中 | 阈值 |
| 计划(沿用) | 日计划弱化为"今晚的几件事"(悬疑夜没有日程表),由 ambition+secret 驱动 | 低 | 开局/天亮 |

prompt 用 **Concordia 式组件装配**:`[身份卡] [静态共识†缓存] [动态公告] [躯体状态(仅越阈值的)] [当前意图] [收件箱新事件] [检索记忆] [对话窗口]` ——每层只装它需要的组件,L1 prompt 极短,这是流畅与成本的双保险。

### 1.6 躯体模型 Soma v2(不再刷餐厅)

核心原则:**需求曲线锚定真实人类节律,且"紧张压制生理"**。

- **饥饿 = 饭点驱动,不是线性累积**:基线随"距上一餐时长"+"是否饭点"(08/13/19 点 ±1h 窗口)起伏;吃一顿压满 5–6 小时;**不饿不进 prompt**(只有 hunger>0.65 才在躯体组件出现一行,且吃完后 4 小时静默期)。
- **精力 = 昼夜节律 + 睡眠债**:23:00–06:00 困意上升;白天恢复;不是匀速流失。熬夜有代价(L2 决策质量提示"你已超过 20 小时没睡,注意力涣散")。
- **恐惧/紧张(新增标量)**:目击尸体/惨叫/停电飙升,随时间衰减。**恐惧 > 0.5 时抑制饥饿与困意**(肾上腺素),并改变行为倾向(怕黑的不去地下室、ESFP 找人抱团、ISTJ 反而冷静)。
- 需求影响行为的机制从"prompt 喊话"改为 **L0 候选注入 + 阈值化提示**:饿了只是让"去吃点东西"出现在候选里,绝不每拍重复"肚子很饿"。
- 全部纯函数实现(输入时刻+事件,输出状态),单测可锁死曲线形状。

### 1.7 对话子系统(流畅交流)

- A 对 B 说话 → 世界建立 **ConversationSession(participants, place)**:
  - 会话内按**对话节拍**推进(模拟 1–2 分钟/轮,真实几秒一轮),参与者用 **L1 短 prompt**(身份摘要+对话窗口+一行意图)接话——七嘴八舌不再隔小时;
  - 同地点非参与者收到"他们在低声交谈"事件,可选择**加入/旁听**(旁听拿到摘要版);
  - 退出:任一方告辞/沉默两轮/高显著度中断(惨叫炸群);
  - 会话结束 → 生成**双方视角摘要**写入各自记忆流(GA 做法),L2 拿摘要不拿全文。
- 群体场面(集会指认)= 多人会话的特例,Director 可以发起。

---

## 2. 模块与目录(自底向上交付)

```
src/genesis/runtime/        # 新增
  simclock.py    # 连续时钟+倍率           ← 第1批(纯逻辑,单测)
  scheduler.py   # asyncio 优先队列调度      ← 第1批
  bus.py         # EventBus:地点订阅+显著度  ← 第1批
agent v2(渐进替换 agent/)
  soma.py        # 生理/情绪曲线(纯函数)     ← 第1批
  attention.py   # 收件箱/中断策略           ← 第2批
  actor.py       # 持续动作/寻路/可中断       ← 第2批
  cognition.py   # L0-L3 调度+组件化prompt   ← 第3批
  conversation.py# 会话子系统               ← 第3批
world v2
  bulletin.py    # CommonGround 三层装配     ← 第2批
  world.py       # 改造:Effect→EventBus,去 step() ← 第2批
```

- **第 1 批(地基)**:时钟/调度/总线/躯体,全部纯逻辑零 LLM,单测覆盖曲线与调度顺序;先用**规则桩 agent**(无 LLM)在 ravenisle 上跑通"独立作息+移动有时长+事件中断",viz 上看 7 个小人各走各的。
- **第 2 批(感知与世界)**:Attention + Actor + bulletin + world 改造,仍可用规则桩验证目击/旁听/中断链路。
- **第 3 批(认知)**:接 LLM 的 L0–L3 + 对话会话,跑整夜剧本,调 salience/节拍/成本。
- 旧 `World.step()` 保留一段时间(smalltown/mansion 兼容),ravenisle 切新运行时。

## 3. 成本预估(7 agents × 一夜 10 模拟小时,30× 倍率 ≈ 20 真实分钟)

- L2 慢想:每人 ~1.5 次/模拟小时 × 10h × 7 = ~105 次(全量 prompt ~2k tok);
- L1 快想:对话密集期 ~每会话 10 轮,估一夜 30 场会话 × 平均 8 轮 = ~240 次(短 prompt ~400 tok);
- 合计 ≈ 350 次调用 / 夜,静态共识段走 prompt 缓存。deepseek-v4-flash 下一夜成本可控在几元内;倍率调高时按比例增加。

## 5. ✅ 决议(2026-06-11)

1. **时间倍率**:默认 30×,运行时可调;**有活跃对话时自动降至 10×**(看得清你来我往),会话结束回升。
2. **L1 模型**:deepseek-v4-flash(短 prompt)。
3. **途经目击**:采纳;跳间时长室内 1–2 模拟分、室外 3–6 模拟分。
4. **情绪**:先单一恐惧标量。
5. **旧世界**:**干掉**——不做兼容,v2 落地后移除旧 `World.step()` 回合制与 smalltown/mansion 路径,只服务新世界。
6. **旁听**:摘要。

## 6. 原则:客观世界状态是唯一真理(决议补充)

- **只给 agent 初始化人设 + 最终目标,环境做实,剩下自我发挥。** 不写剧本、不在 prompt 里替世界说话。
- **一切动作落为持久的世界状态**:`WorldState` 维护设施/物品/环境的客观状态(电力 on/off、电闸完好/被砸、门锁/撬开、物品在位/被取走/被烧毁、尸体位置、琴弦缺失…)。
  - 江离砸电闸 → `power=off, switchboard=砸毁` **持久成立**;任何人此后进发电机房,感知到的是"电闸被砸毁"这个客观事实,而不是一条转瞬即逝的事件;
  - 看到的人可以把这个信息**讲给别人**(进入对方记忆,二手信息标注来源)——信息靠社会传播扩散,这就是多 agent 社会学;
  - **动作必有反馈**:每个动作的 ToolResult 必须告诉 actor"世界变成了什么样"(成功/失败/部分成功+所见),否则 agent 会停滞。
- 感知 = 实时事件(EventBus 推送)+ **所在地的客观状态快照**(进房间就看到房间现在的样子)。两者都源自同一份 WorldState,不会自相矛盾。

## 7. 通信机制矩阵(决议补充)

| 模式 | 机制 | 感知面 |
|---|---|---|
| **1v1 私聊** | ConversationSession(2人);低声 → 同地点他人只见"两人在低声交谈"(不闻内容) | 参与者全文,旁人知而不闻 |
| **1v多 / 群谈** | Session(N人),轮流发言,L1 节拍;任何在场者可插话加入 | 参与者全文 |
| **当众喊话** | 非会话型 speech(音量=高):同地点全员全文入耳,相邻地点闻声 | 公开 |
| **旁听** | 同地点非参与者对"普通音量"会话获得**摘要**;刻意凑近(eavesdrop 动作)可升级为全文但有被发现风险 | 摘要/冒险全文 |
| **留言/物证** | 写字条钉留言板、移动物品——经由世界状态异步传播 | 后来者见 |

## 8. 天气系统(新增)

- `WeatherSystem`:状态机(暴雨 ⇄ 狂风 ⇄ 雷暴 ⇄ 雨歇),按剧情夜推进 + 造物主可干预;天气是**世界状态**,进共识公告,影响行为(雷暴夜室外目击距离下降、雨声掩盖脚步)。
- 视觉素材(PixelLab + 程序化):雨幕 overlay(两档密度)、闪电全屏频闪、风(树摇精灵帧/粒子)、窗上雨痕;渲染端按天气状态叠加。🟡 素材另列生成任务。

## 9. 实时小说化叙事(新增,替代事件罗列)

- 新增 **Narrator(说书人)模块**:独立观察者,不干预世界。
- 每隔一个叙事窗口(~10 模拟分钟或攒满一批事件),把窗口内的**事件流 + 相关 agent 的内心独白(thought)+ 场景状态**喂给 LLM,续写一段 200–400 字的小说正文(第三人称,悬疑文风,保持前文连贯——带前情摘要滚动上下文)。
- 产出 `novel` 流:章节化(以 beat/死亡/天亮为章界),viz 左栏从"事件列表"改为"小说连载",同时保留原始事件流(可切换,调试用)。
- 凶手视角信息**只在公开层面出现**(说书人知道一切但只写"读者此刻该知道的"——上帝视角的克制,保留悬念;造物主可切换"全知模式")。
