Initial commit

This commit is contained in:
2026-07-10 08:31:27 +08:00
commit 2b74c30d36
134 changed files with 21801 additions and 0 deletions

150
docs/architecture.md Normal file
View File

@@ -0,0 +1,150 @@
# 整体架构
## 1. 方向
**标签驱动黑板** + **agent tool loop** + **5 相位阶段机** + **skill 能力库**
```text
Skill 包orchestrator manifest + workers/*/SKILL.md 静态能力定义
Session + 黑板 tag 一次运行的实参
Book 跨 Session 的项目与资产
Agent总管 running 相位内 tool loop调度 invoke 哪个 skill
Runtime 拼接上下文、校验 tool、写黑板、驱动阶段机边界
阶段机 谁可动、何时等用户、产物生命周期——不是流水线剧本
```
核心文档:
```text
docs/tag-blackboard.md 黑板、标签、分工
docs/context-assembly.md ★ 上下文拼接(上半固定、下半动态)
docs/tool-contracts.md 总管 / worker tool
docs/runtime-state-machine.md 5 相位、tool 边界
docs/book-storage.md 长期存储(过程 / 资产 / 游玩)
docs/skill-design-guide.md 新 skill 包设计方法
docs/implementation-guide.md 代码文件职责
```
---
## 2. 术语
| 词 | 含义 |
|----|------|
| **skill** | `SKILL.md` 定义的能力(设计期能力库中的一条) |
| **worker** | 某 skill 被 invoke 的一次执行 |
| **stage** | 业务阶段:`design`(实例化)→ `play`(运行)→ `done` |
| **phase** | 运行相位:`idle` \| `running` \| `waiting_user` \| `done` \| `error` |
不使用 **step** 指代设计步骤编号,避免与管道混淆。
---
## 3. Agent tool loop
```text
用户硬事件(输入 / 确认 / 验收)
→ phase = runningtoolLoopBurst 计数归零
→ while running && burst < N:
LLM(messages, tools)
→ 循环 toolread_blackboard …)→ 结果 append 到 messages
→ 边界 toolrun_worker / ask_user / finish→ 退出 burst
→ 若需用户 → waiting_user
```
- **burst 上限 N**:两次用户操作之间的最大推理轮数,非 Session 终身额度。
- **循环 tool**:不改 phase只追加 agent 对话。
- **边界 tool**:触发阶段机转移(等用户、跑 worker、结束
代码:`src/main-agent/tool-loop.ts``src/runtime/phase-runtime.ts`
目标态worker 执行也用 tool loop`submit` / `ask_user`),上下文仍由 Runtime 拼接,见 `docs/tool-contracts.md`
---
## 4. 阶段机做什么
阶段机 **不是** 编排表执行器,而是:
```text
1. Actor 门禁 此刻用户 / agent / worker 谁在场
2. Tool 边界 当前态允许哪些 tool
3. 事实生命周期 draft → accepted用户才能 accept
4. 等待原因 waitingReason 细分等什么
```
业务「先跑哪个 skill」由 **agent 在 tool loop 里决定**,不由 orchestrator 逐步剧本写死。
---
## 5. 实例化skill 能力库
实例化 = agent 在 `design` stage 按需 invoke **instantiate skill**(原「设计步骤」),不是固定 1→14 管道。
```text
交互范式 skill → 产出 设计.run_skill清单run 阶段需要哪些 skill
每个 run skill 倒推 → 缺什么 instantiate skill → agent invoke
declare_instance_ready → 进入 play stage
```
orchestrator.md = **manifest**(有哪些 skill、约束、验收策略不是逐步思维链。
---
## 6. 上下文拼接
**上半固定、下半动态**。规则在 skill 定义 + 实例 contextProfileRuntime 执行。
`docs/context-assembly.md`
---
## 7. 模块
```text
phase-machine.ts 纯函数event → phase边界规则
phase-runtime.ts Session IO、tool loop 驱动、worker 执行
main-agent/ tool loop、tool 定义
worker/executor.ts assembleWorkerContext、run skill
blackboard.ts tag 池
skills/loader.ts 解析 orchestrator + SKILL.md
book/ Book、快照、持久化
```
---
## 8. 数据流(目标态)
```text
选 orchestrator 包
→ designagent burst → invoke instantiate skills → 设计.* tag
→ declare ready → play
→ play用户输入 → agent burst → invoke run skills → 运行.* tag
→ 过程/资产/游玩 归档 Book见 book-storage.md
```
---
## 9. 边界
```text
Agent 调度 skillread_blackboard不传 inputTags不写黑板边界 tool 除外)
Runtime 拼接上下文;校验 tool写黑板执行 worker
Worker LLM 在拼接后的 prompt 内产出submit 写 declared outputTags
用户 approve、accept、输入
阶段机 只响应 event不调 LLM
```
---
## 10. 实现进度(摘要)
```text
✅ phase-machine、phase-runtime、skills loader
✅ 总管 tool loopread_blackboard、run_worker、…
⬜ toolLoopBurst 按用户事件归零
⬜ assembleWorkerContextcontextSegments
⬜ worker tool loop
⬜ BookdesignTrace / CardAsset / PlayBook
⬜ orchestrator 从编排表迁为 manifest
```

246
docs/book-storage.md Normal file
View File

@@ -0,0 +1,246 @@
# Book 存储模型
## 1. 定位
Book = **长期项目容器**。Session = 一次打开的运行进程。黑板 = Session 内运行时 tag。
存储须同时满足:
```text
1. 保存「创建过程」designTrace / playTrace
2. 保存「创建结果」(可复用资产,如角色卡)
3. 保存「游玩过程」(对话、轮次、变量、存档点)
```
第一版约束:不存 API key历史与当前分离accepted 才升为「当前事实」。
---
## 2. 三种 Book 形态
| 形态 | 用途 | 典型 kind |
|------|------|-----------|
| **CardBook** | 创作角色卡design stage | `character_card_design` |
| **CardAsset** | 从 CardBook 导出的可复用卡 | 资产条目,可挂卡库 |
| **PlayBook** | 用某张卡(+ 可选世界)游玩 | `character_card_play` / `roleplay` |
| **ProjectBook** | 小说等项目(沿用) | `novel` |
同一用户可CardBook 创作 → 导出 CardAsset → 新建 PlayBook 引用该卡。
---
## 3. CardBook创建角色卡
### 3.1 存什么
```ts
type CardBook = {
id: string;
title: string;
orchestratorId: string;
lifecycleStage: "design" | "archived";
/** 创作过程:可时间线展示、可续作设计 */
designTrace: DesignTraceEntry[];
/** 运行时黑板快照(设计.* tag */
designArtifacts: TaggedArtifact[];
/** 用户可见对话agent tool 对话可另存 agentTrace */
messages: PersistedChatMessage[];
runSkillManifest?: RunSkillManifest; // 设计完成后:建议如何游玩
readiness?: InstanceReadiness;
createdAt: string;
updatedAt: string;
};
```
```ts
type DesignTraceEntry = {
at: string;
type:
| "user_input"
| "agent_tool"
| "skill_invoked"
| "user_confirmed"
| "user_rejected"
| "declare_ready";
skillId?: string;
tool?: string;
summary: string;
outputTags?: string[];
};
```
**过程** = `designTrace` + `messages`+ 可选 `agentTrace`)。
**不是**预定实体槽填表agent 调了哪些 instantiate skill过程里就记哪些。
### 3.2 导出 CardAsset创建结果
```ts
type CardAsset = {
id: string;
cardBookId: string;
title: string;
tags: Record<string, string>; // 角色卡.确认稿、角色.设定、口吻…
runSkillManifest?: RunSkillManifest;
confirmedAt: string;
};
```
游玩时 **加载 CardAsset**,不必重跑完整 design。
---
## 4. PlayBook游玩角色卡 / 扮演)
```ts
type PlayBook = {
id: string;
title: string;
orchestratorId: string;
lifecycleStage: "play" | "archived";
cardRef: { assetId: string; version?: string };
worldRef?: { assetId?: string };
contextProfile: ContextProfile; // 实例化选的 variant
runSkillManifest: RunSkillManifest;
playTrace: PlayTraceEntry[];
runState: {
turn: number;
variables: Record<string, unknown>;
eventStream: string;
};
messages: PersistedChatMessage[];
snapshotIds: string[];
createdAt: string;
updatedAt: string;
};
```
```ts
type PlayTraceEntry = {
at: string;
turn?: number;
type: "user_input" | "skill_invoked" | "artifact" | "snapshot";
skillId?: string;
summary: string;
};
```
**游玩过程** = `playTrace` + `messages` + `runState`
**游玩存档** = `RunSnapshot`kind=`run`),见 `run-snapshot.md`
---
## 5. ProjectBook小说等
保留原 `BookProject` + `BookContentRecord` 思路:
```ts
type BookProject = {
id: string;
title: string;
orchestratorId: string;
kind: "novel" | "forum" | "weird_rules" | "custom";
lifecycleStage: "design" | "play" | "done";
currentContentId?: string;
historyContentIds: string[];
designTrace?: DesignTraceEntry[];
sessionIds: string[];
createdAt: string;
updatedAt: string;
};
```
小说正文仍用 `BookContentRecord`outline/chapterdesign 阶段 tag 可进 `designArtifacts`
---
## 6. Session 与 Book
```text
SessionPersistedBookSession
当前打开的 working copyRuntimeSession + 黑板 + messages
关闭时可合并进 Book 的 trace / runState
Book
稳定事实 + 过程记录 + 资产引用
沉淀规则
design accepted tag → CardBook.designArtifacts / 导出 CardAsset
play accepted tag → PlayBook.runState + 黑板归档
draft / rejected → trace 记一笔,默认不升 current
```
---
## 7. 与状态机
```text
worker_completed → draft
user_accepted_artifact / no_confirmation / programmatic_pass
→ 可写 Book由 Runtime非 agent 直接写)
```
---
## 8. 与黑板
| 阶段 | 黑板前缀示例 | 归档到 |
|------|--------------|--------|
| design | `设计.*``用户.需求` | CardBook / ProjectBook design |
| play | `运行.*``世界.*``输出.*` | PlayBook runState |
上下文拼接读黑板Book 存 **确认稿与过程**,见 `context-assembly.md`
---
## 9. 存储布局(建议)
```text
books/{bookId}/project.json
books/{bookId}/design-artifacts.json
books/{bookId}/traces.jsonl
books/{bookId}/contents/{contentId}.json # 小说正文
assets/cards/{assetId}.json # CardAsset 卡库
books/{bookId}/snapshots/{snapshotId}.json
sessions/{sessionId}.json # 可选 working copy
```
---
## 10. 实现优先级
```text
P0 文档对齐(本文)
P1 designTrace / playTrace 写入
P2 CardAsset 导出与 PlayBook.cardRef
P3 与现有 PersistedBookSession / RunSnapshot 字段合并
P4 卡库 UI、跨 Book 引用
```
代码现状:`src/types/book.ts``run-snapshot.ts` 仍为简化模型,实现时按本文扩展。
---
## 11. 角色卡流程示例
```text
1. 新建 CardBook → design stage
2. 用户与 agent 迭代 → designTrace 追加
3. invoke persona-* skills → 设计.* tag
4. 用户确认 → 导出 CardAsset
5. 新建 PlayBookcardRef = assetId
6. play stage每轮 playTrace + messages
7. 手动 PlaySnapshot 存档
8. 下次打开 PlayBook 或读 snapshot 续玩
```
创建过程、卡本身、游玩过程 **三者分开存**,互不覆盖。

189
docs/context-assembly.md Normal file
View File

@@ -0,0 +1,189 @@
# 上下文拼接
## 1. 定位
Worker / skill 执行时Runtime 将黑板 tag 与固定体裁说明拼成 LLM prompt。
**拼接规则在 skill 定义期定制;拼接执行由 Runtime 机械完成agent 不临场改 inputTags。**
详见 `docs/worker-skill-format.md`(字段)、`docs/tag-blackboard.md`(标签原则)。
---
## 2. 上半固定、下半动态
每条 worker prompt 分为两段:
```text
┌─ 上半固定上下文Static────────────────────────┐
│ shared-context.md包级体裁约束
│ worker SKILL.md 正文(能力说明、自检) │
│ contextSegments 中 tier=static 的 tag │
│ 例:角色卡.确认稿、世界.蓝图、设计.交互范式 │
└────────────────────────────────────────────────────┘
┌─ 下半动态上下文Dynamic────────────────────────┐
│ contextSegments 中 tier=dynamic 的 tag │
│ 例:运行.事件流、可见信息、用户.最新输入 │
│ 按 policy 裁剪tail_lines、tail_tokens、concat
└────────────────────────────────────────────────────┘
```
原则:
```text
越稳定、越少改 → 越靠上static
越增量、每轮变 → 越靠下dynamic
```
**不是** agent 在游玩时自由往 prompt 里插段落agent 只决定 **invoke 哪个 skill**;该 skill 的契约决定看见什么。
---
## 3. 三分工
| 谁 | 管什么 | 何时定 |
|----|--------|--------|
| **Skill 定义**`workers/*/SKILL.md` | inputTags、contextSegments、tier、policy、隔离 | 写 skill 时 |
| **实例 manifest** | 启用哪些 run skill、`contextProfile` 选哪档 variant | 实例化时 agent 产出 |
| **Agent** | 何时 invoke 哪个 skill可用 read_blackboard 辅助决策 | 运行中 |
| **Runtime** | `assembleWorkerContext()` 唯一拼接点 | 每次 invoke |
---
## 4. contextSegmentsWorker Skill 字段)
`SKILL.md` frontmatter 声明(`inputTags` 仍保留,作为取数白名单):
```yaml
contextSegments:
- id: persona
tier: static
tags: ["角色卡.确认稿"]
label: "## 角色设定"
- id: world
tier: static
tags: ["世界.蓝图"]
label: "## 世界"
- id: history
tier: dynamic
tags: ["运行.事件流"]
policy: tail_lines_80
- id: turn
tier: dynamic
tags: ["可见信息", "用户.最新输入"]
label: "## 本轮"
```
| 字段 | 含义 |
|------|------|
| `tier` | `static`(上半)或 `dynamic`(下半) |
| `tags` | 从黑板取的 pattern须在 `inputTags` 内 |
| `label` | 拼进 prompt 的 Markdown 标题(可选) |
| `policy` | 动态段裁剪,见 §5 |
未声明 `contextSegments`Runtime 回退:按 `inputTags` 顺序输出 JSON `inputs`(当前实现)。
---
## 5. 动态段裁剪 policy
| policy | 行为 |
|--------|------|
| `latest` | 每 pattern 取最新一条(默认) |
| `concat` | 同 pattern 多条合并 |
| `tail_lines_N` | 事件流等取最后 N 行 |
| `tail_tokens_N` | 按估算 token 截断(预留) |
上下文过长时:
1. 优先靠 policy 裁剪动态段
2. 实例 manifest 可覆盖 variant`historyPolicy: last_10_turns`
3. agent 可 invoke 显式 **compress-history** skill 写摘要 tag调度 skill不是随手删 prompt
---
## 6. contextProfile实例 manifest
实例化阶段产出(写入 `设计.run_skill清单` 或 Book manifestagent **只选预置档位**,不列 tag
```json
{
"runSkills": ["world-simulator", "narrator"],
"contextProfile": {
"narrator": { "variant": "card_rp", "historyPolicy": "last_15_turns" },
"world-simulator": { "variant": "light_rules" }
}
}
```
`variant` 在 skill 包内预定义多组 `contextSegments` 覆盖或 policy 差异。
例:`narrator``card_rp` 强制 static 含 `角色卡.确认稿``short_emotion_flow` 缩短 dynamic 历史。
---
## 7. 隔离
`inputTags` 正交,由 skill 声明 `contextIsolation`
```yaml
contextIsolation: none | role_pov | blind_review
```
- `role_pov`role-decide 等Runtime 调用 `filterInputsForRolePerspective`
- `blind_review`review 不可见指定 tag`核心.危险.隐藏`
隔离在 **取数之后、拼接之前** 应用。
---
## 8. 与 agent tool loop 的边界
两套上下文 **不得混用**
| | Agent tool loop `messages[]` | Worker prompt |
|--|------------------------------|---------------|
| 用途 | 总管推理、选 skill | 具体 skill 执行 |
| 内容 | tool 结果、read_blackboard | assembleWorkerContext 输出 |
| 增长 | 两次用户操作之间的 burst 内累积 | 每次 invoke 按契约重建 |
总管 `read_blackboard` **不注入** worker prompt只帮助 agent 决定下一个 `run_worker`
---
## 9. 拼接结果形态(目标)
```text
system:
{shared-context}
{worker SKILL body}
{固定输出协议}
user:
{按 segment 顺序格式化的 Markdown 或结构化块}
```
实现:`src/worker/executor.ts``assembleWorkerContext()`(待从纯 JSON inputs 升级)。
---
## 10. 设计 checklist
```text
□ 列出 static / dynamic 各需要哪些 tag
□ static 写入 contextSegments tier=static
□ dynamic 写入 tier=dynamic 并选 policy
□ inputTags 覆盖 segments 中全部 pattern
□ 画隔离表:谁不可见哪些 tag
□ 若有多游玩模式,在包内预置 contextProfile variant
□ 实例化 manifest 只选 variant不临场改 tag 列表
```
---
## 11. 相关文档
| 文档 | 关系 |
|------|------|
| `worker-skill-format.md` | frontmatter 字段定义 |
| `skill-design-guide.md` | 如何倒推 tag 与 skill 能力 |
| `tag-blackboard.md` | 标签命名与黑板 |
| `tool-contracts.md` | agent 不得传 inputTags |

83
docs/creation-playbook.md Normal file
View File

@@ -0,0 +1,83 @@
# 创作流程指南Creation Playbook
## 0. 内容在哪
**流程定义在 skill 包里**orchestrator manifest + `workers/*/SKILL.md`)。本文只保留概念。
写新包 → `skill-design-guide.md` → 新建 `skills/.../orchestrator.md`
---
## 1. 定位
```text
Orchestrator 包 能力库 + manifest静态
黑板 tag 一次 Session 的实参(动态)
Book 跨 Session过程、资产、游玩
```
| 层 | 管什么 |
|----|--------|
| **运行相位** | `idle` / `running` / `waiting_user` — 系统在等什么 |
| **业务 stage** | `design`(实例化)→ `play`(运行)→ `done` |
| **Agent** | tool loop 内 invoke 哪个 skill |
| **Skill** | 读哪些 tag、写哪些 tag、上下文怎么拼 |
| **Book** | 长期存储,见 `book-storage.md` |
---
## 2. 启动
```text
skill_selection 选 orchestrator 包
intake 启动询问(最小信息)
design stage agent 按需 invoke instantiate skill
declare ready 进入 play
play stage 用户输入 → agent burst → run skill
done 归档 Book
```
`startupCompleted` / `instanceReady`agent 声明 + 程序校验「当前 run_skill清单 可运行」,非固定 prerequisite 打勾。
---
## 3. Agent 与 Runtime
| | Agent | Runtime |
|--|-------|---------|
| 决定 | invoke 哪个 skill、何时 ask_user/finish | — |
| 拼接上下文 | 只用 read_blackboard 辅助决策 | assembleWorkerContext |
| 写黑板 | 否(边界 tool 驱动 worker 写) | 校验 outputTags 后写入 |
Agent **不指定 inputTags**。见 `context-assembly.md`
---
## 4. Tool loop burst
每次用户硬事件后agent 进入 `running`,在 **burst 上限内** 多轮 tool碰到边界 tool 或需用户则停。
`tool-contracts.md``runtime-state-machine.md`
---
## 5. 角色卡(一种 Book 形态)
```text
创建过程 designTrace + 对话 → CardBookdesign
创建结果 角色卡.确认稿 等 → CardAsset可导入资产
游玩过程 playTrace + runState → PlayBook
游玩存档 PlaySnapshot
```
不拆独立 author/play 包;见 `book-storage.md` §角色卡。
---
## 6. 相关文档
| 文档 | 关系 |
|------|------|
| `architecture.md` | 总览 |
| `skill-design-guide.md` | 设计方法 |
| `context-assembly.md` | 上下文拼接 |
| `tag-blackboard.md` | 标签 |

View File

@@ -0,0 +1,354 @@
# 实现指南
## 1. 写代码的规则
```text
1. 先读文档,再写对应文件。
2. 一次只写一个文件(或一组强绑定的 types + 实现)。
3. 每写完一个文件,对照本文档的「文件职责」自检。
4. 文件行为与文档冲突时,先改文档,再改代码。
5. 不要跳步实现:下游文件不能先于上游文件。
```
文档是规格,代码是文档的实现。
---
## 2. 文档地图
| 文档 | 管什么 |
|---|---|
| `tag-blackboard.md` | **★ 主规格**标签黑板、skill 分工 |
| `context-assembly.md` | **★ 上下文拼接**:上半固定、下半动态 |
| `architecture.md` | 总览、agent tool loop、模块边界 |
| `runtime-state-machine.md` | 5 相位、tool 边界、burst |
| `tool-contracts.md` | 总管 / worker tool |
| `orchestrator-skill-format.md` | manifest 写法(非编排表) |
| `worker-skill-format.md` | SKILL.md、contextSegments |
| `skill-format.md` | 包存储、registry |
| `skill-design-guide.md` | 新包设计方法 |
| `creation-playbook.md` | 创作流程概念 |
| `book-storage.md` | Book、CardAsset、PlayBook、过程存储 |
| `run-snapshot.md` | 手动存档 |
| `preset-format.md` | 预设导入 |
| `implementation-guide.md` | 本文件:文件顺序与职责 |
---
## 3. 实现分期
### Phase A0 — 可运行阶段机(当前优先)
目标:**不依赖 LLM**,阶段机整体可运行、可手动驱动、可脚本跑通最小闭环。
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| A06 | `src/types/runtime.ts` | done | 5 相位、事件、会话类型 |
| A09 | `src/runtime/phase-machine.ts` | done | 纯函数:`applyEvent`,不调 LLM |
| A20 | `src/runtime/phase-runtime.ts` | done | 运行层:处理 effectsstub worker |
| A21 | `src/cli/phase-demo.ts` | done | 交互式演示 CLI手动发事件 |
| A22 | `tests/phase-runtime.test.ts` | done | 闭环 + worker 中途提问测试 |
**运行方式:**
```bash
npm run phase-script # 非交互,自动跑最小闭环
npm run phase-demo # 交互,手动 /decide /approve /worker-ask
npm run phase-demo -- --auto # worker 自动占位完成
```
### Phase A1 — 总管 LLM 接入(阶段机之上)
目标:在 PhaseRuntime 之上接 Main Agent不改动 phase-machine 规则。
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| A11A19 | blackboard、llm、main-agent、orchestrator、run.ts | done | 带 Mock/真实 LLM 的完整栈 |
| A15 | `src/main-agent/prompts.ts` | todo | prompt 拆分 |
### Phase A2 — Skill 层(创作指南)
目标:`skills/` 批量存储 SKILL.md启动第一个询问是选 skill总管读 activeSkill。
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| S00 | `skills/registry.yaml` | done | skill 索引 |
| S01 | `skills/novel-standard/SKILL.md` | done | 小说指南(含启动询问) |
| S02 | `skills/theater-roleplay/SKILL.md` | done | 剧场指南(含启动询问) |
| S03 | `src/skills/types.ts` | todo | ParsedSkill、SkillIndexEntry |
| S04 | `src/skills/loader.ts` | todo | 解析 SKILL.md |
| S05 | `src/skills/registry.ts` | todo | listSkills() |
| S06 | `src/skills/resolver.ts` | todo | 推断当前 stage |
| S07 | `src/main-agent/skill-context.ts` | todo | 注入总管 prompt |
| S08 | `src/runtime/phase-machine.ts` | todo | 增加 skill_selection / skill_selected |
| S09 | `src/cli/phase-demo.ts` | todo | 启动时 /select-skill |
详见 `docs/skill-format.md` §5。
### Phase B — Tool Call 化
目标:总管 / worker 从 JSON 决策改为 tool callRuntime 做 tool 校验与事件转换。
### Phase C — 真实 Worker
目标outline / drafting 等固定 worker 接真实 LLMworker 可中途 ask_user。
### Phase D — 持久化
目标book 存储preset 导入。
### Phase E — 标签黑板迁移(当前文档已完成,代码待做)
目标:代码与 `tag-blackboard.md` 对齐。
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| E01 | `src/types/blackboard.ts` | done | `BlackboardItem`、按 tag 查询 |
| E02 | `src/blackboard/blackboard.ts` | done | listTagIndex、前缀匹配 |
| E03 | `src/skills/types.ts` + `loader.ts` | done | 解析 inputTags / outputTags兼容 outputKeys |
| E04 | `src/worker/executor.ts` | done | 按 tag 取 context校验 outputTags |
| E05 | `src/types/runtime.ts` + `main-agent.ts` + `phase-runtime` | done | 决策去掉 keysruntime 按 Worker Skill 执行 |
| E06 | `skills/novel/weird-rules-short/` | todo | tag 化 skill 样板 + inputTags frontmatter |
| E07 | `skills/novel/quick-write/` | todo | 简易全量 LLM skill |
编排写在各包 `orchestrator.md`,不单独维护 execution-flow YAML。
---
## 4. Phase A 文件顺序
按序号逐个实现。状态列:`done` = 已有初版,`todo` = 未写或未按文档对齐。
### 4.1 项目配置
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| A01 | `package.json` | done | 项目元数据、npm scriptsdev / test / demo |
| A02 | `tsconfig.json` | done | TypeScript 编译选项 |
| A03 | `vitest.config.ts` | done | 测试入口配置 |
| A04 | `.env.example` | done | LLM API 环境变量模板,不含真实 key |
| A05 | `.gitignore` | done | 忽略 node_modules、dist、.env |
**A01 职责:** 声明依赖typescript、tsx、vitest和三条命令。不含业务逻辑。
---
### 4.2 类型层(只放数据结构,不含逻辑)
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A06 | `src/types/runtime.ts` | done | 相位、事件、会话、产物、ResumeContext | `runtime-state-machine.md` §25 |
| A07 | `src/types/blackboard.ts` | done | BlackboardItem、TagIndex | `tag-blackboard.md` §2 |
| A08 | `src/types/tools.ts` | todo | 总管 tool、worker tool 的参数与结果类型 | `tool-contracts.md` |
**A06 职责:**
- 定义 `RuntimePhase`5 种)
- 定义 `WaitingReason`waiting_user 的子原因)
- 定义 `RuntimeEvent`(唯一改变相位的输入)
- 定义 `RuntimeSession`(运行时快照)
- 定义 `PhaseEffect`(阶段机产生的副作用,如 invoke_main_agent
**A07 职责Phase E 迁移后):**
- 定义 `BlackboardItem`id、tag、content、source、metadata
- 定义 `BlackboardTagIndex`给总管tag + source无 content
- 定义 `BlackboardWrite`worker 写回)
**A08 职责Phase B 再写):**
- `MainAgentToolName``WorkerToolName`
- 每个 tool 的 params / result 类型
- tool → event 映射表(类型级注释)
---
### 4.3 阶段机(纯函数,不调用 LLM
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A09 | `src/runtime/phase-machine.ts` | done | 5 相位阶段机:`applyEvent``canApplyEvent` | `runtime-state-machine.md` §68 |
| A10 | `src/runtime/state-machine.ts` | done | 废弃别名re-export phase-machine | — |
**A09 职责:**
- `createSession()` — 创建 idle 会话
- `getAllowedEvents()` — 当前相位允许哪些事件
- `canApplyEvent()` — 事件是否合法(含 waitingReason 校验)
- `applyEvent()` — 纯函数session + event → 新 session + effects
- `createArtifact()` — 创建产物记录
**约束:**
- 不 import llm、blackboard、main-agent
- 不做 IO
- 所有相位转移必须写进 history
**A10 职责:** 兼容旧 import 路径,后续可删。
---
### 4.4 黑板
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A11 | `src/blackboard/blackboard.ts` | done | listTagIndex、queryByPatterns、write | `tag-blackboard.md` §2 |
**A11 职责:**
- `listTagIndex()` — 给总管
- `queryByPatterns()` — 给 worker 注入
- `write()` — 按 tag 写回
- `getContentByTag()` / `getLatestByTag()`
---
### 4.5 配置与 LLM
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A12 | `src/config/env.ts` | done | 从环境变量读 baseUrl、apiKey、model | `architecture.md` §2 config |
| A13 | `src/llm/client.ts` | done | OpenAI 兼容 API + MockLlmProvider | — |
**A12 职责:**
- `loadLlmConfig()` — 必须有 key否则抛错
- `loadLlmConfigOptional()` — 无 key 返回 nullCLI 切 mock
**A13 职责:**
- `LlmProvider` 接口:`complete(messages) → string`
- `OpenAiCompatibleProvider` — 真实 API 调用
- `MockLlmProvider` — 测试 / demo 用预设响应
- apiKey 不出现在 session 或项目文件里
---
### 4.6 总管 LLM
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A14 | `src/main-agent/main-agent.ts` | done | 总管:只选 worker读 tag 索引 | `tag-blackboard.md` §6 |
| A15 | `src/main-agent/prompts.ts` | todo | 总管 prompt 拆分 | `tag-blackboard.md` §6 |
**A14 职责:**
- `MainAgent.decide(context)` — 调 LLM返回 `MainAgentDecision`
- `parseMainAgentDecision(raw)` — 解析 JSONPhase B 改为 parse tool calls
- `buildMainAgentUserPrompt(context)` — 拼 user prompt
- `DEFAULT_WORKERS` — 第一版可用 worker 列表
**约束:**
- 总管不读黑板 value
- 总管不直接改 phase
- 不含 question-worker提问是 worker 能力)
**A15 职责(可选拆分):** 把 prompt 从 main-agent.ts 抽出来,方便迭代。
---
### 4.7 编排层
| # | 文件 | 状态 | 干嘛的 | 对应文档 |
|---|---|---|---|---|
| A16 | `src/runtime/orchestrator.ts` | deprecated | 旧入口;用 phase-runtime | `architecture.md` |
**A16 职责:**
- `RuntimeOrchestrator` — 对外 APIstart、submitUserInput、approve、accept 等
- `dispatch(event)` — 调 `applyEvent`,处理 `PhaseEffect`
- `runMainAgent()` — phase=running 时调总管
- `runStubWorker()` — 第一版占位 workerPhase C 替换)
- `resumeStubWorker()` — worker 中途提问后恢复
**约束:**
- 唯一调用阶段机和总管的地方
- user 事件approve / accept只从 CLI / API 进入,不从 LLM 进入
---
### 4.8 入口与测试
| # | 文件 | 状态 | 干嘛的 |
|---|---|---|---|
| A17 | `src/cli/run.ts` | done | 交互式 CLI输入、/approve、/accept、/status |
| A18 | `tests/phase-machine.test.ts` | done | 阶段机单元测试 |
| A19 | `tests/orchestrator.test.ts` | done | 编排层 + Mock LLM 集成测试 |
**A17 职责:**
- 读 stdin转成 orchestrator 方法调用
- 打印 `phase` + `waitingReason`
- `--mock` 或无 API key 时用 MockLlmProvider
---
## 5. Phase B 文件顺序(下一步)
| # | 文件 | 干嘛的 |
|---|---|---|
| B01 | `src/types/tools.ts` | tool 类型定义 |
| B02 | `src/runtime/tool-registry.ts` | 注册 tool、校验参数、tool → event |
| B03 | `src/main-agent/main-agent.ts` | 改为 tool calling 模式 |
| B04 | `src/runtime/tool-handlers/main-agent.ts` | 总管 tool 处理器 |
| B05 | `src/runtime/tool-handlers/worker.ts` | worker tool 处理器ask_user / submit |
**B02 职责:**
- 白名单:当前 phase + waitingReason 下允许哪些 tool
- LLM 调了不允许的 tool → 拒绝,不转事件
---
## 6. Phase C 文件顺序
| # | 文件 | 干嘛的 |
|---|---|---|
| C01 | `src/workers/types.ts` | WorkerDefinition、WorkerRunOutcome |
| C02 | `src/workers/base-worker.ts` | prompt 拼接、tool 执行循环 |
| C03 | `src/workers/outline-worker.ts` | 大纲 worker |
| C04 | `src/workers/drafting-worker.ts` | 正文 worker |
| C05 | `src/runtime/worker-runner.ts` | 替代 orchestrator 里的 stub |
**WorkerRunOutcomePhase C 核心):**
```ts
type WorkerRunOutcome =
| { kind: "complete"; writes: BlackboardWrite[]; summary: string }
| { kind: "suspend"; questions: string[]; partialWrites?: BlackboardWrite[] }
| { kind: "fail"; reason: string };
```
- `complete``worker_completed` 事件
- `suspend``worker_needs_input` 事件
- `fail``runtime_failed` 事件
---
## 7. Phase D 文件顺序
| # | 文件 | 干嘛的 |
|---|---|---|
| D01 | `src/flows/types.ts` | ExecutionFlow、ExecutionStep |
| D02 | `src/flows/ghostwriting-flow.ts` | 代笔 flow 配置 |
| D03 | `src/book-storage/store.ts` | Book 持久化 |
| D04 | `src/preset/importer.ts` | SillyTavern 预设导入 |
---
## 8. 单文件完成检查清单
每写完一个文件,确认:
```text
[ ] 文件顶部的职责是否只对应文档里的一节
[ ] 是否 import 了不该 import 的上层模块(阶段机不应 import LLM
[ ] 是否有对应测试(内核文件必须有)
[ ] 是否更新了本表的状态列done / todo
[ ] 是否在 PR / 提交说明里写「这个文件干嘛的」一句话
```
---
## 9. 当前进度
```text
Phase A0可运行阶段机 ✅ 完成
phase-machine + phase-runtime + phase-demo + 测试
Phase A1总管 LLM ✅ 有初版orchestrator + run.ts
下一步orchestrator 应基于 PhaseRuntime 重构
Phase Btool call 未开始
Phase C真实 worker 未开始
Phase Dflow / 存储) 未开始
```
**下一个应写文件:**`orchestrator.ts` 内部组合 `PhaseRuntime`,而不是重复阶段机逻辑。写之前会先说明该文件职责。

View File

@@ -0,0 +1,184 @@
# 总管 Skill 格式Orchestrator / Manifest
## 1. 定位
**orchestrator.md** = 本包的 **manifest**:注册有哪些 skill、如何验收、如何声明 instance ready。
**不**写逐步流水线剧本;**不**写「总管思维链」逐步调度。
```text
用户选包
→ designagent 按需 invoke instantiate skill
→ declare ready → playagent invoke run skill
→ done归档 Book
```
| 谁决定 | 什么 |
|--------|------|
| **Agent** | 何时 invoke 哪个 skilltool loop |
| **Manifest** | 可用 skill 列表、验收策略、readiness 规则 |
| **Worker SKILL.md** | inputTags、contextSegments、怎么做 |
`shared-context.md`:包级 **固定上下文上半**,注入各 worker prompt。总管不读。
`architecture.md``skill-design-guide.md``context-assembly.md`
---
## 2. 存储位置
```text
skills/novel/weird-rules-short/
├── orchestrator.md
├── shared-context.md # 可选
└── workers/
└── write-rules/SKILL.md
```
- 文件固定名 **`orchestrator.md`**。
- `registry.yaml``path` 指向它。
---
## 3. Frontmatter
```yaml
---
name: weird-rules-short
description: >-
何时选用:用户要写规则怪谈、守则类短篇。
category: novel
bookKind: novel
workers:
- write-rules
- review-infer
- review-author
sharedContext: shared-context.md
---
```
| 字段 | 用途 |
|------|------|
| `description` | 何时选本包 |
| `workers` | run / design 可调度 skill id 白名单loader 用) |
| `bookKind` | Book 存储形态 |
---
## 4. 正文章节(推荐)
```markdown
# 标题
## 启动询问 # 最小 intake
## Skill 注册表 # instantiate + run skill 清单与说明(见 §5
## 验收策略 # 哪些 skill 产出需 user_confirmed / approve
## Instance Ready # 按 run_skill清单 的动态最低可行性
## contextProfile # 可选 variant 说明(见 context-assembly.md
## 用户回合 # user-turn 等(可选)
## 禁用行为
```
**不应出现:**
- 逐步 **Worker 编排表** / **总管思维链**(已废弃为主流程)
- `inputTags` / `outputTags` 列表 → 在 `workers/*/SKILL.md`
- 写作细则 → Worker SKILL 正文
---
## 5. Skill 注册表(替代编排表)
列出本包 **能力库**,供 agent `list_workers` 与 manifest 校验:
```markdown
## Skill 注册表
### Instantiatedesign stage
| id | 说明 | 典型 outputTags |
|----|------|-----------------|
| interaction-paradigm | 定交互范式与 run_skill清单 | 设计.run_skill清单 |
| world-blueprint | 世界背景 | 设计.世界.蓝图 |
| persona-draft | 角色卡草稿 | 角色卡.草稿 |
### Runplay stage
| id | 说明 | 默认 acceptance |
|----|------|-----------------|
| write-rules | 写规则 | user_confirmed |
| review-infer | 读者视角验收 | programmatic_review |
```
agent **按需 invoke**,不必按表顺序跑全。
---
## 6. 启动询问与 Instance Ready
**启动询问:** 只收集 agent 无法从空推断的最小信息 → `用户.需求` 等。
**Instance Ready** 不写死「14 步全完成」,而写:
```markdown
## Instance Ready
`设计.run_skill清单` 已 accepted且清单中每个 run skill 的
**最低 input 要求**(见各 SKILL.md已在黑板存在或已记录跳过理由。
由 agent `declare_instance_ready` + Runtime 校验。
```
---
## 7. 验收策略
```markdown
## 验收策略
| skill | requiresApproval | acceptanceMode |
|-------|------------------|----------------|
| setup-scenario | true | user_confirmed |
| world-engine | false | no_confirmation |
| present-round | false | user_confirmed |
```
agent 通过 `run_worker(..., requiresApproval)` 触发;默认值也可写在 manifest 供 Runtime 填充。
---
## 8. contextProfile
若同一 skill 有多种游玩/创作模式,在 manifest 列出 variant 名与含义;实例化写入 Book。
格式见 `context-assembly.md`
---
## 9. 与 Worker SKILL 的关系
```text
orchestrator.md 注册 + 验收 + readiness
workers/*/SKILL.md 能力 + 上下文契约contextSegments
```
Prompt 注入:
```text
Agentsession 摘要 + tool可用 read_blackboard
Workershared-context + SKILL + assembleWorkerContext上固定下动态
```
---
## 10. 迁移说明
旧包中的 `## Worker 编排``## 总管思维链` 仍可作为 **人工参考**,但新包与 world-simulator **不应**以此为主流程。
Runtime 目标态以 agent tool loop + manifest 注册表为准。
---
## 11. 相关文档
| 文档 | 关系 |
|------|------|
| `skill-design-guide.md` | 设计方法 |
| `worker-skill-format.md` | SKILL.md 字段 |
| `creation-playbook.md` | 概念 |

297
docs/preset-format.md Normal file
View File

@@ -0,0 +1,297 @@
# 预设格式
## 1. 定位
Preset 是一次 LLM 请求的上下文编排资产。它负责描述 prompt manager 中有哪些条目、这些条目的启用状态与排列顺序,以及本次生成使用哪些模型参数。
第一版的 preset 目标是兼容 SillyTavern 类预设文件中的核心部分,而不是完整复刻 SillyTavern 的所有扩展能力。
需要支持的内容:
```text
prompts
prompt manager 条目列表。每个条目描述一段可插入上下文的 prompt。
prompt_order
条目顺序与启用关系。它决定本次请求实际按什么顺序装配 prompt。
generation parameters
生成参数,例如 temperature、top_p、top_k、min_p、frequency_penalty、presence_penalty、max tokens 等。
```
第一版不支持的内容:
```text
regex_scripts
正则隐藏、正文提取、格式美化等后处理脚本。
extension scripts
预设内携带的前端脚本、按钮、插件配置。
UI-only fields
只影响 SillyTavern 界面展示、不影响 LLM 请求组装的字段。
```
## 2. 从 SillyTavern 预设中读取什么
一个真实的 SillyTavern 预设通常同时包含 prompt manager 条目、顺序关系、生成参数和扩展配置。我们的导入器只读取前三类。
### 2.1 Prompt 条目
`prompts` 数组读取 prompt manager 条目。
典型字段:
```ts
type ImportedPromptEntry = {
identifier: string;
name: string;
enabled: boolean;
role: "system" | "user" | "assistant";
content?: string;
injection_position?: number;
injection_depth?: number;
injection_order?: number;
system_prompt?: boolean;
marker?: boolean;
forbid_overrides?: boolean;
};
```
字段含义:
```text
identifier
条目唯一标识。prompt_order 通过它引用条目。
name
给用户看的条目名称。
enabled
条目自身默认启用状态。最终是否启用还需要结合 prompt_order。
role
条目插入请求时使用的消息角色。
content
条目文本。部分内置 marker 条目可能没有 content需要运行时从 session 或角色数据中补齐。
injection_position / injection_depth / injection_order
SillyTavern 的插入位置、深度和顺序信息。第一版先保留,不完全模拟深度插入语义。
system_prompt / marker
标识这个条目是不是内置占位条目,例如角色描述、世界书、聊天历史等。
forbid_overrides
标识条目是否禁止被覆盖。第一版先保留字段,不实现复杂覆盖策略。
```
### 2.2 顺序与启用关系
`prompt_order` 读取实际装配顺序。
典型字段:
```ts
type ImportedPromptOrder = Array<{
character_id: number;
order: Array<{
identifier: string;
enabled: boolean;
}>;
}>;
```
第一版采用的规则:
```text
1. 以 prompt_order[0].order 作为主顺序。
2. 按 order 数组顺序遍历 identifier。
3. 找到对应 prompts 条目。
4. 只有 order.enabled 和 prompt.enabled 都为 true 时,条目才参与本轮上下文装配。
5. 如果 prompt_order 引用了不存在的 identifier导入时记录 warning但不阻断导入。
6. 如果 prompts 中存在但 prompt_order 未引用,默认不参与请求,但保留在 preset 中。
```
`prompt_order``prompts` 中的排列更重要。`prompts` 是条目仓库,`prompt_order` 才是实际启用的编排表。
### 2.3 生成参数
从预设顶层读取生成参数。
第一版优先支持这些字段:
```ts
type ImportedGenerationParameters = {
temperature?: number;
top_p?: number;
top_k?: number;
min_p?: number;
frequency_penalty?: number;
presence_penalty?: number;
repetition_penalty?: number;
openai_max_context?: number;
openai_max_tokens?: number;
stream_openai?: boolean;
reasoning_effort?: string;
verbosity?: string;
seed?: number;
n?: number;
};
```
字段分为两类:
```text
通用采样参数
temperature、top_p、top_k、min_p、frequency_penalty、presence_penalty、repetition_penalty。
请求容量与行为参数
openai_max_context、openai_max_tokens、stream_openai、reasoning_effort、verbosity、seed、n。
```
不同供应商不一定支持全部字段。导入后应先保存原始字段,再由 LLM adapter 决定哪些字段可以发送。
## 3. 内部归一化格式
导入 SillyTavern preset 后,不应该直接在业务层使用原始 JSON。需要归一化成我们自己的结构。
```ts
type PresetPackage = {
id: string;
name: string;
source: "native" | "sillytavern";
prompts: PresetPromptEntry[];
promptOrder: PresetPromptOrderItem[];
generation: GenerationParameters;
unsupported: UnsupportedPresetSection[];
raw?: unknown;
};
type PresetPromptEntry = {
id: string;
name: string;
enabled: boolean;
role: "system" | "user" | "assistant";
content: string;
marker: boolean;
sourceIdentifier: string;
injection?: {
position?: number;
depth?: number;
order?: number;
};
};
type PresetPromptOrderItem = {
promptId: string;
enabled: boolean;
orderIndex: number;
};
type GenerationParameters = {
temperature?: number;
topP?: number;
topK?: number;
minP?: number;
frequencyPenalty?: number;
presencePenalty?: number;
repetitionPenalty?: number;
maxContextTokens?: number;
maxOutputTokens?: number;
stream?: boolean;
reasoningEffort?: string;
verbosity?: string;
seed?: number;
variants?: number;
};
type UnsupportedPresetSection = {
path: string;
reason: string;
};
```
归一化后的 `PresetPackage` 是系统内部唯一使用的 preset 结构。原始 SillyTavern JSON 只作为导入来源和调试证据保留。
## 4. 上下文装配规则
一次请求的上下文装配按以下顺序执行:
```text
1. 读取 PresetPackage.promptOrder。
2. 过滤 enabled=false 的顺序项。
3. 找到对应 PresetPromptEntry。
4. 再过滤 entry.enabled=false 的条目。
5. 对 marker 条目进行运行时替换。
6. 对普通 content 条目进行变量替换。
7. 按 role 合并成 LLM messages。
8. 应用 generation 参数。
```
其中 marker 条目用于挂载运行时上下文:
```text
charDescription
可以映射为当前创作项目的设定说明。
charPersonality
可以映射为文风、叙事人称、角色行为约束。
worldInfoBefore / worldInfoAfter
可以映射为长期设定、世界观、知识库片段。
scenario
可以映射为当前创作目标或当前阶段说明。
chatHistory
可以映射为历史会话、已有正文、用户反馈摘要。
```
这些映射不是 SillyTavern 原义的完整复刻,而是为了兼容预设资产,让它们能服务我们的写作 agent。
## 5. 与创作流程的关系
Preset 不决定创作流程,也不决定验收模式。
```text
Preset
决定本轮请求如何拼接 prompt、哪些条目启用、使用哪些生成参数。
Execution Flow
决定阶段顺序、每阶段使用哪个 worker、每阶段采用哪种验收模式。
Worker
决定某个创作能力如何执行,例如大纲、剧情线、文风、正文草稿。
Runtime Session
记录当前事实、历史事件、产物和审批结果。
```
因此,同一个 preset 可以用于多个创作流程;同一个创作流程也可以切换不同 preset。二者是正交关系。
## 6. 第一版导入策略
第一版导入器只做保守转换:
```text
保留 prompts。
保留 prompt_order。
保留生成参数。
记录但忽略 regex_scripts。
记录但忽略 extension scripts。
记录未知字段,不丢弃原始 JSON。
```
导入结果应该给用户可读的报告:
```text
导入 prompt 条目数量
启用条目数量
未被 prompt_order 引用的条目数量
缺失 identifier 的 order 项
已读取的生成参数
被忽略的扩展字段
```
这个报告比静默导入更重要。预设文件经常很大且混有脚本、正则、UI 配置和模型参数,必须让用户知道哪些内容真正进入了我们的运行时。

53
docs/run-snapshot.md Normal file
View File

@@ -0,0 +1,53 @@
# Book 快照
用户手动保存、多档位;与自动续作 `session.json` 分离。
## 两种 kind更新语义
| kind | 名称 | 何时存 | 存什么 |
|------|------|--------|--------|
| **`instance`** | 设计完成 / 资产截面 | design 完成或从 play 剥离设定 | **CardAsset 级** tag角色卡.确认稿、世界蓝图…);**不含**轮次、事件流 |
| **`run`** | 游玩存档 | play 任意时刻 | instance 层 + `运行.*`、轮次、变量、对话 |
```text
CardBook design 完成 → 可存 instance导出角色卡 / 世界设定)
PlayBook 进行中 → 可存 run第 N 轮续玩)
```
**instance** 不再表示「14 步 pipeline 填完」,而是 **agent declare ready 时的可复用资产截面**。详见 `book-storage.md`
## 与三种存储需求
| 需求 | 用什么 |
|------|--------|
| 创建角色卡**过程** | CardBook `designTrace` + messages快照不替代须 Book 级) |
| 创建好的**卡** | CardAsset 或 `instance` 快照 |
| 游玩**过程** | PlayBook `playTrace` + `run` 快照 |
## 与自动续作
| | `session.json` | `run-snapshots/` |
|--|----------------|------------------|
| 触发 | 自动 | 用户手动 |
| 数量 | 每 Book 1 份 | 多档、可命名 |
| 打开作品 | 默认恢复 | 选档 **加载** |
## API
```text
GET /api/books/:bookId/saves
POST /api/books/:bookId/saves { label, kind: "instance"|"run", note?, sessionId? }
POST /api/books/:bookId/saves/:id/load
DELETE /api/books/:bookId/saves/:id
```
## 实现
```text
src/types/run-snapshot.ts
src/book/run-snapshot-store.ts
src/book/snapshot-filters.ts instance 时剥离 运行.* 等
src/server/session-manager.ts
```
存储:`books/{bookId}/run-snapshots/{id}.json`

View File

@@ -0,0 +1,136 @@
# 运行阶段机
## 1. 定位
阶段机 = **并发与权限模型**:谁在场、哪些 tool 可用、何时必须等用户、产物何时算事实。
**不是** 流水线执行器;「下一步 invoke 哪个 skill」由 agent tool loop 决定。
```text
业务 stageBook / Session
design实例化→ play运行→ done
运行相位RuntimePhase
idle | running | waiting_user | done | error
```
---
## 2. 五个运行相位
```ts
type RuntimePhase = "idle" | "running" | "waiting_user" | "done" | "error";
```
| phase | 含义 |
|-------|------|
| `idle` | 会话已创建 |
| `running` | agent burst 或 worker 执行中 |
| `waiting_user` | 等用户(见 waitingReason |
| `done` | 正常结束 |
| `error` | 不可恢复 |
---
## 3. waitingReason
```ts
type WaitingReason =
| { kind: "skill_selection"; availableSkills: SkillIndexEntry[] }
| { kind: "intake"; prompt: string }
| { kind: "input"; message?: string }
| { kind: "approve_step"; decisionId: string }
| { kind: "review_artifact"; artifactId: string }
| { kind: "worker_questions"; workerId: string; questions: string[] }
| { kind: "revision"; instruction?: string };
```
---
## 4. 阶段机 vs tool
| 角色 | 说明 |
|------|------|
| **循环 tool** | 仅当 `running` 且无阻塞 worker结果进 agent messages |
| **边界 tool** | 触发 phase / waitingReason 变化 |
| **用户 event** | 唯一 approve、acceptburst 计数归零 |
| **Worker** | `running``currentWorkerId` set 时,总管暂停 |
目标态工具表见 `tool-contracts.md`
---
## 5. Tool loop burst
```text
用户硬事件
→ toolLoopBurstCount = 0
→ phase = running若适用
→ agent while burst < maxBurst:
循环 tool …
边界 tool → 可能 waiting_user / 启动 worker
→ worker 完成 → 按 acceptanceMode 可能 waiting_user
```
`maxBurst`**两次用户操作之间**的上限(默认 12
---
## 6. 典型转移(简化)
```text
idle → skill_selection
skill_selected → intake 或 input
intake 完成 / confirm → running → agent burst
running → run_worker → approve_step 或 worker 执行
worker_completed → review_artifactuser_confirmed
user_accepted → running → agent burst
finish → done
```
---
## 7. 会话结构
```ts
type RuntimeSession = {
id: string;
phase: RuntimePhase;
waitingReason?: WaitingReason;
currentWorkerId?: string;
acceptanceMode?: AcceptanceMode;
resumeContext?: ResumeContext;
slots: Record<string, unknown>;
artifacts: ArtifactRecord[];
pendingDecision?: MainAgentDecision;
history: RuntimeEvent[];
// 目标toolLoopBurstCount, lifecycleStage
};
```
---
## 8. 产物生命周期
```text
drafted → under_review → accepted | rejected | revision_requested
accepted 前不得当下游事实
```
---
## 9. 与编排表的关系
**已废弃为主流程:** orchestrator「思维链 / 逐步编排表」驱动步骤。
orchestrator 改为 **skill 注册表 + 验收策略**;阶段机不解析「第几步」。
`semi_auto` / `pauseCheckpoint` 仍可作为可选策略,由 manifest 声明,第一版未实现。
---
## 10. 代码
`src/runtime/phase-machine.ts` — 纯函数 `applyEvent`
`src/runtime/phase-runtime.ts` — Session IO、tool loop 入口
旧 11 态已合并为 5 phase + waitingReason。

184
docs/skill-design-guide.md Normal file
View File

@@ -0,0 +1,184 @@
# Skill 设计指南
指导 **如何从零设计一个 orchestrator 包**skill 能力库 + run skill 组合)。
格式见 `skill-format.md``orchestrator-skill-format.md``worker-skill-format.md`;上下文见 `context-assembly.md`
**设计顺序:**
```text
1. 用户意图 → run skill 清单(交互范式 skill 产出)
2. 每个 run skill 倒推需要哪些 instantiate skill / tag
3. 定义各 skill 的 inputTags、outputTags、contextSegments
4. 上下文隔离与验收边界
5. orchestrator manifest能力注册非逐步剧本
```
---
## 1. 实例化agent 选 skill不是填步骤
### 1.1 两层倒推
```text
用户意图(长篇 / 短篇 / 思想实验 / 角色卡扮演 …)
→ 交互范式 skill产出 设计.run_skill清单
→ 每个 run skill 需要什么输入?
→ agent 按需 invoke instantiate skill能力库中的 SKILL.md
→ declare_instance_ready → play stage
```
**能力库**(如 world-simulator 包内十几个 instantiate skill不是 1→N 管道agent 跳过不需要的 skill并记录 `设计.跳过.{skillId}`
### 1.2 示例:不同意图的 run skill 组合
| 用户意图 | run skill 示例 | 实例化倒推 |
|----------|----------------|------------|
| 长篇小说 | 变量管理、大纲推荐、转述者、世界机 | 变量目录 skill、叙事指南、世界蓝图… |
| 短篇 | 情感流生成器 | 美学纲领、情感曲线约定 |
| 思想实验 | 世界模拟器 | 规则、行动格式;**无**转述者 |
| 角色卡扮演 | 转述者(+ 可选世界机) | 角色卡确认稿、口吻、回复格式 |
### 1.3 run 阶段交互形态
agent 在 play stage 的 burst 内调度 run skill形态因包而异
| 形态 | 适用 | sketch |
|------|------|--------|
| 单 skill 直出 | 简单大纲 | `outline` |
| 行动–反应循环 | 博弈、世界模拟 | 世界机 ↔ 角色决策 ↔ 展示 |
| 分叉验收 | 规则怪谈 | `write` → 双 `review` |
形态写在各 skill 的 **能力说明**里,**不**写进全局编排表逐步剧本。
---
## 2. 暂停与用户
### 2.1 边界 tool 即暂停
agent tool loop 在下列情况 **退出 burst**,进入 `waiting_user`
```text
ask_user / review_blackboard
run_worker + requiresApproval
worker 完成 + user_confirmed → review_artifact
worker ask_user → worker_questions
```
暂停策略写在 orchestrator manifest 的 **验收策略**,不是「第几步必须停」的管道表。
### 2.2 user-turn
用户亲自决策的环节:独立 skill`用户.最新输入` 写入与 LLM 角色同形 tag供世界机裁决。见 `worker-skill-format.md`
---
## 3. 上下文:上半固定、下半动态
每个 skill`SKILL.md`)声明:
```text
inputTags / outputTags 黑板接口
contextSegments static+ dynamic
contextIsolation 谁看不见什么
contextProfile variants 实例化时选档位
```
原则:**稳定在上、增量在下**Runtime 拼接agent 不改。
全文见 `docs/context-assembly.md`
### 3.1 分层示例roleplay-game-theory
| tier | 含义 | tag 示例 |
|------|------|----------|
| static | 前提、实体设定 | `情境.实验.设定``角色.{id}.设定` |
| dynamic | 历史、本轮 | `运行.事件流``可见信息` |
---
## 4. 倒推标签
对每个 skill 填表:
```text
skill id | 职责 | stage(design/play) | inputTags | outputTags | contextSegments | 验收者
```
### 4.1 角色扮演博弈(摘录)
| skill | 读取 | 写入 | 验收 |
|-------|------|------|------|
| setup-scenario | `用户.博弈需求` | 情境、规则、角色设定 | 用户 |
| world-engine | 情境、规则、事件流、行动 | 可见信息、事件流 | 程序 |
| role-decide | 本人设定、事件流、可见信息 | `.思考``.行动` | 程序 |
| present-round | 本轮产物 | `输出.用户展示` | 用户 |
展示类 skill 单独存在agent 调度,不拼长文。
---
## 5. 上下文隔离(必查)
```text
□ 哪些 tag 只给用户、不注入生产 skill
□ 盲读 review 不可见哪些 tag
□ 草稿 vs 确认稿:下游何时可当作事实?
□ 多角色role_pov 是否生效?
```
`inputTags` + `contextIsolation` + Runtime 过滤,不靠 prompt 口头禁止。
---
## 6. orchestrator manifest非编排表
orchestrator.md 应包含:
```text
□ 启动询问(最小 intake
□ instantiate skill 注册表id、stage、description
□ run skill 注册表
□ 验收策略(哪些 skill 产出需 user_confirmed
□ contextProfile 可选 variant 说明
□ declare_instance_ready 最低可行性(按 run_skill清单 动态校验)
```
**不应包含:** 逐步思维链、「第 5 步必须跑 world-engine」类管道脚本。
---
## 7. checklist
```text
□ 1. 一句话:本包 play stage 的交互形态
□ 2. 交互范式 skill 产出 run_skill清单 的 schema
□ 3. 每个 run skill 倒推 instantiate skill 需求
□ 4. 各 SKILL.mdinput/output、contextSegments、隔离
□ 5. shared-context.mdstatic 上半)
□ 6. manifest注册表 + 验收 + readiness
□ 7. skills/README.md 注册
```
---
## 8. 示例包
| 包 | 特点 |
|----|------|
| `basic` | 单 run skill最小上下文 |
| `weird-rules-short` | 分叉验收 |
| `roleplay-game-theory` | 行动–反应循环 |
| `world-simulator`(规划) | 大能力库 + agent 实例化 |
对照时复用 **方法**,不照搬 tag 名或 skill 数量。
---
## 9. 相关文档
| 文档 | 关系 |
|------|------|
| `context-assembly.md` | 拼接规格 |
| `creation-playbook.md` | 概念 |
| `orchestrator-skill-format.md` | manifest 写法 |
| `book-storage.md` | 过程与资产归档 |

510
docs/skill-format.md Normal file
View File

@@ -0,0 +1,510 @@
# Skill 格式与存储
## 1. 定位:两层 Skill
本项目有 **两种 Skill 文档**,不要混在一个文件里:
| 类型 | 路径 | 消费者 | 写什么 |
|------|------|--------|--------|
| **总管 Skill** | `skills/{bookKind}/{name}/orchestrator.md` | Main Agent | **何时**调哪个 worker、验收方式、启动询问 |
| **Worker Skill** | `workers/{workerId}/SKILL.md` | Worker Agent | **inputTags/outputTags**、怎么做 |
```text
选 weird-rules-short总管 Skill
→ 总管brief 齐了 → run ruleset-workerinput=[project.brief]
→ Worker读 workers/ruleset-worker/SKILL.md → 写 core / rules / commentary
→ 总管rules accepted → run review-worker
→ Worker读 workers/review-worker/SKILL.md → 评估怎么做
```
**何时评估** = 总管 Skill 的 Worker 编排表。
**如何评估** = review-worker 的 Worker Skill。
详细规范:
- 总管 Skill → `docs/orchestrator-skill-format.md`
- Worker Skill → `docs/worker-skill-format.md`
旧称「Skill = 创作说明书」仍成立,但说明书 **拆成编排总管与执行worker两份**
```text
会话开始
→ 询问 1选哪个总管 Skillskills/{bookKind}/{name}/orchestrator.md
→ 加载总管 Skill
→ 询问 2读总管 Skill「## 启动询问」
→ 之后总管按「Worker 编排」调度Worker 读本 skill 包内 workers/{id}/SKILL.md
```
---
## 2. 存储位置
Skill 以 **Skill 包skill pack** 为单位:一个总管 + 其专属 workers**同包绑定,不跨包复用 worker**。
```text
skills/
├── registry.yaml
├── novel/ # Book 形态
│ ├── basic/
│ │ └── orchestrator.md # 总管 Skill
│ ├── weird-rules-short/
│ │ ├── orchestrator.md # 总管:何时调谁、黑板 key
│ │ └── workers/ # 本总管专属,不与其他 skill 共享
│ │ ├── write-rules/
│ │ │ └── SKILL.md # 规则怪谈:怎么写规则+解析
│ │ └── review/
│ │ └── SKILL.md # 规则怪谈:怎么检查
│ └── novel-standard/
│ ├── orchestrator.md
│ └── workers/
│ ├── outline/SKILL.md
│ └── drafting/SKILL.md
└── dialogue/
└── theater-roleplay/
├── orchestrator.md
└── workers/
└── turn/SKILL.md
```
规则:
```text
第一层文件夹 = bookKindnovel | dialogue决定 Book 存储结构
第二层文件夹 = 一个 skill 包,名与 frontmatter.name 一致
orchestrator.md 总管 Skill编排、启动询问、验收
workers/{id}/ 本包专属 workerid 在包内唯一即可
Worker 不复用novel-standard 的 outline worker ≠ weird-rules-short 的任何 worker
registry.yaml 的 path 指向 orchestrator.md如 novel/weird-rules-short/orchestrator.md
```
**为何不复用 worker** 同一「产出形状」如规则表在不同总管下的写法、Rubric、自检完全不同共享 worker 会把体裁细节塞进总管或搞混上下文。需要相似流程时 **复制 worker 包再改**,而不是引用全局 worker。
与 Cursor skill 的区别:
| | Cursor Skill | 本项目 Skill |
|---|---|---|
| 位置 | `.cursor/skills/` | `skills/` |
| 触发 | Agent 自动或显式引用 | **会话开始必须选一个** |
| 内容 | 通用任务指南 | **创作流程 + 思维链 + 询问策略** |
| 消费者 | Cursor Agent | 总管 LLM |
---
## 3. SKILL.md 结构
### 3.0 两层分类(重要)
Skill 分类分**两层**,不要混为一层:
```text
第一层Book 形态category / bookKind选定后不可换
novel 类小说存储:卷、章、大纲、正文(含各类小说子类型)
dialogue 多轮多角色对话回合、角色、场景roleplay / 剧场)
第二层:具体 Skill 包name用户启动时选
每个包 = orchestrator.md + workers/,不是 category 下的平铺 .md 枚举。
```
示例:
```text
category: novel ← 第一层Book 怎么存
name: novel-standard ← 第二层:标准长篇流程
name: weird-rules-short ← 第二层:规则怪谈短篇(仍是 novel Book
name: basic ← 第二层:最小演示
category: dialogue
name: theater-roleplay ← 第二层:剧场式多角色互动
```
**不要把「规则怪谈」做成与 novel 平级的 category。**
规则怪谈是 **novel 形态下的专精 skill**,用 `name` + `tags` 区分:
```yaml
---
name: weird-rules-short
description: 短篇规则怪谈:从隐含核心反推护命规则,再成章撰写。
category: novel
bookKind: novel
tags: [novel, weird_rules, short, horror]
---
```
| 层级 | 字段 | 谁选 | 例子 |
|---|---|---|---|
| Book 形态 | `category` / `bookKind` | 选 skill 时确定,之后不变 | `novel` / `dialogue` |
| 具体流程 | `name` | 启动询问 1选哪个 SKILL.md | `weird-rules-short` |
| 体裁标签 | `tags` | 可选,供匹配与过滤 | `weird_rules`, `standard` |
启动 UI 可以按 `category` 分组展示,组内列出多个 `name`(如「小说」下:标准长篇、规则怪谈短篇、…)。
参考 Cursor `SKILL.md`YAML frontmatter + Markdown 正文。
```markdown
---
name: novel-standard
description: >-
标准长篇小说创作:先简报、再大网、事件细化、正文。
适用于用户要写小说、章节、大纲时使用。
category: novel
version: 1
defaultFlowId: ghostwriting-flow
tags: [novel, outline, draft]
---
# 标准小说创作
## 启动询问
(流程 2选 skill 后向用户展示什么、必收集项、写入 book.brief
## 创作总纲
(给总管:这类内容是什么、总体顺序、禁忌)
## 总管思维链
(每轮决策前先检查什么、如何选 worker
## 推荐阶段
brief → outline → plotline → style → draft
## 询问策略
### 总管应先问
### 交给 Worker 问
## 推荐 Worker
## 示例
```
### 3.1 Frontmatter 字段
```yaml
---
name: novel-standard # 必需,唯一 id[a-z0-9-]
description: > # 必需,供启动时向用户展示、供总管匹配
第三人称描述 WHAT + WHEN。
category: novel # 必需Book 形态,见 §3.0
bookKind: novel # 建议与 category 对齐;选定后 Book 结构固定
version: 1
defaultFlowId: ghostwriting-flow # 可选,绑定 execution flow
defaultPresetId: writing-default # 可选
tags: [novel, standard] # 体裁/子类型标签,如 weird_rules、short
suggestedWorkers: # 可选,本 skill 常用 worker
- outline-worker
- plotline-worker
- drafting-worker
---
```
| 字段 | 必需 | 用途 |
|---|---|---|
| `name` | ✅ | skill id通常与文件名一致不含 .md |
| `description` | ✅ | 启动选择列表展示;总管判断用户描述是否匹配 |
| `category` | ✅ | 与 `bookKind` 一致:`novel` \| `dialogue` |
| `bookKind` | 建议 | 选定后 Book 结构固定;缺省时由所在文件夹推断 |
| `tags` | | 体裁细分:`weird_rules``standard` 等 |
| `path` | registry | 相对路径,如 `novel/weird-rules-short.md` |
| `defaultFlowId` | | 选中后默认 execution flow |
| `suggestedWorkers` | | 总管选 worker 时的白名单提示 |
### 3.2 撰写标准:写作生命周期(推荐)
Skill 文件本质上是**给 LLM 与 Runtime 读的字符串规格**。下面这套「触发 → 写前 → 写中 → 写后 → 质量维度」与现有阶段机、验收模式对齐,**应作为所有 skill `.md` 的撰写标准**。
```text
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 触发条件 │ → │ 写作前 │ → │ 写作中 │ → │ 写作后 │ → │ 质量维度 │
│ 何时激活 │ │ 需求分析 │ │ 分步引导 │ │ 自检润色 │ │ 可量化 Rubric│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
frontmatter 启动询问 推荐阶段 自检清单 质量评估标准
+ tags + 创作总纲 + 示例/约束 + 验收策略 + 接受度(预留)
+ 询问策略 + 禁用行为
```
#### 与正文章节的对应关系
| 生命周期 | 对应章节 | 写什么 |
|---|---|---|
| **触发条件** | frontmatter `description` + `tags` | 何时应选本 skill用户说什么话时应匹配如「规则怪谈」「短篇怪谈」 |
| **写作前 · 需求分析** | `## 启动询问` + `## 创作总纲` | 对象、受众、语气、目标、禁忌;**禁止直接动笔**;写入哪个 key |
| **写作中 · 分步引导** | `## 推荐阶段` + `## 示例` + `## 禁用行为` | 阶段链、每步约束、好/坏示例;对应 worker 与产出 key |
| **写作后 · 自检润色** | `## 自检清单` + `## 验收策略` | 产出前检查点LLM 自审 vs 人工 vs 程序验收 |
| **质量评估** | `## 质量评估标准` | 可量化维度 + 各 stage 的 acceptanceMode |
| **编排** | `## 总管思维链` + `## 询问策略` + `## 推荐 Worker` | 总管如何调度;谁向用户提问 |
不必每个 skill 都写独立 `# 写作前` 大标题;**用统一章节名即可**,内容覆盖上表即可。
#### `## 质量评估标准`(必需)
避免只写「写得更好」。每个 skill 应列出 **可检查的质量维度**,格式建议:
```markdown
## 质量评估标准
| 维度 | 说明 | 检查方式 |
|---|---|---|
| 完整性 | 是否满足启动询问中的必收集项 | programmatic / 人工 |
| 体裁符合 | 是否符合创作总纲(如规则怪谈:规则可反推危险) | LLM 自审 + 人工 |
| 一致性 | 与已 accepted 上游产物是否矛盾 | programmatic |
| **接受度** | 用户/系统是否接受该产物(预留) | user_confirmed → 记录 accept/reject |
各 stage 默认 acceptanceMode 见「验收策略」。
```
**「接受度」维度(预留):**
- 第一版:**不强制数值打分**;用阶段机的 `user_accepted_artifact` / `reject` 记录二元结果即可。
- 后续可在 Book / Session 元数据写入 `acceptanceScore``acceptanceNotes`(字符串或 15 分),与 Skill Rubric 对齐。
- Skill 里写清楚:**接受度由验收事件沉淀,不由 LLM 自报分数代替人工。**
#### `## 自检清单`(必需)
写作后、提交验收前worker 或总管应过的检查点(字符串列表即可):
```markdown
## 自检清单
### rules.draft 提交前
- [ ] 每条规则能否对应非玄学的危险动机?
- [ ] 是否未直接写出 core.danger
- [ ] 是否无「违反即抹杀」空规则?
### content.chapter.* 提交前
- [ ] 是否遵守 rules.draft 已 accepted 版本?
- [ ]
```
`programmatic_review` 的关系:自检清单 = LLM/人读的规范;程序验收 = 可机械执行的子集。
#### 模板骨架(`skills/{bookKind}/{name}.md`
```markdown
---
name: …
description: … # 触发条件WHEN
tags: …
category / bookKind: …
suggestedWorkers: …
---
# 标题
## 启动询问 # 写作前 · 需求分析
## 创作总纲
## 总管思维链
## 推荐阶段 # 写作中 · 分步引导
## 询问策略
## 推荐 Worker
## 示例 # 写作中 · 约束与范例
## 禁用行为
## 自检清单 # 写作后
## 验收策略 # 写作后 · 与 acceptanceMode 绑定
## 质量评估标准 # Rubric + 接受度(预留)
## Book 结构 # 可选novel / dialogue 形态说明
```
代码当前**结构化解析**的仍主要是 `## 启动询问`;其余章节整段注入总管 prompt`buildSkillContext`)。**全部是 Markdown 字符串,不矛盾。**
### 3.3 正文必需章节(检查清单)
| 章节 | 生命周期 | 内容 |
|---|---|---|
| frontmatter | 触发 | name、description、tags、bookKind |
| **启动询问** | 写前 | 必收集项、写入 key |
| **创作总纲** | 写前 | 顺序、边界、体裁原则 |
| **推荐阶段** | 写中 | 阶段链、worker、产出 key、prerequisites |
| **示例** | 写中 | 好/坏对照或完整流程范例 |
| **禁用行为** | 写中 | 绝对不要做的事 |
| **自检清单** | 写后 | 提交验收前的检查点 |
| **验收策略** | 写后 | 各 stage 的 acceptanceMode |
| **质量评估标准** | 质量 | 可量化维度 + **接受度(预留)** |
| **总管思维链** | 编排 | 每轮决策检查 |
| **询问策略** | 编排 | 总管问 vs worker 问 |
| **推荐 Worker** | 编排 | 与 suggestedWorkers 一致 |
可选:`## Book 结构``examples.md` 外链。
---
## 4. registry.yaml可选
启动时列举可用 skill不必扫描目录
```yaml
skills:
- name: novel-standard
description: 标准长篇小说:大纲 → 事件 → 正文
category: novel
- name: weird-rules-short
description: 短篇规则怪谈:隐含核心 → 护命规则 → 成章
category: novel
- name: theater-roleplay
description: 剧场式角色扮演:角色 → 场景 → 回合互动
category: dialogue
- name: forum-thread
description: 论坛体连载:楼主身份 → 回帖风格 → 楼层
category: novel
```
若无 `registry.yaml`Runtime 扫描 `skills/novel/*.md``skills/dialogue/*.md`
**registry 列举的是第二层 skillname不是 category。**
---
## 5. 会话启动:第一个询问是选 Skill
Skill 选择发生在**任何创作逻辑之前**。
### 5.1 启动转移
```text
idle
session_started
→ waiting_user(skill_selection)
```
新增 `waitingReason`
```ts
| { kind: "skill_selection"; availableSkills: SkillIndexEntry[] }
```
### 5.2 向用户展示
```text
请选择创作类型:
【小说】Book 形态:卷 / 章)
1. novel-standard — 标准长篇:大纲 → 事件 → 正文
2. weird-rules-short — 短篇规则怪谈:核心 → 规则 → 成章
3. basic — 最小演示
【对话】Book 形态:回合 / 多角色)
4. theater-roleplay — 剧场式角色扮演
也可直接描述你想写什么,我会帮你匹配 skill name。
```
### 5.3 用户回答方式
```text
输入编号或 namenovel-standard
输入自然语言:我想写一个剧场扮演
输入自定义:用 novel-standard但是偏悬疑
```
Runtime 解析为 `skill_selected` 事件:
```ts
{ type: "skill_selected"; payload: { skillId: string; userHint?: string } }
```
然后:
```text
加载 skills/{skillId}/SKILL.md
解析 ## 启动询问 → session.slots.activeSkill
→ waiting_user(input)
message 来自 SKILL.md「启动询问·向用户展示」
必收集项 / 写入目标 同样来自该节
```
### 5.4 两阶段启动
```text
询问 1系统 skill_selection 「用哪个 skill」→ registry / description
询问 2skill input 「启动询问」章节 → 每类内容问的不同
```
**只有询问 1 是系统固定的。询问 2 及之后所有创作逻辑,都在 SKILL.md 里。**
---
## 6. 总管如何使用已选 Skill
`session.slots.activeSkill` 加载后,总管 prompt 注入:
```text
当前 skill: novel-standard
category: novel
创作总纲: SKILL.md 摘要或全文)
当前推荐阶段: outline由 resolver 根据黑板推断)
询问策略: 总管应先问 briefoutline 细节交给 worker
建议 worker: outline-worker, drafting-worker
defaultFlowId: ghostwriting-flow
```
总管决策仍通过 tool / JSON 决策,**不**直接改 phase。
---
## 7. 示例
完整示例见仓库内真实文件(不要只在文档里维护一份):
```text
skills/novel/weird-rules-short.md
skills/dialogue/theater-roleplay.md
```
---
## 8. 解析与加载(将来代码)
| 文件 | 干嘛的 |
|---|---|
| `src/skills/types.ts` | SkillIndexEntry、ParsedSkill 类型 |
| `src/skills/loader.ts` | 扫描 skills/、解析 frontmatter + 正文 |
| `src/skills/registry.ts` | 读 registry.yaml 或目录扫描 |
| `src/skills/resolver.ts` | 根据黑板 index 推断当前 stage |
加载流程:
```text
listSkills() → SkillIndexEntry[]
loadSkill(skillId) → ParsedSkill含 startupInquiry 解析自 ## 启动询问)
selectSkill(session, skillId) → session.slots.activeSkill
getStartupPrompt(activeSkill) → 流程 2 展示文案
buildSkillContext(activeSkill, blackboardIndex) → 总管 prompt 片段
```
---
## 10. 与 creation-playbook.md 的关系
`creation-playbook.md` 描述**概念与数据结构**Playbook、Stage、InquiryPolicy
**本文件**描述**落盘格式**SKILL.md 怎么写、放哪、启动时怎么选)。
关系:
```text
Creation Playbook概念
= SKILL.md存储+ stages.yaml可选结构化
+ session.slots.activeSkill运行时
```
`creation-playbook.md` 中的 TS 类型,实现时可从 SKILL.md 解析或从 stages.yaml 读取。
---
## 11. 第一版范围
```text
skills/ 目录 + 2 个示例 SKILL.mdnovel、theater
启动 → skill_selection → 用户选择 → 加载 skill
总管 prompt 注入 skill 摘要
registry.yaml 可选
```
不做:
```text
skill 可视化编辑器
运行时 LLM 自动生成 skill
与 Cursor .cursor/skills 混用(路径独立)
```

573
docs/tag-blackboard.md Normal file
View File

@@ -0,0 +1,573 @@
# 标签驱动黑板
## 1. 定位
本系统用于多 worker 协作式文本生成,覆盖:
```text
简易小说快速撰写quick-write
规则怪谈 / 短篇结构化创作weird-rules-short 等)
长篇小说 / 交互式写作助手interactive-novelTODO
场景扮演模拟scene-roleplayTODO
角色扮演 / 角色卡互动(并入 scene-roleplay 的 instantiate + run见 §2
```
核心思想:
```text
黑板 = 标签化数据池
标签 = skill 之间的接口
skill = SKILL.md 定义的能力worker = 一次 invoke
Agent = tool loop 内调度 invoke 哪个 skill
Runtime = 按 skill 契约拼接上下文(上半固定、下半动态)
```
一句话:**标签驱动 + agent 选 skill + Runtime 拼上下文**——不是固定流水线,也不是 LLM 自由分发上下文。
上下文拼接见 `docs/context-assembly.md`。不要让 agent 临场改 inputTags分发由 `inputTags` + `contextSegments` 静态声明。
---
## 2. Skill 定义、实例化与 Book
### 2.1 类比
```text
Skill 包orchestrator manifest + workers = 能力定义(静态)
Session + 黑板 tag = 一次运行的实参
Book = 过程、资产、游玩(见 book-storage.md
play stage = agent invoke run skill
```
**写角色卡、收集设定、启动询问** 都不是独立「产品模式」,而是 **实例化阶段** 的不同形态:把 prerequisite tags 写满,然后才进入运行阶段。
### 2.2 三层阶段(勿混淆)
```text
运行相位RuntimePhase
idle | running | waiting_user | done | error
系统在等什么。见 runtime-state-machine.md
业务 stageBook / Session
design实例化→ play运行→ done
tag 阶段(黑板条目 tag 名中的段)
候选 | 草稿 | 确认稿 | 当前 | 更新
单条数据的 lifecycle
```
业务 stage **写在各 skill 的 orchestrator.md**,不扩运行相位 enum。
### 2.3 实例化design
**职责:** agent 按需 invoke instantiate skill沉淀 `设计.*` tag产出 `设计.run_skill清单` 后 declare ready。
```text
选 orchestrator 包
→ design stage启动询问 → agent invoke instantiate skill能力库
→ declare_instance_ready
→ play stageagent invoke run skill
→ done → 归档 Book过程 + 资产,见 book-storage.md
```
可从 Book / CardAsset 加载已有 tag跳过部分 instantiate skill。
运行相位上,实例化阶段多为 `waiting_user(input)`(启动询问、总管 ask_user实例化也可调用 **setup worker**,但仍是 worker固定 inputTags/outputTags不是第二个生产总管。
### 2.4 每个 skill 声明什么
在 orchestrator.md 中写清(见 orchestrator-skill-format.md
```text
## 启动询问 / ## 实例化
prerequisiteTags本 skill 运行前必须有的 tag
instanceReadyWhen何时可进入 run stage文字条件 + tag 列表)
写入目标 tag可多个不必再塞进单一 book.brief
## 阶段定义
instantiate或沿用 stageId brief→ runwrite / review …)→ done
## Worker 编排
仅 run stage 及之后调度生产 workerinstantiate 阶段只 ask_user 或 run setup worker
```
### 2.5 已有雏形weird-rules-short
| 业务 stage | 现 stageId | 含义 |
|---|---|---|
| 实例化 | `brief` | 启动询问 → `book.brief`(≈ `需求.核心要点`)→ `startupCompleted` |
| 运行 | `write` | `write-rules` 产出规则与 core |
| 运行 | `review` | 双验收 worker |
| 结束 | `done` | finish |
`basic` 同理:`brief` = 实例化,`outline` worker = 运行。
文档与实现迁移时,可将 stageId 改名为 `instantiate`,或保留 `brief` 但在 ## 阶段定义 注明 **brief ≡ instantiate**
### 2.6 角色卡
不再单独维护 `character-card-author` skill 包。角色设定、口吻、行为边界等 tag 在 **扮演类 skill 的 instantiate 段** 收集或生成(可选 setup worker
若需跨 Session 复用,将 `角色.A.设定``角色卡.确认稿`**确认稿** 存入 Book新 Session **加载 Book** 而非再跑完整实例化。
### 2.7 Book 与黑板
```text
黑板Session 内) 运行时 tag 池worker 读写
Book项目级 长期实例accepted 确认稿归档;下一场 Session 可加载
```
详见 `docs/book-storage.md`。Session 是一次运行Book 是一个创作项目(一本小说、一个扮演项目)。
---
## 3. 黑板条目
### 2.1 最小结构
```ts
type BlackboardItem = {
id: string;
tag: string;
content: string;
source: string;
metadata?: Record<string, unknown>;
};
```
### 2.2 可选增强
```ts
type BlackboardItem = {
id: string;
tag: string;
content: string;
source: string;
scope?: string;
createdAt?: number;
updatedAt?: number;
dependencies?: string[];
metadata?: Record<string, unknown>;
};
```
### 2.3 字段含义
```text
id 唯一标识,追踪与依赖
tag 标签本体,决定身份与消费路径(核心)
content 具体内容(字符串)
source 产生该条目的 worker id调试用
scope 作用范围,如当前章节、场景、项目(可选)
dependencies 依赖的其他黑板条目 id可选
metadata 置信度、真实性、控制模式等(不参与基础路由)
```
注意:
```text
tag 是核心路由依据。
source 不是路由依据,只用于调试与溯源。
metadata 不参与基础路由,除非某 skill 明确约定。
```
**已废弃:** 旧模型的 `key``summary``tags[]``readableBy``writableBy` 作为路由字段。迁移期代码可能仍保留 `BlackboardEntry`,以本文为准逐步替换。
---
## 4. 标签原则
标签不是信息分类学,而是 **工作流接口**
### 3.1 设计原则
```text
标签越少越好,但必须能区分不同消费路径。
两个信息若永远被同一批 worker 消费,可合并 tag。
若在不同阶段被不同 worker 消费,必须拆分 tag。
若可能被误当成事实,必须加阶段段。
若涉及角色私有认知tag 里必须体现角色归属。
```
### 3.2 推荐格式
```text
对象.内容
对象.内容.阶段
领域.对象.内容.阶段
```
不强制四段式;按复杂度逐级增加。
示例:
```text
大纲.草稿
事件.草稿
正文.草稿
正文.确认稿
角色.A.行动.候选
角色.A.台词.候选
角色.A.内心想法.候选
角色.A.记忆.当前
```
### 3.3 标签替代的旧字段
```text
角色.A.内心想法.候选
```
已表达:归属 A、类型为内心想法、阶段为候选、消费路径由声明 inputTags 的 worker 决定。
**不需要** 再写 `owner``visibleTo``type=action` 等平行字段。
### 3.4 匹配规则Runtime
Worker frontmatter 声明 `inputTags`
```yaml
inputTags:
- "需求.核心要点" # 精确匹配
- "角色.A.*" # 前缀匹配tag 以「角色.A.」开头
- "大纲.*.草稿" # 前缀匹配
```
规则:
```text
无通配 → 精确匹配 tag
以 .* 结尾 → 前缀匹配(实现优先于完整正则)
多条命中 → 默认按 updatedAt 取最新;或 worker 声明 inputMerge: concat | latest
```
总管调度时也可对 **tag 索引**(不含 content做存在性判断例如「是否有 规则.确认稿」。
---
## 5. 阶段标签
阶段表示同一类信息在不同生命周期下的语义。
```text
候选 worker 生成的可能内容,不等于事实
草稿 生成中的文本或结构
确认稿 用户或流程确认,可进入长期状态
当前 当前生效状态
更新 状态变化结果
```
重要规则:
```text
候选 ≠ 已发生。
草稿 ≠ 确认稿。
角色行动候选不能直接写入长期记忆。
长期记忆优先从正文.确认稿 与明确 记忆.更新 生成。
```
验收(`user_confirmed` / `programmatic_review`通过后Runtime 将对应产物 tag 从 `.草稿` 升级为 `.确认稿`(或写入新的确认稿条目并标记旧草稿 superseded
---
## 6. Worker
每个 worker 是固定的标签消费者和生产者。
### 5.1 类型
```ts
type WorkerDefinition = {
id: string;
name: string;
description: string;
inputTags: string[];
outputTags: string[];
inputMerge?: "latest" | "concat";
run: (context: WorkerContext) => Promise<WorkerResult>;
};
type WorkerContext = {
taskId: string;
workerId: string;
items: BlackboardItem[];
params?: Record<string, unknown>;
};
type WorkerResult = {
items: BlackboardItem[];
logs?: string[];
askUser?: string[];
};
```
### 5.2 规则
```text
worker 只能读取 inputTags 声明的标签(含前缀规则)。
worker 只能输出 outputTags 声明的标签。
worker 不读取全量黑板。
LLM 不决定自己能看什么。
Runtime 校验 outputs 的 tag ⊆ outputTags。
```
### 5.3 Worker Skill 落盘
`docs/worker-skill-format.md`。正文写「怎么做」;`inputTags` / `outputTags` 写在 frontmatter。
### 5.4 ask_user
提问是 worker **能力**,不是独立 worker。中途提问时 `resumeContext` 保存 workerId**不**保存 inputTags恢复时仍从 Worker Skill 读 inputTags
---
## 7. 总管Main Agent
总管只负责流程推进。
### 6.1 职责
```text
判断当前任务属于哪种 skill / 业务阶段
选择下一个 workerrun_worker
判断是否需要追问用户ask_user
判断是否需要用户确认下一步requiresApproval
判断是否 finish
```
### 6.2 不负责
```text
不决定某条信息给谁看
不手动拼接 worker 上下文
不让 LLM 判断信息权限
不把全量黑板交给 worker
不在 run_worker 里指定 inputTags / outputTags
```
### 6.3 决策结构
```ts
type MainAgentDecision = {
id: string;
action: "ask_user" | "run_worker" | "create_temp_worker" | "review_blackboard" | "finish";
reason: string;
workerId?: string;
requiresApproval: boolean;
statePatchAllowed: false;
};
```
执行链:
```text
总管选择 worker
→ Runtime 读取该 worker 的 inputTags
→ 从黑板取匹配条目,组装 WorkerContext
→ 调用 worker
→ worker 输出固定 outputTags
→ 写回黑板
→ 按 acceptanceMode 验收
```
Tool 合约见 `docs/tool-contracts.md`
---
## 8. Skill 包与 manifest
Skill 包 = `orchestrator.md`manifest+ `workers/*/SKILL.md`。详见 `orchestrator-skill-format.md`
manifest 包含:
```text
Skill 注册表instantiate + run
验收策略
Instance Ready 规则
```
**不写** 逐步编排表。inputTags / contextSegments 在 Worker SKILL.md。
### 8.1 已启用包
| 包 | 说明 |
|---|---|
| `novel/basic` | 演示:需求 → 大纲 |
| `novel/weird-rules-short` | 规则怪谈:写 + 双验收 |
### 8.2 规划包(见 `skills/README.md`
| 包 | 说明 |
|---|---|
| `novel/quick-write` | **简易档**:几乎无 tag 路由,上下文全量给 LLM |
| `novel/interactive-novel` | 长篇 / 写作助手instantiate + 多轮 run |
| `novel/novel-standard` | 标准流水线(或与 interactive 合并) |
| `dialogue/scene-roleplay` | 场景扮演(含角色设定 instantiate + 互动 run |
---
## 9. 各模式核心 tag参考
实现 skill 时从下列词汇出发,不必一次全部实现。
### 8.1 简易小说quick-write
极简;可大量依赖 session + 全量上下文tag 仅作交付锚点:
```text
用户.输入
需求.摘要
正文.草稿
正文.确认稿
```
### 8.2 结构化短篇(如 weird-rules-short
迁移目标示例(与现 key 对照实施):
```text
需求.核心要点 ← book.brief
核心.危险.隐藏 ← core.danger
规则.草稿 ← rules.draft
规则.说明.草稿 ← rules.commentary
验收.读者视角.记录 ← review.infer.notes
验收.作者视角.记录 ← review.author.notes
```
### 8.3 交互式长篇
```text
用户.原始输入 | 用户.意图转述 | 用户.确认结果
项目.设定 | 项目.风格要求
大纲.当前 | 大纲.候选修改 | 大纲.确认稿
事件.当前 | 事件.确认稿
正文.原文 | 正文.续写锚点 | 正文.草稿 | 正文.确认稿
记忆.长期摘要 | 记忆.确认稿
```
### 8.4 场景扮演
```text
用户.行动输入 | 用户.行动意图
世界.规则 | 世界.当前状态 | 世界.隐藏状态
场景.可见信息 | 场景.隐藏信息
角色.{id}.记忆 | 信念 | 行动.候选 | 台词.候选
行动.裁决结果
输出.场景反馈
更新.世界状态 | 更新.角色状态
```
### 8.5 角色卡
撰写:`角色卡.草稿``角色卡.确认稿`
游玩:读 `角色卡.确认稿` + `用户.控制模式` + `NPC.*`
---
## 10. 正文 worker 与角色候选
角色 worker 输出 `角色.*.台词.候选` 等,**不**直接进入正文事实。
正文 worker 读取候选 + 风格约束,输出 `正文.草稿`;用户确认后为 `正文.确认稿`
```text
角色候选 → 提供意图
正文 worker → 文本化、风格化、叙事化
正文.确认稿 → 最终发生与表达
```
---
## 11. 可选增强
### 10.1 采用记录
```text
tag: 采用.记录
```
记录正文采用了哪些候选条目,避免未采用候选污染记忆。早期可省略,记忆 worker 只从 `正文.确认稿` 抽取。
### 10.2 真实性 / 置信度metadata
```ts
type TruthMode =
| "truth" | "belief" | "claim" | "lie" | "rumor" | "plan" | "unknown";
```
不参与基础路由。
### 10.3 RAG
RAG 不替代黑板。检索结果也应写成 tag例如 `角色.A.相关记忆摘要`,由 worker 通过 inputTags 读取。
---
## 12. 与阶段机的关系
运行相位、业务 stage、tag 阶段 **三者正交**(详见 §2.2
```text
运行相位 系统在等什么(见 runtime-state-machine.md
业务 stage instantiate → run → doneorchestrator.mdbrief 即 instantiate
tag 阶段 黑板条目 lifecycle候选 / 草稿 / 确认稿)
```
实例化阶段在运行相位上通常体现为 `waiting_user(input)`;进入 run stage 后为 `running` + worker 验收循环。
阶段机 enum 不增加 `instantiate` 相位——业务 stage 由 orchestrator + tag 索引判断。
阶段机规则本身不因 tag 迁移而改变。变的是黑板读写、worker 上下文、总管决策字段。
---
## 13. 与 Book 存储
Book 存长期实例;黑板存 **当前 Session** 的运行时 tag。实例化产物与 run 阶段确认稿可归档到 Book。
详见 `docs/book-storage.md` 与 §2.7。
---
## 14. 实现原则(必须遵守)
```text
1. worker 不读取全量黑板quick-write 等显式声明全量 inputTags 的 skill 除外)。
2. worker 只能读取 inputTags 声明的标签。
3. worker 只能输出 outputTags 声明的标签。
4. LLM 不决定上下文分发。
5. 总管 run_worker 时不带 inputTags / outputTags。
6. 标签是 worker 之间的接口。
7. 候选不等于事实;草稿不等于确认稿。
8. 正文 worker 可重写角色候选产物。
9. 长期记忆优先从正文.确认稿 更新。
10. 简单 skill 用简单 tag复杂 skill 再增加对象与阶段段。
11. 角色卡与设定在 instantiate 阶段写入 tag跨 Session 复用走 Book 加载,不单独 author skill 包。
12. 代码与旧文档冲突时,以本文为准改代码。
```
---
## 15. 代码迁移顺序(参考)
```text
1. src/types/blackboard.ts → BlackboardItem + listByTag / matchPrefix
2. src/skills/types.ts + loader → 解析 inputTags / outputTags
3. src/worker/executor.ts → 按 tag 组装 context校验 outputTags
4. src/types/runtime.ts + main-agent → 决策去掉 inputKeys / outputKeys
5. skills/novel/weird-rules-short → 第一个 tag 化样板
6. skills/novel/quick-write → 简易全量 LLM 档
```
当前代码仍为旧 key 模型;实现前以本文为规格。
---
## 16. 相关文档
| 文档 | 内容 |
|---|---|
| `orchestrator-skill-format.md` | 总管 orchestrator.md 写法 |
| `worker-skill-format.md` | Worker SKILL.md 写法 |
| `tool-contracts.md` | 总管 / worker tool |
| `skill-format.md` | Skill 包存储 |
| `runtime-state-machine.md` | 5 相位阶段机 |
| `implementation-guide.md` | 写代码顺序 |
| `skills/README.md` | Skill 包索引与 TODO |

125
docs/tool-contracts.md Normal file
View File

@@ -0,0 +1,125 @@
# Tool 合约
## 1. 定位
LLM 通过 **tool call** 表达意图Runtime 校验后执行 tool 或转为 **RuntimeEvent** 改变 phase。
**真 tool loop**:循环 tool 的返回值 append 到 agent `messages[]`;边界 tool 结束 burst。
不依赖「副作用列表」隐式推进(迁移方向,见 `architecture.md`)。
黑板与上下文:`tag-blackboard.md``context-assembly.md`
---
## 2. 调用者
| 调用者 | 可用 tool | 禁止 |
|--------|-----------|------|
| 总管 Agent | 见 §3 | 改 phase 直接写黑板;传 inputTagsaccept 产物 |
| Worker LLM | `ask_user``submit`(目标态) | 调度其他 skill写未声明 tag |
| 用户 / CLI | submit_input、approve、reject、accept | — |
| Runtime | 发射 event、拼接上下文、执行 worker | — |
---
## 3. 总管 Tool
### 3.1 循环 toolburst 内,不改 phase
| name | 作用 |
|------|------|
| `read_blackboard` | `tags: string[]` 读正文 |
| `list_workers` | 当前包可调度 skill 列表 |
| `list_artifacts` | 产物状态 |
### 3.2 边界 tool结束 burst
| name | 行为 |
|------|------|
| `run_worker` | 执行 skill`requiresApproval``approve_step` |
| `ask_user` | `waiting_user(input)` |
| `review_blackboard` | 向用户展示概况 → `input` |
| `finish` | `done` |
```ts
type RunWorkerParams = {
workerId: string;
reason: string;
requiresApproval: boolean;
roleId?: string;
};
```
Runtime 从 Worker Skill 读 `inputTags` / `outputTags`,总管 **不得传入**
### 3.3 Tool loop burst
```text
计数器 toolLoopBurstCount 在每次用户硬事件时归零:
user_submitted_input、user_confirmed_intake、user_approved_next_step、
user_rejected_next_step、user_accepted_artifact、user_rejected_artifact、…
running 内每轮 LLM+tool 使 burst+1超过 maxBurst默认 12可配置→ 强制 waiting_user
```
**maxBurst = 两次用户操作之间的上限**,非 Session 累计。
代码:`src/main-agent/tool-loop.ts``src/runtime/tool-registry.ts`
---
## 4. Worker Tool目标态
| name | 行为 |
|------|------|
| `ask_user` | `worker_questions` + resumeContext |
| `submit` | 校验 tag ⊆ outputTags → 写黑板 → `worker_completed` |
Phase A 仍用 JSON `outputs` + `askUser`,语义等价。
---
## 5. 用户硬事件
| waitingReason | 用户动作 |
|---------------|----------|
| `skill_selection` | 选包 |
| `intake` / `input` | 输入 |
| `approve_step` | approve / reject |
| `review_artifact` | accept / reject |
| `worker_questions` | 输入 |
| `revision` | 输入修改说明 |
---
## 6. 校验
```text
1. phase + waitingReason 允许该 actor
2. tool 参数 schema
3. workerId ∈ manifest 注册表
4. submit tag ⊆ outputTags
5. 总管不得带 inputTags
6. burst ≤ maxBurst
```
---
## 7. 代码对应
| 章节 | 文件 |
|------|------|
| 总管 tool loop | `src/main-agent/tool-loop.ts` |
| tool 定义 | `src/main-agent/tools.ts` |
| 解析/校验 | `src/runtime/tool-registry.ts` |
| worker | `src/worker/executor.ts` |
| 阶段机 | `src/runtime/phase-machine.ts` |
---
## 8. 已废弃
- 总管 JSON 一次性决策(保留 fallback 解析)
- `inputKeys` / `outputKeys`
- orchestrator 思维链逐步调度
-`PhaseEffect.invoke_main_agent` 链为主的路径(迁向 burst 入口)

279
docs/worker-skill-format.md Normal file
View File

@@ -0,0 +1,279 @@
# Worker Skill 格式
## 1. 定位
**Worker Skill** 服务 **Worker Agent**:规定 **读哪些 tag、写哪些 tag、怎么做** 本阶段产出。
Worker **从属于某一个总管 Skill 包**,不与其它 skill 共享。
```text
总管run_worker(write-rules)
→ Runtime 读 workers/write-rules/SKILL.md 的 inputTags / outputTags
→ 从黑板取匹配条目 → Worker 执行 → 写回 outputTags
```
规格背景见 `docs/tag-blackboard.md``docs/context-assembly.md`
---
## 2. 存储位置
```text
skills/novel/weird-rules-short/
├── orchestrator.md
└── workers/
├── write-rules/SKILL.md
└── review-infer/SKILL.md
```
- 目录名 = 包内 **worker id**
- 文件统一 **`SKILL.md`**。
- **没有** 全局共享 worker 目录。
---
## 3. Frontmatter
```yaml
---
id: write-rules
skill: weird-rules-short
name: 规则与解析创作
description: >-
从 需求.核心要点 推演内部核心,产出规则与说明。
version: 1
inputTags:
- "需求.核心要点"
- "验收.读者视角.记录"
- "验收.作者视角.记录"
- "用户.修改说明"
outputTags:
- "核心.危险.隐藏"
- "规则.草稿"
- "规则.说明.草稿"
inputMerge: latest
---
```
| 字段 | 用途 |
|------|------|
| `id` | 包内 skill id与 manifest 注册表一致 |
| `skill` | 所属 orchestrator 包 name |
| `inputTags` | Runtime 从黑板取数的 tag精确或 `前缀.*` |
| `outputTags` | 允许写回的 tagRuntime 校验 |
| `inputMerge` | 可选,`latest`(默认)或 `concat` |
| `contextSegments` | 可选,上下文拼接:上半 static、下半 dynamic见 §3.1 |
| `contextIsolation` | 可选:`none` \| `role_pov` \| `blind_review` |
### 3.1 contextSegments上下文拼接
`docs/context-assembly.md`。示例:
```yaml
contextSegments:
- id: brief
tier: static
tags: ["book.brief"]
label: "## 创作需求"
- id: history
tier: dynamic
tags: ["运行.事件流"]
policy: tail_lines_80
- id: turn
tier: dynamic
tags: ["可见信息", "用户.最新输入"]
label: "## 本轮"
```
未声明时 Runtime 回退为 JSON `inputs`(当前实现)。
**review-infer 示例**(不得读隐藏核心):
```yaml
inputTags:
- "需求.核心要点"
- "规则.草稿"
- "规则.说明.草稿"
outputTags:
- "验收.读者视角.记录"
```
**review-author 示例**(可读隐藏核心):
```yaml
inputTags:
- "需求.核心要点"
- "核心.危险.隐藏"
- "规则.草稿"
- "规则.说明.草稿"
outputTags:
- "验收.作者视角.记录"
```
---
## 4. 正文章节
```markdown
# 标题
## 角色与口吻
## 能力范围 # 能做什么 / 不能做什么
## 思维链与自检
## 上下文用法 # 各 inputTag 如何使用(不重复 frontmatter 列表)
## 输出格式 # 各 outputTag 的 content 格式
## 示例 # 可选
```
正文中用 **tag 名** 指代上下文,例如「读 `需求.核心要点`」而非旧 key `book.brief`
### 评估类 Worker
总管 orchestrator 只写:`rules 确认后 → run review-infer`
本 SKILL 写 **评估怎么做**、verdict 写入 `验收.*.记录` 的 JSON 形状等。
### 用户回合 workeruser-turn
**用途:** 该环节 **完全由用户输入** 组成LLM 不替用户选行动21 点玩家、线下人类一方等)。
**与 role-decide 的区别:**
| | role-decide | user-turn |
|--|-------------|-----------|
| 决策 | LLM 产出 `.思考` + `.行动` | 用户经 ask_user 提供worker **只**写 `.行动` |
| LLM | 需要 | 仅需展示/校验/格式化(可无生成模型) |
**frontmatter 示例:**
```yaml
id: user-turn
skill: blackjack-roleplay
name: 用户回合
description: 展示局面,收集用户合法行动,写入角色.用户.行动
inputTags:
- "角色.用户.可见信息"
- "场景.公开叙述"
outputTags:
- "角色.用户.行动"
```
**SKILL 正文要点:**
```markdown
## 角色
你是 **用户操作的采集器**,不是玩家 AI。禁止替用户选择行动。
## 执行
1. 读可见信息与合法行动集
2. ask_user简短展示局面 + 列出可选行动
3. 校验用户输入是否在合法集内;不合法则再问
4.`角色.用户.行动`(行动选择 + 可选说话)
## 禁止
- 调用 LLM 模拟用户策略
- 写入 `.思考`(用户无内心 tag或仅 UI 留空)
```
编排:总管在轮到用户时 `run_worker(user-turn)`world-engine 与 role-decide **同一套**`.行动` 规则。
---
## 5. 运行时输出协议
Worker LLM 返回 JSONPhase APhase B 改为 tool call。语义不变
```json
{
"outputs": {
"规则.草稿": "...",
"规则.说明.草稿": "..."
},
"summary": "50字以内摘要",
"askUser": null
}
```
- `outputs` 的 key 必须是 **outputTags 中的 tag**(或与 tag 一一映射的别名,由 Runtime 归一化)。
- 缺信息时 `askUser` 提问,不臆造。
Runtime 写黑板:
```ts
{
id: "...",
tag: "规则.草稿",
content: "...",
source: "write-rules",
}
```
---
## 6. ask_user
任何 worker 可中途提问。Runtime 暂停并保存 `resumeContext`workerId 等);恢复时 **重新** 从 SKILL 读 inputTags不依赖总管。
---
## 7. 命名原则
Worker id 按 **本包流程职责** 命名,包内唯一:
| 包 | worker id | 职责 |
|----|-----------|------|
| weird-rules-short | write-rules | 写规则 |
| weird-rules-short | review-infer | 读者视角验收 |
| novel-standard | outline | 大纲 |
不要设计全局共享 worker id。
---
## 8. 与代码的关系
| 文档 | 代码 |
|------|------|
| frontmatter inputTags / outputTags | `src/skills/loader.ts``ParsedWorkerSkill` |
| 运行时取数 | `src/worker/executor.ts` |
| 角色 worker 独立 LLM | `llmProfileId` / `llm-bindings.yaml` | `src/skills/worker-llm.ts` |
当前代码仍为旧 `inputKeys` / `outputKeys` 模型;迁移以 `tag-blackboard.md` 为准。
---
## 9. Worker 独立 LLM可选预留多 AI 博弈)
默认worker 与会话 **同一 ApiProfile**(设置页当前选中的 profile
### 9.1 Worker SKILL frontmatter
```yaml
llmProfileId: "<profiles.json 中的 ApiProfile.id>"
```
省略 = 走 skill 包 `llm-bindings.yaml` 或会话默认。
### 9.2 Skill 包 llm-bindings.yaml
```yaml
defaultProfileId: null # null = 会话默认
workers:
world-engine: {}
role-decide:
byRole:
A: "<profile-id-1>"
B: "<profile-id-2>"
```
Runtime 解析顺序见 `src/skills/worker-llm.ts`
`role-decide``slots.世界.当前角色.id` 匹配 `byRole`
### 9.3 设计意图
- 配置仍在 **profiles.json**(或 .env不在 SKILL 里写密钥
- 同一 skill 可让不同角色用不同模型/API实现真实多 agent 博弈
- 总管 LLM 不受 worker 绑定影响(始终会话默认)
---