# Agent V2｜持续角色对话与后台连续性维护

Agent V2 用于持续的文字角色互动：用户说一句，角色先接住当前对话；后台同时判断人物关系、知识边界、剧情、承诺和场景是否需要整理，把有必要延续的信息保存下来，供后续回复使用。它可以支撑角色陪伴、角色扮演和开放式互动故事；具体题材、人物及表达方式由角色资料和模型提示词决定。

这里的完整产品是仓库已经接线的普通 Agentic V2 应用：**角色回复 + 每轮后台判断 + 按需咨询专家 + 按需更新持续状态**。六位专家不是六个默认都要运行的步骤；图片、世界书、日记记忆和剧情 Story 也不因启用 V2 就全部自动开启。

本章核对的是 `roleplay-harness` 主干固定源码 `fa41d220d48327e58d994f936517b23f2f15f658`。已确认应用实现和配置入口；未取得目标生产环境的有效配置与运行记录，因此不把某个模型名、图片能力或可选扩展写成所有线上 V2 的默认配置。普通 V2 与 Story 在入口使用互斥选择：场景启用 `storyV1` 时进入 Story 接线，不再同时启动本文的普通 V2 专家后台。[应用装配与分流](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1160)

## 先看一次真实机制下的两轮演示

下面是用于解释输入、输出和保存关系的业务示例，文字由文档拟写，不是线上模型实测结果。工具是否被模型选中也属于示例分支；程序强制触发的条件在后文单独列出。

角色卡描述“守卫莉娅谨慎，负责旧城城门”；已保存的历史记录说明“用户借了一把铜钥匙，答应天黑前归还”。目前仍在城门前。

| 时机 | 系统实际处理什么 | 本例可能得到的结果 | 谁接着使用 |
| --- | --- | --- | --- |
| 用户发送第 N 句 | `我把铜钥匙递给莉娅，告诉她明早再来。`；程序同时取得角色资料、聊天历史、现有持续状态和本场景的提示词 | 得到本轮回复所需的输入，以及另一份供后台判断的输入 | 回复模型和后台分别读取各自的材料 |
| 当前回复开始 | Actor，即负责写正文的回复模型，读取已经准备好的角色、历史和旧状态 | `莉娅接过钥匙，指尖抚平铜环上的细痕。“明早城门初开时，我还在这里。”` | 通过流式输出交给聊天业务，由业务完成展示和消息保存 |
| 后台判断同时开始 | Orchestrator，即负责决定请谁分析的后台模型，看本轮用户输入和此前材料 | 可能请求 `manage_callbacks`，理由是“钥匙归还及新的会面约定影响后续连续性” | 程序解析工具名称和参数，启动对应专家模型 |
| 专家完成 | 承诺与伏笔专家根据已有材料分析，而不是直接改数据库 | “用户已递出钥匙；此前归还承诺应核对；用户表达了明早再来，莉娅是否同意尚未在本轮初始材料中出现。” | 回给 Orchestrator 第二阶段，也可成为 Director 的参考 |
| 后台决定需要保存 | Orchestrator 选择 `update_director_state`；程序启动 Director，即整理持续笔记的模型 | 生成一整份新的状态草稿，保留此前事实，补充“用户递出钥匙、表示明早再来”等已被材料支持的信息 | 程序检查格式、长度与是否发生变化，再向存储服务提交 |
| 保存成功 | 存储服务接受带旧版本号的更新，返回新版本号 | 状态从版本 12 变成 13，并记录来源轮次、关联回复 ID、调用过的工具及专家分析账目 | 之后开始准备的对话请求可以读到版本 13 |
| 用户发送第 N+1 句 | `第二天，我又来到城门。`；业务传回已保存的前轮正文，程序重新读取当前持续状态 | Actor 同时看到“莉娅上一轮说她还在这里”的聊天原文，以及已保存的约定信息 | 可自然接着写莉娅认出用户，而不重新演一次初次相遇 |

**最关键的时间关系：本轮 Actor 不等后台才开始写，后台也不自动拿到它同时生成的新正文。** 因此，后台可以根据“用户递出钥匙”整理信息，却不能提前把 Actor 稍后才写出的“莉娅接过钥匙、答应明早见面”当成已经看到的事实。到了下一次请求，只有业务已经保存并传回这些文字，后台才会在新的可见历史中得到它们。运行时对 Actor 的完成记录用于观测，没有把该正文追加进本次已启动的后台输入。[轮初材料](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L462) · [前后台同时启动](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L570) · [Actor 完成后的处理](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L701)

如果用户很快发出下一句，版本 13 尚未保存，下一轮就先使用实际读到的旧版本 12，加上当时能取得的聊天历史。普通 V2 不会为了等上一轮后台而把下一句整体排队。

## 一轮内部怎样运转

### 1. 程序先确认这次请求应进入普通 V2

接入方提交本轮消息、会话、用户、角色和场景标识；服务从已允许的场景表找配置。只有选中的场景配置了 `agenticV2`，并走服务准备上下文的闭环路径，才有本文的后台处理。`roleplay-harness/v2` 是请求协议版本，`agenticV2` 是产品的后台配置，二者不是同一个开关。

服务创建三类对象：读取与保存状态的适配器、后台 Planner、安排前后台的 Coordinator，再包装普通的回复运行时。实际代码分别位于 `dispatch.ts`、`planning/runtime.ts`、`planning/agentic-v2.ts`，不是通过一个叫“Agent V2”的文件自动包含整个仓库功能。[场景校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L298) · [普通 V2 装配](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1177)

### 2. 分别准备回复模型和后台的输入

不是“把同一个完整 Prompt 发给两个模型”。两边共用轮初读取的状态，但其他材料各自组装。

| 材料 | Actor：本轮写正文 | Orchestrator／专家／Director：后台 |
| --- | --- | --- |
| 角色是谁 | 按 Actor 的 ModelConfig／PromptManager 展开角色变量和模板 | 角色名、简介、定义；专家与 Director 还会收到角色开场白 |
| 用户这次说什么 | 本轮输入作为回复材料 | 独立列出的 `LATEST USER QUERY` |
| 聊过什么 | 按 Actor 模板中的历史插入规则与上下文预算保留消息 | 编译器只抽取最近 **9 条**非空 user／assistant 消息；不是 9 轮 |
| 已保存的连续性信息 | 把轮初状态及有限的账目内容加入系统消息 | 同一份轮初状态被编译成后台材料；常规上限为 6,500 字符 |
| 用户 Persona | 如果接入和模板提供，则参与回复材料 | 从组装结果取得有效 Persona，截取至 4,000 字符；明确告诉模型这是用户身份，不是角色或 NPC 身份 |
| 本轮选中的世界书 | 场景开启世界书后，由世界书流程选出并按位置注入 Actor 消息 | 当前后台材料构造没有复制本轮世界书条目；专家名叫“知识审计”也不代表自动有世界书检索权限 |
| 日记摘要、记忆表、外部 Hook 补充内容 | 取决于所选记忆来源、Hook 和模板是否接入 | 不会自动复制 Actor 的全部模板变量；不能仅凭 Actor 看到它，就认为后台也看到了 |
| Compact 压缩摘要 | 开启 Compact 后参与 Actor 的预算与上下文组装 | `contextGovernance.mode=enforce` 时使用压缩后保留历史和摘要；`shadow` 模式仅作对照，后台仍取原请求历史 |
| 本轮专家结果 | 不会自动追加入已经发出的本轮 Actor 请求 | 第二阶段 Orchestrator 和 Director 可读取 |
| 本轮新生成正文 | Actor 自己生成 | 本次后台初始快照不含该正文，之后也不会自动补入 |

上述字符数是应用主动截取的材料长度，不等于模型的 token 窗口。[后台历史与输入](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L315) · [专家输入](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/modules/memory/advisory.ts#L218) · [前台模板和预算来源](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/integrations/roleplay-axon-adapter.ts#L425)

### 3. Actor 写回复时，后台先判断要不要调用工具

普通 V2 在每次成功进入 `generate()` 的逻辑轮安排一次后台，协调器防止同一轮重复安排。这里**没有“累计 X 轮才启动后台”的总开关**。前置准备失败、尚未进入生成时，自然不会启动该次后台；自动回复建议也有自己的入口，不应混算成普通 RP 生成。

后台第一阶段会给 Orchestrator：它自己的系统提示词、上表中的业务材料、当前允许使用的工具定义。请求设置 `toolChoice: auto` 和允许并行工具调用。它可以返回工具调用，也可以不调用工具。工具调用形如：

```json
{
  "name": "manage_callbacks",
  "arguments": {
    "reason": "用户递还借用物品并提出新的会面约定",
    "focus": "区分已经发生的递出动作、待确认的接收动作和新的承诺"
  }
}
```

这是工具名与参数的业务展示；实际模型消息使用 `tool_calls` 包装，`function.arguments` 是 JSON 字符串。程序按工具名到已注册能力中查找，解析参数，调用该专家的文本模型。参数不是直接执行的脚本，也不是模型自己访问数据库。

普通 V2 注册表固定装配六位专家与 `update_director_state`；场景 `enabledTools` 从这七个名字中选择。它没有在这条后台默认装上生图、世界书查找、独立 NPC 卡保存、任意 HTTP 请求等工具。公共 SDK 能扩展 Registry，不等于普通 V2 的场景 JSON 可以直接填任意新工具名。[工具注册](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L464) · [模型请求](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L1083) · [参数解析与执行](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/registry.ts#L19)

### 4. 程序有少量强制分析规则

| 情况 | 程序做什么 | 生效条件与限制 |
| --- | --- | --- |
| 首轮 | 补上人物审查 `judge_npc` 和场面指导 `direct_scene` | 对应工具必须存在于 `enabledTools`；目的是逐个识别人和建立场面尺度 |
| 近期出现旧状态未记载的名字 | 补上 `judge_npc` | 从最近 4 条可见消息和本轮问题，用多语言规则识别候选名字，再检查旧状态；这不是能识别所有新人物的语义模型 |
| 上述强制专家被安排过 | 第二阶段补上 `update_director_state` | 状态工具必须启用，仍需通过后续模型调用、内容校验和保存检查 |
| 其他普通继续聊天 | 由 Orchestrator 决定是否需要专家或状态更新 | 不调用专家、不更新状态都是正常分支 |

因此“每轮后台都会跑”不等于“每轮六位专家都会跑”，也不等于“每轮都保存一个新状态”。[强制规则与名字识别](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L655) · [第二阶段补状态调用](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L933)

### 5. 六位专家究竟分别做什么

专家是“带岗位提示词、用各自 ModelConfig 执行一次分析的文本模型工具”。以下输出是解释用途的示例，不是固定 JSON 字段，也不代表自动写库。

| 工具与岗位 | 解决什么问题 | 本例或相近场景的建议结果 | 不能把它理解成什么 |
| --- | --- | --- | --- |
| `analyze_plot` 剧情分析 | 现在是否需要改变可选主线、当前情节或故事方向 | “用户只想归还钥匙，本轮结束借物情节；明早可以留一个低压力的相遇机会。” | 不是强制执行十几轮完整剧本，也不是 Story 产品的世界事件调度器 |
| `judge_npc` 人物审查 | 谁是谁、几个人、动机、知道什么、在哪、如何进入或离开 | “新出现的商人艾登是一名独立人物；与莉娅的关系尚未建立，不应当作旧友。” | 不是一个直接创建独立角色卡并保存的 NPC 服务 |
| `audit_knowledge` 知识审计 | 事实、猜测、谎言、秘密与证据分别被谁知道 | “用户只说钥匙归还，莉娅没有依据知道用户昨晚去过地下室。” | 不会因名称含知识而自动搜索世界书或联网 |
| `simulate_world` 世界时间与场外生活分析 | 经过了多少时间、位置是否合理、日程和期限如何变化 | “用户明确说第二天，时间可推进；莉娅是否仍值守需要结合已建立班次。” | 不是自动把世界书设定或世界数据库全面修改 |
| `manage_callbacks` 承诺、物品与伏笔管理 | 哪些约定、线索、物品、伤势、义务和后果尚未处理 | “核对铜钥匙归还状态；保留用户说明早再来的意向。” | 不意味着伏笔必须立即兑现，也不直接更新物品表 |
| `direct_scene` 场面指导 | 视角与描写重心怎样分配，角色允许主动做多少事 | “以莉娅反应和城门环境为主；她可以提出一个话题，不替用户决定留下。” | 不是后面写完整持续状态的 Director；这是六位专家之一 |

所有成功专家先返回 `{ ok: true, tool, result, usage? }`；`result` 是分析文本。常规模式最多保留 5,000 字符。开启严格上下文治理后，结果超出配置长度会成为失败结果，不静默截成一份貌似完整的建议。[工具职责与 schema](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/modules/memory/advisory.ts#L18) · [模型执行与结果限制](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/modules/memory/advisory.ts#L244)

### 6. 第二阶段决定是否把建议变成持续状态

已选中的专家并行执行。程序把工具调用与结果重新交给 Orchestrator；这一阶段只提供 `update_director_state` 一个工具，模型选择更新或结束。这是看完分析后的再次决策，并没有无限的“再叫专家、再分析”循环。

如果第一阶段没有咨询专家，但直接请求了状态更新，程序可以直接运行 Director，省略第二阶段；如果什么都不需要，保留旧状态。如果第一阶段同时请求专家和 Director，Director 被延后，不能在还没得到专家建议时与专家一起写状态。[第一阶段执行和第二阶段工具范围](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L843) · [延后 Director 的消息](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L607)

### 7. Director 生成整份笔记，程序校验后保存

Director 接收角色与开场白、用户 Persona、旧状态、近期历史、本轮用户输入、更新理由，以及已取得的专家结果。它输出一整份 **Agentic RP State 文本**，不是补丁，也不是用户看的回复。

这份持续笔记包含固定栏目：当前场景、角色生活与近期目标、柔性主线、未来发展备选、人物关系和代词归属、知识边界、位置与进入限制、新人物安排、时间与场外生活、待兑现承诺与物品、场面重心、角色主动程度、必须保持的事实、用户拒绝过的方向等。它允许提出后续方向，但这些方向不是已经发生的故事事实，也不是必须照做的工作流。

程序检查候选是否为空、是否至少 500 字符、是否超过配置上限（最多 8,000 字符）、规定栏目是否存在且顺序正确、是否混入内部推理标签，并比较新旧文本是否真的变化。**这是结构和写入检查，不是另一个模型逐条核实新状态真实性的独立复审。** Director 的输入提示要求保留未变事实、只采用材料支持的变化，但不能把这条提示当成事实一致性已经被程序证明。[Director 输入与输出要求](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L380) · [程序校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L514)

## 保存什么、何时保存、下次取回什么

| 数据 | 什么时候产生或保存 | 保存／读取通道 | 实际用途 |
| --- | --- | --- | --- |
| 用户与角色聊天原文 | 由聊天业务在自己的消息生命周期中保存 | Chat Service 的消息系统；后续请求传入历史 | 让下一轮知道实际说过什么，包括 Actor 本轮新写的内容 |
| Agentic RP State | Director 给出有效、与旧状态不同的候选，且存储检查通过时 | Axon `roleplay.state.compare_and_set` | 后续轮初通过 `roleplay.state.get` 取回，加入 Actor 与后台输入 |
| 专家分析账目 `ledger` | 与成功状态提交一起写入；不是专家一完成就单独保存 | 同一状态提交携带 `ledgerEntries` | 保留该轮分析、来源工具和用户问题；之后只选有限的活动账目投影到上下文 |
| 已调用工具与来源 | 状态提交时 | `toolsCalled`、`sourceTurn`、`sourceMessageId`、`historyRevision` | 识别状态来自哪个轮次和回复，协助版本检查和追踪 |
| 调试节点与完成状态 | 各阶段异步、尽力记录 | `markPlanning`、`markDebugNode`、`markProcessed` | 观察走到哪、为什么没有更新；不是持久业务状态的替代品 |

持续状态按 **用户 + 会话 + 场景** 读取和提交。正常提交携带“我基于版本 12 生成”这样的预期版本。存储服务若认为已经过时，返回冲突；本次 Planner 记为 `stale_write_rejected`，不会换成另一个版本再自行拼接重写。该仓库调用 Axon 接口，实际底层表、事务及跨实例仲裁由 Axon 实现，不能从客户端代码推断出未读过的数据库保证。[状态读取与提交适配器](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L84) · [RPC 字段](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/axon/client.ts#L697)

下次读取返回的是“版本信息 + 状态文本 + 账目”，不是模型的一段隐藏思考。以下展示应用内部转换后的数据形态，`state` 内容只节选了两行用于阅读，不能拿这个节选当作满足 Director 校验的完整状态：

```json
{
  "found": true,
  "revision": 13,
  "sourceTurn": 8,
  "sourceMessageId": "assistant-turn-8",
  "historyRevision": 16,
  "state": "[Agentic RP State]\nCurrent scene: 旧城城门；用户已递出铜钥匙，表示明早再来。\nOpen callbacks, promises, items, injuries, and deadlines: 核对钥匙交接；用户表达了明早再来的意向。",
  "toolsCalled": ["manage_callbacks", "update_director_state"],
  "ledger": [
    {
      "type": "callback",
      "key": "manage_callbacks:turn:8",
      "status": "active",
      "payload": {
        "analysis": "区分用户递出、对方接收与双方同意会面，不能把未看到的回应补成事实。",
        "sourceTool": "manage_callbacks",
        "sourceTurn": 8,
        "userQuery": "我把铜钥匙递给莉娅，告诉她明早再来。"
      },
      "sourceTool": "manage_callbacks",
      "sourceTurn": 8
    }
  ]
}
```

程序会把 `state` 和选中的账目拼成供模型读取的文字，再放进相应消息；并非把整个数据库对象直接给模型。Actor 这段状态材料的编译上限为 `maxStateChars + 2000` 字符，后台常规调用使用 6,500 字符；最终仍分别受各自模型输入预算约束。

账目按 `event / npc / knowledge / world / callback / camera / agency` 分类。下一次编译上下文时，不会把所有历史账目无限堆给模型：代码选最近 60 个活动项，每类最多再取 6 条、每条内容最多 900 字符，最后受总字符上限约束。专家成功但未执行 Director、候选没变化、候选无效或提交冲突，都不能直接等同于这些专家结果已经成为下一轮的持续资料。[账目形成](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L758) · [读回时的账目选择](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/modules/memory/advisory.ts#L197)

首次没有状态时，程序给一份保守默认笔记：从角色开场和可见对话自然继续、不凭空确定未出现的人物、秘密和场外事件、保留用户自主决定。提供默认笔记不代表已写库；仍需后续完成有效状态提交。[初始状态](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L470)

## 模型开始工作前，系统到底给了什么提示词

普通 V2 **没有一条写死在 Planner 文件内、覆盖所有角色的“主 Agent 完整 system prompt”**。当前服务从 Axon 获取各 ModelConfig 关联的 PromptManager，再按岗位名读取模板：

| 模型岗位 | 提示词来源 | 程序另加的业务材料 |
| --- | --- | --- |
| Actor | 本轮有效 Actor ModelConfig 的 PromptManager／Preset；按各 prompt 的角色和顺序展开 | 角色、用户、历史、接入的记忆／世界书、V2 持续状态 |
| Orchestrator | `orchestrator` 命名模板；升级模型可以有自己的版本 | 轮次、旧状态版本、角色、Persona、近期对话、本轮输入；结尾明确允许普通继续聊天不选工具 |
| 六位专家 | `specialist_analyze_plot` 等六个命名模板，各自从配置的 ModelConfig 读取 | 岗位共有材料加 `reason`／`focus` |
| Director | `director` 命名模板；加载时必须含明确的状态开头与栏目 | 旧状态、角色、历史、问题、更新理由、专家结果；要求生成完整替换文本 |

模型调用形式是“系统岗位提示词 + 用户角色的材料文本 + 本阶段工具定义”。运行时会展开语言和角色等模板变量。源码可以证实材料与工具是如何组织的；**具体环境里正在生效的整段岗位提示词，要从该环境 ModelConfig／PromptManager 读取**。本文没有把测试里的假提示词、某份旧 Demo 的模板或工具介绍冒充成生产 prompt。[模板加载与岗位名称](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/prompt-manager.ts#L21) · [后台首条业务材料](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L538)

## 可配置什么，默认值意味着什么

这些是服务端场景配置，不是让最终用户随请求任意控制的参数。下表默认值来自构造函数和配置解析器，表示“未覆盖时的程序行为”，不是宣称生产每个场景都采用这些值。

| 配置 | 默认／范围 | 开启或修改以后 |
| --- | --- | --- |
| `agenticV2` | 缺省不启用；内部 `enabled:false` 也不启用 | 装配本文的后台；普通模式需不启用 `storyV1` |
| `modelConfigs.actor` | 必填，并必须等于该场景 `modelConfigRef` | 选择写正文的模型配置；同时需要有效 PromptManager／Preset |
| `modelConfigs.orchestrator`、`specialists`、`director` | 均需提供引用；六位专家分别配置 | 可以替换不同岗位模型及其 prompt；即使限制 enabledTools，当前加载仍要求六专家配置与模板齐全 |
| `enabledTools` | 默认六专家 + `update_director_state` | 限制 Orchestrator 可选工具；只能填当前七个名字。删掉 Director 后，专家咨询结果不会沿本文流程写成新状态 |
| `routingMode` | `manual` | 指固定编排模型，不是人工手动点选工具；`auto` 在达到阈值时可换升级模型重新作选择 |
| `escalationToolCallThreshold` | 3；可配 1–20 | 自动路由时模型请求工具数达到阈值会触发升级；需同时设置 `escalationModelConfigId` |
| `maxToolCalls` | 7；可配 1–20 | 参与第一阶段的选取预算；第一阶段实际执行上限同时受并行上限控制，Director 在后续独立处理。不能把 7 理解成整轮最多只有 7 次模型请求 |
| `maxParallelTools` | 6；可配 1–20 | 限制第一阶段并行专家选择；模型重试、编排判断和 Director 不等于这六个并行名额 |
| `maxStateChars` | 8,000；可配 800–8,000 | 限制完整持续状态候选长度；不是聊天历史长度或模型窗口大小 |
| `modelCallTimeoutMs` | 180,000 ms；可配 10,000–300,000 | 后台单次模型调用超时；不是用户一定要等待三分钟，也不是整个后台统一三分钟截止 |
| `contextGovernance` | 缺省不启用 | `shadow` 记录预算对照；`enforce` 严格约束并要求配置 Compact。V2 与 Compact 同时配置时必须显式指定该项 |
| `contextGovernance.safetyMarginTokens` | 开启治理时默认 256；可配 1–8,192 | 在模型可用预算内预留空间 |
| `contextGovernance.maxToolResultChars` | 开启治理时默认 5,000；可配 512–20,000 | 控制专家结果大小；严格模式过长则按失败结果处理 |
| `jevOrchestration.argumentModelConfigId` | 缺省不用 Jev | 可把两阶段工具选择交给 Jev 决策端点；专家和 Director 仍用文本模型，工具自由文本参数由指定文本模型补全 |
| `recovery.transientRetryModelConfigId` | 可选 | 提供暂时故障时的恢复模型；具体恢复策略由模型适配器处理 |
| `recovery.tolerateMissingStreamMetadata` | 仅显式 `true` 时启用 | 容许特定缺失流元信息情形；不是忽略一切模型错误 |
| `modelConfigs.autoReply`、`autoReplyWaitMs` | AutoReply 模型可选；等待默认 30,000 ms，范围 0–120,000 | 供自动回复建议的独立请求使用，不是控制普通后台每隔多少轮触发 |

配置解析还要求自动路由有升级模型；Jev 的参数模型不能填写 Orchestrator／升级 Orchestrator 的配置；Actor 引用要与场景一致。这些不匹配会在配置解析或依赖就绪检查时失败。[默认值](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L220) · [配置范围与校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L114) · [V2 与 Compact 组合条件](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L808)

另外，仓库一些 JSON 留有 `actorControlEnabled`，但本基线的 `AgenticV2Config` 和解析返回值没有采用这个字段；它不能作为“关闭后台控制”的有效开关。前台模型选 Actor／图片／DIO 的工具循环由另一个配置 `preActorDirector` 控制，需在对应原子能力中说明，不能混进后台专家开关。[实际解析返回值](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L235) · [独立前台循环接线](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L957)

### 上下文窗口能不能改

可以改，但不在 `maxStateChars` 上改。Actor 的窗口上限来自有效 ModelConfig 的 `context_limit`，或服务接受的 `runtime_decision.context_limit`；模型的输出预算来自生成参数。历史实际保留多少还受 PromptManager 的历史插入规则、Compact 和最终预算计算影响。旧路径会保留系统消息，按剩余空间从较近消息向前选择；严格治理路径在最终请求上计数和检查。

后台又有自己的截取规则：最近 9 条可见消息、状态材料和专家结果长度限制。**只把 Actor 窗口调大，不会自动取消后台最近 9 条的规则。** 需要更长后台上下文时，应修改／扩展对应编译逻辑，或使用已实现的 Compact 严格接线；不能对客户承诺“换成长上下文模型就自动看完所有历史”。[Actor 窗口来源](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/integrations/roleplay-axon-adapter.ts#L439) · [旧 V2 补状态与滑动历史](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L498)

## 怎么接入这个完整产品

普通 V2 当前是 `apps/emochi` 下的应用实现，已经通过 Agent Router 服务接入；不能只安装 SDK 后调用一个未提供的 `createAgentV2()` 就获得上述全部行为。

接完整产品的路径是：**业务聊天服务 → 已配置的 Agent Router 场景 → Harness 应用服务 → Axon 资料／状态与 Kaon 模型 → 流式事件与结果返回业务**。接入方继续负责用户权限、额度、聊天消息保存、最终展示和自身的计费／通知；本应用承担本文明确接管的上下文准备和 V2 状态处理。

### 接入要准备的数据和服务

| 需要准备 | 为什么需要 | 缺少时会怎样 |
| --- | --- | --- |
| 用户 ID、会话 ID、角色／Prompt ID、请求 ID | 找到角色和会话资料，定位本轮 | 请求或资料查询无法正常执行 |
| 当前用户输入、此前可见消息及消息 ID | 当前回复和后台连续性判断的事实来源 | 无法恢复未传回的实际正文；部分校验会直接报错 |
| 预先分配的本轮 `assistant_message_id` | 关联本轮后台来源及状态保存 | 普通 V2 prepare 明确报错，不能省略 |
| 场景 ID 与对应服务配置 | 选择普通 V2、阶段模型和预算 | 未注册场景返回错误，不自动随意选模型 |
| Actor、Orchestrator、六专家、Director 的 ModelConfig 和 PromptManager | 取得实际模型、岗位提示词、生成参数 | 模型配置、岗位模板或 provider 不合要求时失败 |
| Axon | 角色资料、变量／模板展开、状态读取和条件保存等 | 完整应用当前不能脱离这些依赖直接运行 |
| Kaon 模型服务与凭证 | 执行正文和后台模型调用 | 没有可用路由就不能生成 |
| 后台任务生命周期与关停处理 | 后台在当前进程运行，应允许正常完成或处理关停 | 普通进程内 runner 不是可在重启后自动恢复的持久任务队列 |

**服务配置片段**如下，只说明字段之间的关系。占位 ModelConfig 需要由接入环境真正创建；这不是复制即可运行的完整配置，也不是公共 SDK options：

```json
{
  "upstreamModel": "<该场景允许的模型路由>",
  "defaultTimeoutMs": 580000,
  "modelConfigRef": "<actor-model-config>",
  "presetRef": "<actor-preset>",
  "agenticV2": {
    "modelConfigs": {
      "actor": "<actor-model-config>",
      "orchestrator": "<orchestrator-model-config>",
      "specialists": {
        "analyze_plot": "<plot-model-config>",
        "judge_npc": "<npc-model-config>",
        "audit_knowledge": "<knowledge-model-config>",
        "simulate_world": "<world-model-config>",
        "manage_callbacks": "<callback-model-config>",
        "direct_scene": "<scene-model-config>"
      },
      "director": "<director-model-config>"
    }
  }
}
```

**请求扩展片段**如下。它应放进 Agent Router 的完整请求中，外层还需要协议要求的项目、版本、会话、角色、消息及 `generation.n=1` 等字段。这里不把片段冒充为完整 HTTP 请求：

```json
{
  "extensions_version": "roleplay-harness/v2",
  "extensions": {
    "scenario_id": "<服务端已启用的普通-v2-场景>",
    "turn_context": {
      "user_id": "user-example",
      "model_alias": "<业务侧有效模型别名>",
      "assistant_message_id": "assistant-next-message",
      "language": "zh",
      "input_question": "我把铜钥匙递给莉娅，告诉她明早再来。"
    }
  }
}
```

实际服务使用 ConnectRPC AgentService／Agent Router 协议，不应把这些内部字段随意塞到普通 OpenAI `/chat/completions` 就认为能启用该产品。[请求扩展解析](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L482) · [完整服务接入说明](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/docs/integrations/chat-service-agent-router.md)

场景文件存在不等于场景启用：文件方案需要服务的 `HARNESS_ENABLED_SCENARIOS` 选中，旧配置表 `AGENT_ROUTER_SCENARIOS_JSON` 也仍然支持。文件与旧表同名时以旧表的整项定义为准。实际生效配置在服务启动时加载，不是每个请求自动重新读取文件。[场景启用与合并规则](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/docs/scenario-configuration.md)

## 失败、取消和并发时会发生什么

| 情况 | 当前行为 | 对业务接入的含义 |
| --- | --- | --- |
| Orchestrator 决定不用工具 | 本次后台结束，旧状态保留 | 正常的成本控制分支，不是漏跑后台 |
| 某位专家失败 | 返回失败结果，其他并行专家仍可完成；结果交给后续阶段 | 后续模型可能仍决定更新，不能把“有一位专家失败”说成整轮必定回滚 |
| Director 输出不合法 | 有限重试后仍失败则拒绝该候选，旧状态保留 | 文本回复可以已经成功；观察后台 outcome 才知道状态是否更新 |
| Director 输出与原状态相同 | 不创建新版本 | 后台执行过不代表写库过 |
| 状态提交发现旧版本过时 | 本次结果记为 `stale_write_rejected` | 不覆盖新状态，也不在这次后台自动换材料重跑 |
| 保存请求失败但是否写入不明确 | 有限重试；若后续出现冲突，会读回并对版本、来源回复 ID、状态全文核对是否其实已经成功 | 处理不明确结果，不能简单把重试次数当成功次数 |
| 用户取消／Actor 报错 | 已安排的后台没有绑定 Actor 请求取消信号，仍可能完成并尝试保存 | 若业务要求“正文已接受才允许状态生效”，需要额外接入接受机制；普通后台本身没有这个保证 |
| 下一句到来时上一轮后台仍运行 | 可以并发，以各自轮初材料工作 | 下一轮可能先用旧状态；不存在必然在下一句前完成的 SLA |
| 进程退出 | runner 依赖进程内任务与关停信号 | 不承诺重启后自动恢复这次后台 |

后台模型层有有限失败重试（通常原调用加两次），并对中止、上下文治理错误及明确不应重试的错误提前停止。整个后台任务此处未设置统一超时，但各模型调用有自己的超时；界面状态使用的 `timeoutSeconds:900` 是规划状态记录字段，不能当成后台运行器一定在 15 分钟停止的保证。[模型和保存重试](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L102) · [保存 outcome](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L957) · [后台任务生命周期](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/tasks.ts#L23)

## 二开时哪些可以直接改，哪些需要补接线

- **换模型、岗位提示词和预算**：使用现有配置入口；必须保持岗位模板、状态格式和模型 provider 等要求。
- **启用世界书或记忆来源**：使用对应应用已有配置和依赖接线；Actor 能读取不代表后台自动拥有同样资料，需要检查实际输入。
- **为前台增加“写正文／配图／长期修改”的选择**：使用独立前台工具循环配置及相关服务，不是在后台专家名单里加三个名字。
- **希望新人物成为独立可编辑 NPC 卡**：现有 `judge_npc` 产出分析，普通 V2 保存的是持续状态和分析账目；还需角色卡 schema、持久化工具、身份归属和读取接入，不能直接承诺已经完成。
- **希望后台能检索世界书、根据剧情改写设定**：需为该后台提供检索／写入能力、输入与权限策略，再定义哪些结果可以变成事实；当前六专家并不具有这套自动执行链。
- **希望根据用户是否接受本轮正文来提交状态**：需实现并接通明确的接受边界、来源版本和取消语义；不能只借用“快慢通道”名称便认为已有。
- **希望把这些模型工具用于别的业务**：原子能力可分别使用；普通 V2 的两阶段、强制 NPC 规则和状态格式属于本产品策略，不是公共 SDK 对每个接入方的硬要求。

## 代码入口与当前交付核对点

| 要定位的内容 | 固定源码位置 |
| --- | --- |
| 普通 V2 与 Story 如何分流、应用怎样装配 | [dispatch.ts 1160](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1160) |
| 前后台材料、逐轮调度、Actor 完成与取消关系 | [planning/runtime.ts 350](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/runtime.ts#L350) |
| 工具选择、首轮强制规则、第二阶段、Director 和提交 | [planning/agentic-v2.ts 792](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/agentic-v2.ts#L792) |
| 实际岗位提示词从哪里加载 | [prompt-manager.ts 75](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/prompt-manager.ts#L75) |
| 六专家共有执行、输入和结果 | [memory/advisory.ts 218](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/modules/memory/advisory.ts#L218) |
| 配置字段是否真的被解析 | [server.ts 114](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/server.ts#L114) |

源码已经具备普通 V2 应用与可选 Jev 编排适配；仓库场景中未默认开启 Jev。`agentic-v2-luna-gemini38-pov-v1` 名字带 V2，但文件启用了 Story，不作为本文普通后台的默认配置。Pioneer 等开发分支也不自动进入本文主干行为。对具体交付环境，还需取得有效场景、运行镜像、各岗位 ModelConfig／PromptManager、真实状态读写与取消／并发结果，才能形成“该产品此环境当前运行什么”的交付记录。[Jev 接线](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L934) · [Story 场景文件](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/config/scenarios/agentic-v2-luna-gemini38-pov-v1.json)
