# DIO：把明确的长期要求用于后续角色回复

用户说“从现在起莉娅都用短句回答，少用比喻”，这是一条希望持续生效的角色要求。现有 DIO 接线把这类要求提交给外部指令服务处理；后续 RP 请求再读取服务产出的有效提示词，让回复模型使用。

**可复用形态：仓内的 `DioTool` 服务适配器，以及前台工具循环中的 `schedule_dio` 动作。** DIO 服务本身不在本次两个仓库里；本仓能验证的是提交、查询、有效提示词读取和注入链路，不能据此承诺外部服务内部怎样编辑或审核每条要求。

[服务适配器](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/dio-skill.ts#L52) · [前台提交接线](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L974)

## 从一句要求到后续生效

| 时机 | 谁做什么 | 输入与结果 | 保存与生效 |
| --- | --- | --- | --- |
| 用户提出长期要求 | 前台控制模型按提示词判断是否调用 `schedule_dio` | 本轮原文：“从现在起莉娅都用短句回答，少用比喻。” | 仅模型选择，不等于要求已修改成功 |
| 提交 | 程序调用 DIO `POST /v1/chat` | 用户、RP 会话、目标角色 Prompt、原用户输入；返回已接受任务的身份信息 | 外部任务已受理；当前 RP 回复继续 |
| 后台执行 | 程序每秒查询任务，直到终态或后台超时 | `processing` → `succeeded` / `failed` / `timed_out`；附 `promptChanged` 等 | 指令编译与持久化由 DIO 服务负责 |
| 后续请求准备 | 开启相同接线的 RP 入口查询有效提示词 | 按用户、RP 会话、目标 Prompt 读取 `compiledPrompt`、来源版本及哈希 | 验证内容哈希后写入当前请求上下文 |
| 后续 Actor 回复 | 回复组装器用有效提示词替换指定的角色提示词位置，再展开模型消息 | 控制说明、历史与本轮问题仍按原模板保留；替换位置放入 DIO 编译后的完整角色提示词 | 影响这次新生成；已经输出的旧正文不重写 |

受理结果中的 `appliesFrom: "next_turn"` 表示后续轮次使用这条通路；**不是保证紧接着的一句一定等得到新要求。** 如果任务仍在处理、没有生成新 Prompt，下一句只能读取当时服务实际已有的有效内容。

当前程序把**原始用户输入**发给 DIO，没有让前台模型另外编造一份要提交的文本。控制提示词要求只在用户明确要求长期变化时使用该动作；执行端主要验证工具可用、参数和重复调用，不额外运行一个语义模型重新判断用户意图。

[提交原始输入及后台查询](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L974) · [下一轮读取](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L775) · [有效内容与哈希校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/dio-skill.ts#L113)

## 有效提示词怎样真正进入 Actor

下一轮查询得到有效 `compiledPrompt` 后，入口把它放到请求的 `dioCompiledPrompt`。随后 Actor 组装器执行 `applyDioCompiledPrompt`，并把原文放入模板变量 `dioCompiledPrompt`，再由原有模板展开流程生成 Actor 的实际消息。

替换规则是明确的：

1. 如果 PromptManager 有且只有一个 `name="roleplay"` 的提示词，替换它的整个内容，角色和顺序保持不变。
2. 如果没有 `roleplay` 提示词，则要求所有提示词合计只有一个 `{{getvar::systemPrompt}}` 占位，把该占位改为 `{{getvar::dioCompiledPrompt}}`。
3. 重复的 `roleplay`、没有可用位置、或没有 `roleplay` 却有多个 `systemPrompt` 占位，均会抛出错误；程序不随意挑一个位置，也不把长期要求追加到整份上下文末尾。

例如，原 Actor 模板为“控制说明 → 角色提示词 → 历史 → 本轮问题”，新一轮变为“控制说明 → DIO 编译后的完整角色提示词 → 历史 → 本轮问题”。这意味着 DIO 交付的是**可替换该角色位置的完整编译提示词**，不能只交一句“少用比喻”并假定原角色设定仍然保留在这个被替换的位置。

仓库测试检查了最终 `prepared.history` 确实含 DIO 内容，并检查控制说明、历史与当前问题保留；因此这里不是仅把字符串读回日志、却没有交给 Actor 的半成品接线。DIO 服务如何把旧角色资料与新要求编译成这段文本，仍属于外部服务内部职责。

[角色提示词替换规则](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/integrations/roleplay-axon-adapter.ts#L132) · [绑定变量并继续组装](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/integrations/roleplay-axon-adapter.ts#L435) · [最终 Actor 消息与歧义失败测试](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/test/closed-loop-parity.test.ts#L633)

## 如何接入

接入现成 RP 应用时，需要场景开启 `preActorDirector`，服务依赖中提供 DIO 适配器，并带正确的用户、会话、目标 Prompt 身份；Actor 的 PromptManager 还必须满足上述唯一角色提示词位置要求。只填写 `schedule_dio` 字符串不能建立服务连接。

客户若在仓内二开，可以复用适配器。它是应用源码模块，当前不是 Core Tools 的公共子入口：

```ts
// 仓库内代码位置：apps/emochi/tools/dio-skill.ts
const dio = new DioTool(dioServiceUrl, requestTimeoutMs);
const accepted = await dio.schedule({
  actorTurnRequestId: "rp-turn-101",
  userId: "user-42",
  rpConversationId: "conversation-8",
  targetPromptId: "lia-character",
  instruction: "从现在起莉娅都用短句回答，少用比喻。",
  language: "zh",
  userAuthorization: userAuthorizationHeader,
}, signal);
```

示例中服务地址、超时、授权头和 `signal` 由接入者提供。适配器派生 `dio_conversation-8` 作为 DIO 会话，`rp-turn-101:dio` 作为请求 ID，`rp-turn-101:dio:assistant` 作为任务消息 ID；收到的受理身份必须与这次提交一致，否则抛错。

<details>
<summary>接口、返回字段及失败边界</summary>

| 方法 | 接口 | 关键返回 |
| --- | --- | --- |
| `schedule` | `POST /v1/chat` | 验证 `ok`、`accepted`、会话、Prompt、消息、请求 ID；转换为 `{accepted:true,dioConversationId,requestId,taskMessageId,appliesFrom:"next_turn"}` |
| `waitForTerminal` | `GET /v1/director/tasks/by-request/{requestId}` | 任务状态、`promptChanged`、可选 `sourceVersionId` / `compiledPromptSha256` / 错误码与信息 |
| `activePrompt` | `GET /v1/director/active-prompt?userId=…&conversationId=…&targetPromptId=…` | 没找到返回 `undefined`；找到则返回版本、变更集、完整编译提示词及 SHA-256 |

应用后台轮询任务预算为 16 分钟；不是每次 RP 都等 16 分钟，也不是服务处理时效承诺。成功终态仍可能 `promptChanged:false`，不能据“任务成功”就宣布角色要求已改变。

有效提示词**查询或哈希校验失败**时，入口记录降级事件，继续使用正常 RP 材料；显式取消仍向外传播。与此不同，已经读到有效内容，但后续发现 Actor 模板缺少或重复替换位置时，组装器会报错，不走上述查询失败降级。接入验收需要同时验证“服务能返回有效内容”和“Actor 模板能够正确采用”。

长期要求不是普通 V2 的持续笔记，不保存在 `update_director_state` 里，也不自动写世界书或创建独立角色小卡。

[受理请求与字段校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/dio-skill.ts#L65) · [终态返回校验](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/tools/dio-skill.ts#L144) · [读取失败的处理](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L794)
</details>
