# Pi Session Tree 与角色状态操作语义

> 本文定义 pi-roleplay 如何服从 Pi 原生 Session v3 entry tree。它是后续撤销、编辑、fork、checkpoint 和 branch summary 实现的设计约束。

## 1. 唯一时间线边界

Pi Session entry 通过 `id` / `parentId` 组成树；当前剧情只等于当前 leaf 到 root 的路径：

```text
Canonical Roleplay Timeline
  = ctx.sessionManager.getBranch()
```

插件不得使用：

```text
JSONL 文件最后一行
全文件中 revision 最大的 Commit
外部 current-state.json
另一个 branch 的 checkpoint
```

所有角色状态 entry 都必须成为当前 leaf 的子节点：

```text
user
└─ assistant
   └─ roleplay_finalize_turn toolResult
      └─ review decision / checkpoint / correction
```

因此 Pi 自身的 `/tree`、`/fork`、`/clone` 和 `/resume` 才是剧情历史导航的权威机制。

## 2. 三种不同操作不能混淆

| 用户意图 | 正确机制 | 是否追加剧情状态 Commit |
|---|---|---:|
| 回到过去并从那里重写剧情 | Pi `/tree` | 否 |
| 从某节点复制成独立 Session | Pi `/fork` / `/clone` | 否，继承目标路径已有 entry |
| 当前时间线不变，只纠正一个错误字段 | Correction Commit | 是 |

### 2.1 历史撤销：使用 Tree

```text
A 初识
└─ B 剪发
   └─ C 抵达王都       ← old leaf

/tree 到 A
└─ D 拒绝剪发
   └─ E 前往南区       ← new leaf
```

Reducer 在 `session_tree` 后读取新 branch：

```text
A → D → E
```

所以 B、C 上的短发、地点、关系、记忆和待审项自然不再生效，不需要 inverse Commit。

### 2.2 独立会话：使用 Fork

```text
原 Session: A → B → C
                    ↑ 选择 B fork

新 Session: A → B
```

Pi 新 Session header 记录 `parentSession`，并复制到目标节点为止的活动路径。插件在新实例的 `session_start(reason="fork")` 中只需对新 Session 的 `getBranch()` 运行同一 reducer。

禁止在 `session_before_fork` 将当前 old leaf 的运行时 state 强行写入新 Session，因为这会把 B 之后的 C 状态泄漏进 fork。

### 2.3 当前线纠错：使用 Correction Commit

如果用户的意图不是改写历史，而是说：

```text
“刚才记错了，钥匙其实还在爱丽丝手里。”
```

则追加：

```text
Correction Commit
source = manual-correction
correctsCommitId = commit-...
```

它表达“当前时间线中的记录被纠正”，而不是 `/tree` 意义上的历史撤销。

第一版不应提供含糊的 `/rp commit revert`。命令名称应明确：

```text
/rp history goto <entry-id>   # 包装 Pi navigateTree，可选；默认推荐 /tree
/rp history fork <entry-id>   # 包装 Pi fork，可选；默认推荐 /fork
/rp state correct <json>      # 当前 branch 追加纠正（已实现）
```

`/rp state correct` 只接受单个 JSON 参数，没有位置参数形式；`reason` 必填，为空会被拒绝：

```text
/rp state correct {"op":"replace","path":"/location","from":"王都南门","value":"旧城区旅店","reason":"地点记录错误","correctsCommitId":"commit-..."}
```

`from` 与 `correctsCommitId` 可选，但补上 `correctsCommitId` 才能形成完整的 provenance 链。

## 3. Tree 导航恢复流程

```mermaid
sequenceDiagram
    participant U as 用户
    participant Pi as Pi Session Tree
    participant RP as pi-roleplay
    participant R as Branch Reducer
    participant LLM as 下一轮模型

    U->>Pi: /tree 选择目标 entry
    Pi->>RP: session_before_tree(targetId, oldLeafId)
    RP->>RP: 不复制 old leaf 状态
    Pi->>Pi: branch(targetId) / branchWithSummary
    Pi->>RP: session_tree(newLeafId, oldLeafId)
    RP->>R: reduce(getBranch())
    R-->>RP: 目标 branch 的 state/events/memories/reviews
    RP->>RP: 替换内存 runtimeState
    RP->>LLM: 下一轮注入新 Current Character View
```

`session_tree` 必须执行：

1. 重新解析该 branch 的 Session Role Identity；角色首次回复或产生结构化状态后身份锁定，不能把 selection 当作运行时热切换；
2. 重新运行绑定角色的 reducer，并过滤其他 `characterId` 的 legacy Commit/Review/Turn/Checkpoint；
3. 清除旧 branch 的运行时缓存；
4. 更新 footer/status；
5. 下一轮重新检索该 branch 相关 memory 和 world context。

不能复用导航前的 `runtimeState.current`。

## 4. Branch Summary 的非 Canon 边界

Pi `/tree` 可为被放弃的 branch 生成 `branch_summary`，它会进入 LLM context。例如：

```text
在被放弃的分支中，爱丽丝剪短了头发并抵达王都。
```

这段摘要用于帮助通用 coding agent 记住另一条路线，但在角色扮演中不能自动成为当前时间线事实。

### 4.1 结构化状态规则

Reducer 永远忽略：

```text
branch_summary.summary
compaction.summary
普通自然语言文本
```

状态只来自当前 branch 上受支持的结构化 entry：

```text
roleplay_finalize_turn toolResult.details
pi-roleplay-commit          # 含 manual-correction 来源的 Correction Commit
pi-roleplay-review-decision
pi-roleplay-review-amendment
pi-roleplay-checkpoint
pi-roleplay-turn-status
pi-roleplay-repair
pi-roleplay-archive
```

### 4.2 给模型的运行时规则

角色模式下默认采用更严格策略：`context` hook 从发往模型的瞬时消息副本中移除 `role=branchSummary`。Session tree 原 entry 不被修改，通用 Pi 历史与审计能力仍保留。

同时，角色 system/runtime boundary 继续明确：

```text
Branch summary may describe an abandoned alternate timeline.
Do not treat it as current roleplay canon unless the Current Character View
or current-branch messages independently establish the same fact.
```

中文语义：

> 分支摘要可能描述已放弃的另一条时间线。除非 Current Character View 或当前分支消息独立确认，否则不得把摘要中的剧情事实视为当前 canon。

### 4.3 可选自定义摘要

后续可在 `session_before_tree` 中，仅当 `userWantsSummary` 时提供角色专用 summary，分为：

```text
## Abandoned Alternate Timeline — Non-Canon
- 旧分支发生了什么

## Current Branch Canon Boundary
- commonAncestorId
- 被放弃内容不得更新 Current State / Memory
```

但第一版不必接管 Pi 的摘要模型；先加入明确的 non-canon runtime boundary 和测试即可。

## 5. Fork 继承规则

Pi 的 fork 创建独立 Session 文件。插件恢复必须遵守：

```text
Forked State
  = reduce(newSession.getBranch())
```

而不是：

```text
Forked State
  = oldRuntimeState
```

必须测试两种 position：

| Pi 操作 | 目标语义 | 状态边界 |
|---|---|---|
| `fork(entryId, { position: "before" })` | 在选中的 user prompt 之前分叉 | 不含该 prompt 之后的 Commit |
| `fork(entryId, { position: "at" })` | 复制到选中 entry 本身 | 只含该 entry 已经位于路径上的状态 |

`session_start(reason="fork")` 后扩展实例已被替换。任何 `withSession` 代码只能使用新回调 `ctx`，不能使用旧 `pi`、旧 `ctx.sessionManager` 或旧 runtimeState。

## 6. Checkpoint 必须属于树节点

Checkpoint 不是全局缓存，而是 branch entry：

```text
A ─ B ─ checkpoint-X ─ C
 \
  D ─ checkpoint-Y ─ E
```

在 C branch：

```text
可见 checkpoint = X
```

在 E branch：

```text
可见 checkpoint = Y
```

Reducer 只能从 `getBranch()` 参数中选择最新有效 checkpoint。不得扫描 `getEntries()` 后选择全文件最新 checkpoint。

### 6.1 Tree 返回 checkpoint 之前

```text
A ─ B ─ checkpoint-X ─ C
↑ /tree 到 A
```

新 branch 不包含 checkpoint-X，Reducer 必须从 revision 0 或 A 之前的 checkpoint 开始。

### 6.2 Compaction checkpoint

Compaction entry 只改变 LLM context 构造，不改变完整 `getBranch()` 路径。自动 checkpoint 应作为 compaction 后当前 leaf 的子 entry 追加：

```text
... → compaction → pi-roleplay-checkpoint
```

如果稍后 `/tree` 回到 compaction 之前，该 checkpoint 自然不在新 branch 中。

## 7. Review、Incomplete 与 Memory 同样 branch-local

以下记录全都必须随 branch 回退：

```text
Pending Review
Review Decision
Incomplete Turn Marker
Repair Commit
Event
Memory
Checkpoint
Correction Commit
```

例子：

```text
A 救援事件
└─ B pending trust review
   ├─ C accept trust=0.55
   └─ D reject
```

C branch：

```json
{
  "relationship": {
    "user": { "trust": 0.55 }
  }
}
```

D branch：

```json
{}
```

两个 branch 都可保留 A 的客观救援 Event，但拥有不同的关系状态与 Review Decision。

## 8. 修订后的命令设计

### 8.1 使用 Pi 原生命令处理历史

```text
/tree       浏览并切换当前 Session 内的 branch
/fork       从某节点创建新 Session
/clone      复制活动路径到选中 entry
/resume     切换 Session 文件
```

pi-roleplay 不重复实现另一套历史栈。

### 8.2 插件命令只处理结构化状态

```text
/rp inspect state
/rp review list|amend|accept|reject
/rp checkpoint
/rp state correct <json>
/rp turn repair <proposal-json>
/rp archive
```

以上命令均已实现。完整调用形式见 [`../README.md`](../README.md) 的「手动写入、纠错与修复」一节。

`/rp state correct` 必须在 UI 中说明：

```text
这会在当前 branch 追加纠正，不会移动 Session Tree。
若要从过去重写剧情，请使用 /tree 或 /fork。
```

## 9. E2E 测试矩阵

### 9.1 In-place Tree

```text
1. Alice 齐肩黑发
2. Commit：剪为耳下
3. 记录 short-hair entry ID
4. /tree 到剪发前 user/assistant 节点
5. 发送拒绝剪发的新消息
6. 断言 revision 回到 0
7. 断言 payload 不含耳下短发
8. 断言旧 branch 仍可再次导航并恢复短发
```

### 9.2 Fork Before

```text
1. 原 Session 已有剪发 Commit
2. fork 到剪发 user message 之前
3. 新 Session header.parentSession 指向原文件
4. 新 Session reducer 不含剪发 Commit
5. 原 Session 仍保持短发
```

### 9.3 Clone At

```text
1. clone 到剪发 toolResult entry
2. 新 Session 包含该 Commit
3. reducer 恢复短发
4. 后续状态只写入新 Session
```

### 9.4 Pending Review Branches

```text
救援 Event
→ Pending trust review
├─ Branch accept → trust=0.55
└─ Branch reject → 无 trust override
```

### 9.5 Branch Summary Pollution

```text
旧 branch：剪发
/tree 到剪发前，并生成 summary
新 branch：拒绝剪发

断言：
- reducer state 无 short hair
- Current Character View 无 short hair
- system 含 alternate-summary non-canon boundary
- 模型不会仅凭 branch summary 声称当前短发
```

### 9.6 Checkpoint Isolation

```text
Branch A checkpoint: short hair
Branch B checkpoint: shoulder-length/no override

断言两个 reducer 只读取各自路径上的 checkpoint。
```

## 10. 实施顺序

```text
1. 将本文的 Tree-first 语义加入 runtime boundary
2. 为 reducer fixture 使用真实 `SessionManager` 的 id/parentId 构造多分支树（已完成）
3. 验证 leaf 导航与 branched session extraction 的不同投影（已完成）
4. 从角色 Provider context 移除 branch summary、保留 Session entry（已完成）
5. 接入 branch-local automatic compaction checkpoint（已完成）
6. 使用扩展命令调用真实 `navigateTree` 生命周期 E2E（已完成）
7. 使用扩展命令调用真实 fork before / clone at 替换生命周期 E2E（已完成）
8. 纯 TUI `/tree` 选择器与 `/fork` 选择器保留人工验收（未完成）
9. 最后才实现 `/rp state correct`（已完成）
```

## 11. 自动化生命周期 E2E

```bash
pnpm test:e2e:session-tree
```

测试通过仅用于 E2E 的扩展命令调用 Pi 受支持 API：

```text
ctx.navigateTree(...)
ctx.fork(..., { position: "before" })
ctx.fork(..., { position: "at" })
ctx.compact(...)
```

断言包括：

- `session_before_tree` / `session_tree` 都真实触发；
- 导航到剪发前路径时 revision 为 0，再回到 Commit 路径时 revision 为 1；
- fork before 的新 Session 不继承剪发；
- clone at ToolResult 的新 Session 继承剪发；
- 两个新 Session header 的 `parentSession` 指向源 Session；
- Compaction 真实触发 hooks；
- 自动 Checkpoint 是 compaction entry 的后代；
- Compaction 前后 revision 与 state hash 一致。

该自动化覆盖 Pi 的核心命令上下文和 replacement lifecycle，但不模拟终端方向键、选择器渲染或按键确认。纯 TUI 外观与键盘操作仍需一次人工验收。

补偿式 Commit 不再叫“回滚”，只作为当前 branch 的显式纠错。真正的历史撤销、重写和分叉全部交给 Pi Session Tree。
