# 编排与扩展：做自己的 Agent、工具循环和快慢协作

**用途：**业务可以复用公共执行组件，自己决定模型要完成什么、能够调用哪些工具、结果由谁采用。这个入口不自动附带普通 V2 的六专家、Director 或 Story 的剧情策略。

## 三个可以单独使用的公共入口

| 入口 | 你提供什么 | 程序实际做什么 | 得到什么 |
| --- | --- | --- | --- |
| `Agent<TInput, TResult>` | 实现 `prepare`，返回原生 Pi `options`、`prompt` 和 `result` 解释器 | 每次 `run` 新建一个 Pi Agent，执行对话/工具循环，传递取消 | `result` 从 Agent 消息中提取的业务结果；没有默认状态写入 |
| `SlowTurnRuntime` | 一次准备好的上下文、模型、系统提示、可选工具与预算 | 运行受限制的模型—工具循环，可等待完成 | `final`、`toolResults`、调用量、状态结果等 |
| `FastSlowRuntime` | 快路执行器＋慢路配置＋状态仓库 | 先读同一份状态并冻结本轮材料，再启动慢路及快路 | `fast` 和 `slow` 两路结果、起始快照、`cancel` |

`Agent` 入口已经具备通用模型选工具循环；不能写成“Harness 的 agentic 编排都待支持”。同时，普通 V2 应用自己的两阶段规则并不因此自动替换为这个通用循环。

三者都是 SDK 根入口 `@flowgpt/roleplay-harness` 的公开导出。`apps/emochi` 下的 Actor、应用协调器与 profile 节点执行器不在这个包的公开导出范围内；安装 SDK 不会同时装出一套可直接运行的 RP 产品。固定源码的 package.json 标注 SDK `0.3.0`、Node.js ≥22、受限 GitHub Packages；这个版本号本身不能证明某个仓库新增字段已在注册表中的同号包发布。

[通用 Agent 的 prepare/run](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/agent.ts#L3) · [独立慢路实际循环](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/slow-runtime.ts#L64) · [读取状态与启动快慢两路](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime.ts#L89)

[实际公开导出](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/index.ts#L1) · [包范围、运行环境与发布目标](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/package.json#L1)

### 接线片段：调用自己的快慢两路

下面用公共入口展示接入位置。`replyModel`、`analysisModel` 是宿主实现的模型适配器；`scopedStateStore` 绑定业务身份并实现读与 CAS；`registeredTools` 是宿主明确允许的工具执行器列表；`validateStateProposal` 是自己的采用规则。它们都不是安装包后自动出现的服务。

```ts
import { FastSlowRuntime } from '@flowgpt/roleplay-harness';

const runtime = new FastSlowRuntime({
  fast: { model: replyModel, modelConfigId: 'reply-config' },
  slow: {
    model: analysisModel, modelConfigId: 'analysis-config',
    prompt: '检查连续性；把已确认事实与后续建议分开。',
    capabilities: registeredTools,
    state: scopedStateStore,
    prepareCommit: (final, results, context) =>
      validateStateProposal(final, results, context),
  },
});

const turn = await runtime.run({ context: {
  requestId: 'turn-2', sourceTurn: 2, sourceMessageId: 'user-message-2',
  language: 'zh', input: '我递出铜钥匙。',
  history: [{ role: 'assistant', content: '莉娅站在城门旁，等你归还钥匙。' }],
}});
const reply = await turn.fast;    // reply.content 交给自己的聊天业务
const background = await turn.slow; // 检查 status 和 stateOutcome
```

该例使用 `run()`，返回完整快路结果。若前端需要流式正文，改用 `stream()` 并提供真实流式快路执行器。`validateStateProposal` 只能返回符合契约的候选或 `undefined`，不能直接返回任意数据库对象。产品级服务通常复用 runtime；服务关闭时调用 `shutdown()`，并处理尚未完成任务的结果。

模型适配器实现 `complete(request, signal)`，返回 `content`、`toolCalls`、模型身份和所需 usage；状态仓库提供 `get` / `compareAndSet`。仓库的完整示例连同假模型、工具适配与存储实现可以直接查看并运行，示例里的固定文本不代表真实模型质量。

[完整工具接线与快慢调用](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/07-fast-slow.ts#L18) · [上下文与状态仓库示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/support/runtime.ts#L5) · [通用 Agent 的 prepare 与 result 示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/01-agent.ts#L10)

## 主模型实际收到什么

`SlowTurnRuntime` 给主模型的消息依次是：你配置的系统提示词、已保存状态的 system 消息、传入的历史、本轮 user 输入；提供给模型的工具列表来自你传入的 `capabilities`。没有自动读取业务数据库、世界书或角色卡的步骤。

每次模型返回工具请求，程序解析参数、验证这一批请求，再调用已注册执行器。结果回到模型消息中，模型可以继续选择工具或输出最终内容。调用方必须解释 `final.content` 与 `toolResults`，不能把任意文字直接当作一份合格的业务状态。

`SimpleFastRuntime` 使用同一份起始状态、历史和输入调用快模型。它的默认 `stream()` 先等待 `complete()` 再输出一个完整块；要真正逐 token 交付，需要提供自己的流式快路执行器。

[快照、历史与本轮消息组装](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime-shared.ts#L154) · [主模型输入及工具回传](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/slow-runtime.ts#L82) · [默认快路的流式行为](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime.ts#L12)

## 结果何时保存

`SlowTurnRuntime` 可以只做分析。未配置 `prepareCommit` 时，不自动写状态；配置后由该回调检查模型结果，返回 `{state, toolsCalled?, ledgerEntries?}`，或返回 `undefined` 表示本次不改。

只有回调给出候选，才调用宿主的 `state.compareAndSet`，使用本轮读取的版本、来源消息和轮次做检查。版本冲突返回 `stale_write_rejected`。数据库、权限与原子检查由宿主实现，SDK 没有自带持久数据库。下一轮 `FastSlowRuntime` 才会重新读当时已保存的版本。

**例：**用户要求角色以后说话简洁，快路先承接本轮对话；慢路分析后返回风格偏好提议。宿主只允许白名单字段，通过才写入。下一轮重新取出这份偏好并作为输入，才可能影响说话方式。若没实现 `prepareCommit` 或没把保存值交回下一轮，分析不会自行变成长期能力。快路也不应在保存尚未确认时把“永久记住了”当成已完成事实。

默认 `prepareCommit` 只拿到慢路最终模型结果、工具结果和起始上下文，**没有本轮快路最终正文或“业务已接受正文”的标志**。若你的产品要求正文接受后才能改状态，应采用等待式 `SlowTurnRuntime`，先得到分析结果，再由业务接受边界显式提交；不能假设 `FastSlowRuntime` 已自动等快路完成。

[候选到 CAS 保存](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/slow-runtime.ts#L153) · [状态快照与写入字段](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/contracts.ts#L96)

<details>
<summary>预算、gate、取消和工作流接入参数</summary>

| 配置 | 源码默认或要求 | 影响 |
| --- | --- | --- |
| `limits.maxSteps` | 4 | 慢路模型步骤上限 |
| `limits.maxToolCalls` | 8 | 整轮工具调用上限 |
| `limits.maxParallelTools` | 4 | 一批允许的工具调用数量上限 |
| `limits.timeoutMs` | 180,000 | 慢路期限 |
| `limits.maxTotalTokens` / `maxCostUsd` | 可选 | 调用前检查已累计值，调用后检查本次 usage；缺少所需计量时失败。一次调用仍可能使累计超额，不能理解成供应商绝不超支的硬上限 |
| `gate(context, signal)` | `slow.gate` 可选；每次慢路 run 开始评估一次 | 返回 `{run:false}` 跳过慢模型循环、工具与提交；结果为 completed、保留状态、慢循环调用计数 0 |
| `prepareCommit` | 可选 | 解释结果与决定是否更新状态 |
| `cancel()` / 外部 signal | 显式取消 | 传播到两路与相关任务；快路失败本身不能当作慢路保存已取消的保证 |

gate 如果自身调用模型，该调用和成本要由宿主记录；慢循环计数为 0 不代表整项业务毫无开销。gate 抛错、取消或返回非法对象会进入失败路径。它没有自动接入普通 V2 应用，也不能当作“普通 V2 现在累计 X 轮才运行”的证据。该能力在本次主干中有实现，使用发布包前需核实具体版本是否包含。

`gate` 的合法结果必须有 boolean `run`，另可含 `reason`、0–1 的 `confidence` 与非负 `latencyMs`；跳过时没有 `final`，读取结果前需要判空。`FastSlowRuntime` 仍先读状态、再调度慢任务和快路；它不是让 gate 在快路之前统一决定本轮是否执行。

两路是同一进程中的异步任务，不是各自创建一个系统线程。快路失败、用户停止读取快路迭代器，不会自动等价于 `cancel()`；需要取消整轮时显式触发 `cancel` 或外部 signal。取消会终止等待并传递信号，但不能撤销已经发生的外部工具副作用或数据库提交；后端仍需配合取消和幂等。`drain()` 只等现有后台任务，`shutdown()` 发出取消再等；两者到等待期限都可返回，不能作为“所有外部写入一定停止”的证明。

后台任务是进程内执行，不是可跨进程恢复的持久任务队列。需要可靠工作流时，把 `SlowTurnRuntime.run` 作为可等待的步骤，持久化任务输入、结果与业务接受状态，再由外部工作流决定重试和提交。`status:'failed'` 是返回结果，不一定抛异常，工作流步骤应主动检查。

[预算与 gate 定义](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime-shared.ts#L47) · [默认限制](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime-shared.ts#L144) · [跳过与失败行为](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/slow-runtime.ts#L69) · [等待分析与业务接受后提交的完整示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/08-workflow-step.ts#L24)

[drain、shutdown 与共享取消](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/runtime.ts#L75) · [进程内异步任务](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/tasks.ts#L23) · [按模型响应计量预算](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/slow-runtime.ts#L275)

</details>

## 工具注册和 Hook 怎样接

注册表保存的是工具定义和执行器；不会扫描仓库把所有函数自动挂给模型。`AgenticCapabilityRegistry` 直接构造时参数校验默认关闭，设置 `validateArguments:true` 才按 Schema 强校验；`SlowTurnRuntime` 使用自己的受控注册与调用校验。通用 Pi Agent 则由 `options.initialState.tools` 明确提供工具。

Core Tools 基类的生命周期是 `validate → beforeExecute → 再次 validate → perform → afterExecute`。`afterExecute` 失败时底层副作用可能已经完成，不自动重跑。`afterTurn` 必须由宿主在整轮结束后显式调用，`execute` 不会自动触发它。

已有 Workflow 可以在一个节点直接 await 工具，也可以把 `SlowTurnRuntime.run` 作为一个节点；依赖执行顺序由该 Workflow 决定。仓内另有下面这套 Profile 固定流程，可以按业务规定的顺序串联工具。

[注册与参数校验开关](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/registry.ts#L32) · [工具生命周期](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/tool.ts#L3) · [Hook 接口](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/tools/src/hooks.ts#L1) · [完整通用 Agent 示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/01-agent.ts#L1) · [完整快慢协作示例](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/examples/sdk/07-fast-slow.ts#L1)

## 固定 Workflow：先查资料，再形成写作要求

**可复用形态是仓内程序组件，当前不是公共 npm SDK 导出。** `StaticPlanner` 读取业务定义的节点图，`CapabilityExecutor` 按依赖调用工具，`RPPromptCompiler` 把明确指定的文本结果整理到输入区域。模型不用先判断要不要查这份资料：只要走到这个流程，程序就按定义执行。

比如用户说“我递出铜钥匙”，你希望每次都先查询城门通行规则，再结合规则形成写作要求，最后交给正文模型。业务可以定义：**查规则 → 准备场面要求 → 组装模型输入 → 调用自己的正文模型**。这套组件负责前三步的节点执行和材料整理；模型调用、正文保存与接受规则仍由接入者或选用的应用运行时负责。

| 组件 | 接入位置 | 负责什么 |
| --- | --- | --- |
| `StaticPlanner.plan(request, profile)` | `apps/emochi/planning/static-planner.ts` | 校验请求与 Profile 对应、节点可见性、重复节点、依赖缺失及环；输出按依赖排序的 `ExecutionPlan` |
| `CapabilityExecutor.execute(plan, request, signal, emit, additionalCapabilities?, parallel?)` | `apps/emochi/capabilities/executor.ts` | 调用已注册的节点执行器，传递本轮请求、节点参数和已完成输出；返回 `{outputs, traces}` |
| `RPPromptCompiler.compile({axon, memory, plan, capabilities})` | `apps/emochi/prompt/compiler.ts` | 将基础材料、记忆与节点的 `promptContent` 组成 `blocks`，并生成 `foundation_context` / `turn_context` 两个文本区域 |
| `McpCapability` | `apps/emochi/capabilities/mcp-capability.ts` | 把宿主提供的 `MCPAdapter.invoke` 包装成一个 Capability；提供工具名、节点参数、本轮 `query` 和已完成的 `dependencies`，再由回调提取提示文本/资产 |

`McpCapability` 没有默认 MCP 服务地址、账号或网络客户端。接入者实现适配器并绑定允许使用的服务；如果只是调用本地函数，直接实现下面示例的 `Capability.execute` 即可。

[固定图的校验与排序](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/planning/static-planner.ts#L4) · [执行入口](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/capabilities/executor.ts#L18) · [编译结果](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/prompt/compiler.ts#L33) · [外部工具包装](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/capabilities/mcp-capability.ts#L4)

### Profile 和节点里分别填什么

| 字段 | 作用与条件 |
| --- | --- |
| `profile.id` | 本次流程标识，必须等于 `request.scenarioId` |
| `modelConfigRef` / `presetRef` | 完整 `ScenarioProfile` 的模型/提示词引用；单独规划及执行节点时不会读取这两个配置，也不会调用模型 |
| `visibleCapabilities` | 此 Profile 允许节点引用的能力名称；还必须为名称注册实际执行器，名字本身不会加载代码 |
| `executionGraph.nodes` | 业务定义的固定节点列表；程序根据 `dependsOn` 排序 |
| 节点 `id` / `capability` | `id` 是本流程内唯一节点 ID，供依赖与结果定位；`capability` 是执行器的注册名称，可以多次调用同一能力但要使用不同节点 ID |
| 节点 `input` | 可选参数对象，原样交给执行器；这条执行器不会自动按 JSON Schema 校验它，应在能力内部校验 |
| 节点 `dependsOn` | 必须先完成的节点 ID 列表；依赖失败时怎样处理见下表 |
| 节点 `required` | 该工具抛错时是否把流程判为必需步骤失败；不能代替输入、权限和存储校验 |
| 节点 `projectTo` | `foundation_context`、`turn_context` 或 `none`；只决定该节点明确返回的 `promptContent` 放在哪个编译区域 |
| `defaultTimeoutMs` / `failurePolicy` | 完整 `TurnHarness.run` 采用的轮次默认超时及记忆读取/写入失败策略；单独调用 Planner/Executor 不会自动读取它们来设定时限或保存记忆 |

`failurePolicy` 的实际字段为 `memoryRecall: "fail_turn" | "continue_without_memory"`、`memoryIngest: "fail_turn" | "best_effort"`。这是记忆步骤的策略；工具节点失败由 `required` 处理。能力元数据中的 `readOnly`、`idempotent` 等描述性质，执行器不会据此自动撤销写入或保证幂等。

[能力、节点与 Profile 字段](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/packages/sdk/src/domain/contracts.ts#L91) · [Capability 调用与返回契约](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/ports.ts#L131) · [完整 Harness 的超时与记忆策略](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/harness.ts#L145)

### 最小可运行示例：看到工具结果进入最终 messages

下面两个工具是**示例里由接入者新定义的工具**，不冒充仓库自带的世界书 API。它们使用内存资料演示接口；换成真实世界书或其他服务时，在各自 `execute` 内完成调用和校验。

在已安装依赖并完成 `npm run build` 的源码仓库根目录，将此段保存为 `workflow-demo.mjs`，运行 `node workflow-demo.mjs`。使用的是仓内构建产物路径，不是公共 SDK 的导入路径：

```js
import { StaticPlanner } from './dist/apps/emochi/planning/static-planner.js';
import { CapabilityExecutor } from './dist/apps/emochi/capabilities/executor.js';
import { RPPromptCompiler } from './dist/apps/emochi/prompt/compiler.js';

const request = {
  requestId: 'workflow-1', scenarioId: 'gate-workflow',
  actorId: 'guard', input: '我递出铜钥匙。', stateVersion: 0,
};
const profile = {
  id: 'gate-workflow', modelConfigRef: 'actor-config', presetRef: 'actor-preset',
  defaultTimeoutMs: 30000,
  failurePolicy: { memoryRecall: 'fail_turn', memoryIngest: 'best_effort' },
  visibleCapabilities: ['read_gate_rule', 'prepare_scene'],
  executionGraph: { nodes: [
    { id: 'rule', capability: 'read_gate_rule', dependsOn: [],
      required: true, projectTo: 'none', input: { location: '城门' } },
    { id: 'scene', capability: 'prepare_scene', dependsOn: ['rule'],
      required: true, projectTo: 'turn_context' },
  ] },
};
const metadata = name => ({ name, readOnly: true, idempotent: true,
  longRunning: false, streaming: false, cancellable: true });
const tools = [
  {
    metadata: metadata('read_gate_rule'),
    async execute({ input }, signal) {
      signal.throwIfAborted();
      if (input.location !== '城门') throw new Error('未知地点');
      return { value: { rule: '进入禁区需要手令；铜钥匙不能替代手令。' } };
    },
  },
  {
    metadata: metadata('prepare_scene'),
    async execute({ dependencyOutputs }, signal) {
      signal.throwIfAborted();
      const rule = dependencyOutputs.get('rule')?.value?.rule;
      if (typeof rule !== 'string') throw new Error('缺少已查到的规则');
      const instruction = `${rule} 守卫应检查手令，不把递出钥匙写成已经获准通行。`;
      return { value: { instruction }, promptContent: instruction };
    },
  },
];
const plan = new StaticPlanner().plan(request, profile);
const capabilities = await new CapabilityExecutor(tools).execute(
  plan, request, AbortSignal.timeout(30000), () => {},
);
const compiled = new RPPromptCompiler().compile({
  axon: { history: [], foundationBlocks: [] },
  memory: { version: '0', items: [] }, plan, capabilities,
});
const messages = [
  { role: 'system', content: '你扮演城门守卫，承接用户动作。' },
  ...compiled.blocks.map(({ role, content }) => ({ role, content })),
  { role: 'user', content: request.input },
];
console.log(JSON.stringify({ outputs: capabilities.outputs, messages }, null, 2));
// 自己的模型适配器接收 messages 后，材料才真正参与生成。
```

执行后，`outputs` 中有原始规则对象与场面要求对象；`messages` 是“角色岗位 → 场面要求（内含手令规则）→ 用户输入”。`rule` 节点设为 `none`，所以它不会被重复直接注入；`scene` 节点显式返回 `promptContent`，才产生模型可读的文本块。这段示例不调用真实模型，也不写数据库。

### 执行结果、失败及进入模型的边界

| 情况 | 实际行为 | 二开时需要做什么 |
| --- | --- | --- |
| 默认执行 | `parallel` 默认为 `false`，按排好序的节点逐个执行 | 有独立慢查询时，可显式传 `true`；声明依赖的节点仍等待前置节点 |
| 节点读取前序结果 | `dependencyOutputs` 是以节点 ID 为键的已完成输出 Map，并不限于自身 `dependsOn` 中的节点 | 只读取声明过的依赖 ID；并行时不要依赖另一个无依赖关系节点碰巧先完成 |
| `required:true` 节点失败 | 串行立即失败，后面尚未开始的节点不运行；并行时阻断它的依赖节点，其他已启动的独立节点仍可能完成，最终执行抛错 | 业务明确哪些步骤不可缺；并行失败不等于所有外部副作用已取消 |
| `required:false` 节点失败 | 记录失败 trace，没有该节点输出；依赖它的节点仍可继续 | 下游明确处理“缺少结果”，不能直接读取不存在字段 |
| 工具没有注册、依赖无效 | 返回/抛出执行或规划错误；未注册工具不会因 `required:false` 自动变成可忽略步骤 | 配对校验 Profile 和执行器注册表 |
| 执行完成 | `outputs` 含 `nodeId/capability/value`，可有 `promptContent/artifacts`；`traces` 含耗时、状态和失败信息 | 单独使用执行器时由业务保存/展示；它不会持久化任务、自动重试或合并长期状态 |
| 取消/超时 | 执行器传递并检查传入的 `signal`；正在运行的外部调用也必须配合信号 | 单独使用时显式设置请求期限；取消不能撤销已经写出的数据 |
| 整理成 Prompt | 仅非 `none` 且有 `promptContent` 的输出会进入编译块，role 为 `system`；`value`、`artifacts` 不自动转成 Prompt | 验证/筛选工具文本后再投影；还要由下游模板或消息适配器真正采用，完成计数再发给模型 |

编译器按区域、priority 和来源等字段排序；它不是直接把所有结果按工具完成先后追加在末尾。`TurnHarness` 负责把 `compiledContext` 交给所选正文运行时，**具体运行时仍必须使用它**。例如 ChatKernel 接法写入 `harnessFoundationContext` / `harnessTurnContext` 模板变量；当前闭环 Actor 主要发送 `axon.history` 中已准备好的消息，不能仅把工具结果放进 `compiledContext` 就假定已经注入了这个 Actor。上面的示例显式构造 `messages`，正是为了让采用动作可见。

当前 Agent Router 的普通入口与闭环入口会构造空的 `visibleCapabilities` 和 `executionGraph.nodes`。所以这项能力的交付方式是**在仓内二开组装 Profile、执行器和模型材料接法**，不是往现有 Scenario JSON 随意加一个 `executionGraph` 就能加载任意工具。通用 SDK 的工具名单、这个固定节点图、普通 V2 的后台专家名单，是三种不同接线。

[依赖、并行及失败](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/capabilities/executor.ts#L42) · [只投影明确的提示文本](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/prompt/compiler.ts#L35) · [模板采用编译区域](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/integrations/chat-kernel-runtime.ts#L111) · [闭环 Actor 的实际消息来源](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/apps/emochi/actor/roleplay-runtime.ts#L732) · [闭环入口构造的空节点图](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L731) · [普通入口构造的空节点图](https://github.com/FlowGPT/roleplay-harness/blob/fa41d220d48327e58d994f936517b23f2f15f658/adapters/agent-router/dispatch.ts#L1325)

## 观测与接入验证

可以接入 `HarnessTelemetry`、`observeModel` 和 `/langfuse` 子入口，记录一次调用的模型、工具、状态步骤；`createAgentBuildMetadata` 描述构建和配置身份。观测用于核对“实际执行了什么”，不代替业务接受或存储成功的检查。

先用仓库公开示例中的假模型和假存储跑通：模型请求工具 → 执行器收到正确参数 → 结果回到模型 → 宿主校验并采用 → CAS 写入 → 下一轮读回；再替换真实后端。安装包不会自动获得 Axon、Kaon 或其他业务凭证。
