# Intent Petri V2｜确定的当前执行栈与暂定的未来路径

> 状态：V2 首发于 `intent-petri@0.2.0`；`0.2.1` 增加旧 checkpoint 兼容恢复、扩展来源诊断，并将所有可视化从 Pi UI 移到 Herdr plugin pane；当前 `0.2.2` 修复 npm `.bin` symlink 启动。Pi 不再显示 `STACK / NOW / WHY / NEXT / WATCH`、modal 或 float pane；Mermaid 投影改为 TD、typed labels 与 18 列自动换行。下文原 Pi Pane 章节保留为历史设计记录，已由 Herdr viewer 架构取代。完整验收记录见 [`docs/implementation/V2_ACCEPTANCE.md`](docs/implementation/V2_ACCEPTANCE.md)。
>
> 本文是 [`docs/design/INTENT_PETRI_NET.md`](docs/design/INTENT_PETRI_NET.md) 在 Slice 1 实际使用后的 V2 收敛设计。它不推翻 Workflow Net Core，而是修正当前步骤、层级下钻、未来计划、历史和图形投影的表达方式。

## 一句话决策

Intent Petri V2 将“当前有多深”和“未来有多确定”拆成两个独立维度：

- **当前执行栈可以不断下钻，但从总体目标到 active leaf 的整条路径必须确定、可解释、可回溯；**
- **未来路径允许模糊、分支、实时变化，并必须显式标记为“暂定，随当前结果调整”；**
- Pi 当前 branch 上已验证的 committed checkpoints 仍是 branch authority；GraphPatch batches 是变更历史，WorkflowState 是 fold 后的快照，Mermaid、文件和 Herdr Pane 都是可重建投影。

## 为什么需要 V2

Slice 1 已经证明了 revisioned GraphPatch、单 token Workflow Net 和 Pi branch reconstruction 可以稳定工作，但实际体验暴露出四个问题：

1. 当前视图只有少量节点，看不出完整任务结构；
2. 当前步骤下钻为多个子步骤后，用户无法看到“我位于哪一层”；
3. 完成、失败、改道和未来计划的变化已经被记录，却没有用户可读的历史；
4. 当前 `NEXT` 只显示少量一跳后继，未来路径既少，也没有表达“它只是暂定”。

这些问题不能只靠换一个渲染器解决。V2 必须同时调整路径语义、层级模型、历史投影和界面。

## 核心语义修正

### “当前确定”不等于“结果已知”

当前步骤确定，指的是以下内容没有歧义：

- Agent 当前服务于哪个总体目标；
- 当前处于哪个阶段；
- 正在执行哪个具体 leaf Transition；
- 为什么现在执行；
- 期待什么证据；
- 什么条件下退出；
- 失败后如何处理。

当前动作可以是“调查未知根因”，因此调查结果仍然未知。确定的是**最近一次有效声明中的 operational intent 和执行位置**，不是调查结论，也不是声明必然与真实行为一致。

路径声明的准确性继续由 `pending / aligned / lagging / diverged / unknown` 调和状态表达。若当前没有可执行 leaf，界面必须明确显示 token 正处于 Place、Gate 阻塞、等待重新锚定、已经完成或 checkpoint 无效，不能伪造一个 active step。

### 深度与确定性是两个维度

旧的 Action Horizon 容易把“越具体”与“越靠近当前”混成一个轴。V2 改为两个正交维度：

```text
维度 A：Refinement Depth
总体目标 → 阶段 → 任务 → 子任务 → active leaf

维度 B：Plan Commitment
committed → provisional → directional
```

因此：

- 当前可以下钻很多层，每一层仍然属于确定的 Current Execution Stack；
- 一个很近的未来节点也可以是 provisional；
- 一个很远的方向节点只需要表达结果，不需要伪造步骤；
- 图形视图不能再用“节点离 token 近，所以一定具体”作为唯一规则。

## 两个主要视图对象

### Current Execution Stack｜当前执行栈

Current Execution Stack 是从 North Star 到 active leaf 的唯一层级路径：

```text
恢复可靠的 npm 发布流程
  └─ 验证 Pi 用户级安装
      └─ 评估真实使用体验
          └─ 设计 V2 图形投影
              └─ ▶ 定义当前与未来的确定性边界
```

约束：

1. 单 Agent、单 token 模式下，任一时刻最多只有一个**可执行 active leaf Transition**；初始化、停在 Place、blocked、complete 或 unanchored 时可以为零；
2. active leaf 的祖先 shell 可以同时处于 `active` 生命周期状态，但它们不能再次 firing，也不各自持有 token；
3. 所有 active Transition 必须组成一条从外层 shell 到 leaf 的线性 refinement chain，不能形成两个并行 active 分支；
4. token 只存在于最深 active subnet 的形式 marking 中；
5. active leaf 必须具有完整 operational contract；
6. 任一祖先至少声明目标、范围和退出条件；
7. 下钻深度不设产品级硬上限，但 UI 默认只展开 active stack 和当前 subnet；
8. read/edit/bash 等工具动作仍只进入 Evidence，不自动成为子步骤。

V2 将 Slice 1 的单值 `activeTransitionId` 明确收窄为 `activeLeafTransitionId`；祖先 shell IDs 从 refinement records 派生：

```ts
interface ExecutionPosition {
  mode: "at_place" | "executing" | "blocked" | "complete" | "unanchored";
  activeLeafTransitionId?: string;
  activeShellTransitionIds: string[]; // outermost → innermost
  tokenPlaceId: string;
  alignment: "pending" | "aligned" | "lagging" | "diverged" | "unknown";
}
```

“当前步骤很多”应通过 hierarchical refinement 表达，而不是把所有工具调用平铺成主图节点。

### Future Planning Cone｜未来计划锥

Future Planning Cone 从当前 leaf 的成功、失败和待观察结果向右展开。它允许包含多个条件分支和不同承诺级别。

```text
                                  ┌─ ⧖ 暂定：实现 SVG 历史浏览
▶ 当前：定义 V2 语义 ── 设计确认 ─┤
                                  ├─ ⧖ 暂定：先验证终端 ASCII 可读性
                                  └─ ◇ 方向：形成可长期回放的意图地图
```

未来变化是正常行为，不视为路径错误。重要的是：

- 当前看到的未来是什么；
- 它为何只是暂定；
- 它依赖当前步骤产生什么结果；
- 后来为什么被修改、替代或放弃。

Observer 可以提出未来候选，但 proposal 不直接进入 Workflow Net Core。Future Planning Cone 可以用虚线单独显示 observer proposal；只有 Agent 或 Human 将其接受为正式 revisioned GraphPatch 后，它才获得稳定节点 ID、commitment 和激活资格。

Proposal 使用独立的 branch-scoped lifecycle：

```ts
type ProposalDraftOp =
  | CreatePlaceOp
  | CreateTransitionOp
  | ConnectOp
  | SetTransitionPlanningOp
  | RefineTransitionOp;

interface PathProposal {
  proposalId: string;
  baseRevision: number;
  checkpointEntryId: string;
  source: "observer";
  modelVersion: string;
  createdAt: string;
  expiresAfterRevision?: number;
  draftOps: ProposalDraftOp[];
  evidenceIds: string[];
  status: "pending" | "accepted" | "rejected" | "expired";
  decisionReason?: string;
  acceptedPatchId?: string;
}
```

- proposal 使用 `proposal.submitted` Pi custom entry 持久化并由 SQLite 镜像，不进入 WorkflowState marking；
- `PathProposal.status` 是对 `proposal.submitted / accepted / rejected / expired` entries 的 fold，不修改旧 entry；
- proposal 内节点使用 `prnode_*` 临时 ID，不能被正式 Arc、Evidence 或 token 引用；
- draft ops 只能描述未来结构，不能包含 activate、complete、fail、mark gate 或 place-state mutation；
- `accept_path_proposal` 由 Agent/Human 提交，在当前 revision 上把 draft ops 转成新的 GraphPatch，并以 `correlationId = proposalId` 保留 provenance；
- 接受时由正式 patch 创建稳定节点 ID，并追加 `proposal.accepted` entry，记录 `acceptedPatchId` 和临时 ID → 稳定 ID mapping；
- `reject_path_proposal` 追加 `proposal.rejected` entry，并必须记录 decisionReason；
- 超过 `expiresAfterRevision` 或 base lineage 不再是当前 branch 祖先时确定性标记 expired；
- Observer 无权接受自己的 proposal。

## Plan Commitment 模型

Transition 增加独立于 `status` 的计划承诺级别：

```ts
export type PlanCommitment =
  | "committed"
  | "provisional"
  | "directional";
```

### committed｜已承诺

含义：

- 当前 active leaf 及其 active ancestor shells 必须是 committed；
- immediate next 可以 committed，表示当前声明已选择该路径；
- committed 仍可改变，但必须通过 revisioned patch 说明原因；
- 激活前必须有完整 operational contract。

显示：

```text
◆ 已确定
```

### provisional｜暂定

含义：

- 当前认为最可能采用，但依赖尚未出现的证据或结果；
- 允许实时修改、替换或退役；
- 激活前必须重新确认并提升为 committed；
- 至少声明依赖条件或 reconsider trigger。

默认显示文案：

```text
⧖ 暂定 · 随当前结果调整
```

### directional｜方向

含义：

- 只表达目标、约束或远期结果；
- 不承诺具体顺序；
- 不要求 why-now、具体证据选择器或失败策略；
- 接近 token 时必须先 refinement，再变为 provisional 或 committed。

显示：

```text
◇ 方向 · 尚未展开
```

### status 与 commitment 不重复

`TransitionStatus` 继续描述生命周期：

```text
planned | active | completed | failed | retired | superseded
```

`PlanCommitment` 描述计划声明强度。合法组合示例：

| Status | Commitment | 含义 |
|---|---|---|
| planned | directional | 远期结果方向 |
| planned | provisional | 暂定候选路径 |
| planned | committed | 已选定的下一步 |
| active | committed | 当前确定执行的 leaf |
| completed | committed | 已完成的历史行动 |
| superseded | provisional | 曾经暂定、后来被替代的路径 |

不允许：

- `active + provisional`；
- `active + directional`；
- 没有完整 contract 的 Transition 被激活。

## Transition 计划字段

建议为 Transition 增加结构化 planning 数据：

```ts
type PlanningDependency =
  | { kind: "place_state"; placeId: string; state: "satisfied" | "invalidated" }
  | { kind: "gate_state"; placeId: string; state: "open" | "closed" | "blocked" }
  | { kind: "transition_outcome"; transitionId: string; outcome: "success" | "failure" }
  | { kind: "evidence_selector"; selector: string };

interface TransitionPlanning {
  commitment: PlanCommitment;
  dependsOn: PlanningDependency[];
  requiresRefinement: boolean; // directional 来源一旦设为 true，直到成为 shell 都不能直接激活
  reconsiderWhen?: string;
  refinementTrigger?: string;
  source: "declared" | "human" | "migration_default";
}
```

字段要求：

| Commitment | 必填信息 |
|---|---|
| committed | active/near 时需要完整 operational contract |
| provisional | intent，以及 `dependsOn` 或 `reconsiderWhen` 至少一个 |
| directional | intent，以及建议提供 `refinementTrigger` |

结构化 dependency 由 reducer 检查；`reconsiderWhen` 是给人阅读的调整说明，不参与自动 firing。无法机器验证的判断必须通过带 provenance 的 Agent/Human Evidence 明确确认，不能因为一句自由文本而自动激活。`evidence_selector` 只与 Evidence 的专用 `selectors[]` 标签匹配，禁止用 evidence ID 或 summary/source 的自然语言推断正向事实。

`planning.source = human` 只能由 runtime 绑定：Human Evidence 的 summary 必须能精确对应当前 Pi branch 中某条真实 user message，committed patch 再保存该 user entry ID 和 runtime-verified Evidence ID。调用方单纯提交 `type: human_statement` 不构成 Human provenance；无法绑定时按 Agent declaration 处理。

示例：

```json
{
  "transitionId": "tr_add_pi_pane",
  "intent": "增加 Pi 右上角局部路径 Pane",
  "status": "planned",
  "planning": {
    "commitment": "provisional",
    "dependsOn": [
      {
        "kind": "evidence_selector",
        "selector": "viewer:orientation-value-confirmed"
      },
      {
        "kind": "place_state",
        "placeId": "pl_ascii_readable_at_120_cols",
        "state": "satisfied"
      }
    ],
    "reconsiderWhen": "外部查看器已经足够，或 Pane 明显遮挡会话内容",
    "source": "declared"
  }
}
```

## Hierarchical Refinement

V2 实现原设计中已定义、但 Slice 1 尚未交付的 `refine_transition`。

### Refinement 数据

建议增加显式 refinement record，而不是仅靠标签推断父子关系：

```ts
interface TransitionRefinement {
  id: string;
  parentTransitionId: string;
  entryPlaceId: string;
  successExitPlaceId: string;
  failureExitPlaceId?: string;
  nodeIds: string[];
  status: "draft" | "active" | "completed" | "superseded";
}
```

V2 仍保持单 token，不加入 split/join。Refinement 可以嵌套，从而形成任意深度的 active stack。

父 shell 在 token 进入其 subnet 时变为 `active`，但不建立第二份 reservation；只有最深 active leaf 拥有可执行 reservation。父 shell 的 active 状态完全由“token 是否位于其递归 subnet 中”派生。这样允许多个 active statuses 沿一条祖先链存在，同时仍然只有一个可执行 leaf 和一个 token。

### 激活规则

一个 Transition 从未来计划进入当前执行栈时，必须在同一原子 GraphPatch 中完成必要操作：

1. 若为 directional，先增加可执行 refinement；
2. 若为 provisional，由 reducer 验证结构化 dependency，并要求 promotion patch 携带选择理由和必要 Evidence；
3. 将目标 Transition 以及本次将进入的所有 shell commitment 提升为 committed；
4. 补全 active leaf operational contract，并确保每个 ancestor shell 已有目标、范围和退出条件；
5. 激活最深 leaf Transition；
6. 保留所有被替代计划的 provenance。

上述 refinement、promotion、contract 补全和 activation 必须处于同一个原子 GraphPatch。若当前 leaf 完成时下一步已经确定，完成旧 leaf 与激活新 leaf 也应在同一 patch；若证据不足，则允许 token 明确停在 Place/Gate，界面显示 `at_place` 或 `blocked`，而不是制造一个暂时的 active step。

不能先把模糊节点激活，再在执行过程中补写“为什么”和“如何完成”。

### 当前路径变化

若当前结果迫使路径改变，必须显式执行以下之一：

- complete 当前 leaf，然后选择新的 committed next；
- fail 当前 leaf，进入 recovery branch；
- supersede 当前 leaf，并记录人类纠正或外部条件变化；
- 在父 shell 内新增或替换 refinement。

每次改变只推进一个 graph revision，并留下可回放 batch。

### 递归完成与失败展开

1. deepest leaf 完成后，token 先进入当前 subnet 的 success Place；
2. 若该 Place 是父 shell 的 `successExitPlaceId`，reducer 在同一原子 firing 中关闭父 shell，并把 token 移到父 Transition 的 success 输出；
3. 若父输出又是更外层 refinement exit，继续向外展开，直到 token 到达一个普通稳定 Place；
4. failure exit 使用同样规则向外传播：只有显式 `failureExitPlaceId` 才能使失败离开当前 subnet；
5. 若 refinement 没有 `failureExitPlaceId`，失败必须在该 subnet 内处理：leaf 没有本地 failure arc 时返回自己的 flow 输入 Place，ancestor shell 保持 active；
6. token 到达显式 failure exit 后，若父 Transition 有 failure output，则移动到该 Place；若父 Transition 没有 failure output，则按 Slice 1 规则返回父 flow 输入 Place，并把父 shell 标记 failed；
7. replay 只根据 refinement mapping 和 committed batch 执行该展开，不能为每层父 shell 伪造额外 token；
8. 任一层 mapping 缺失或引用失效时，整份 firing patch 拒绝，原 state 保持不变。

### 递归 firing 的领域事件顺序

V2 的 `DomainEvent` 增加 `eventIndex`；CommittedBatch 内严格按 `(opIndex, eventIndex)` 排序。Schema 1 的单事件 op 在迁移视图中使用 `eventIndex: 0`。

显式 `complete_transition` 的规范事件顺序：

```text
transition.completed   leaf, derived=false
 token.moved           leaf input → leaf success output
 refinement.exited     innermost refinement, outcome=success
 transition.completed  parent shell, derived=true
 token.moved           child exit → parent success output
 ...                    逐层由内向外重复
```

显式 `fail_transition` 的规范事件顺序：

```text
transition.failed      leaf, derived=false
 token.moved           leaf input → local failure output / leaf input
 refinement.exited     仅当到达显式 failureExitPlaceId，outcome=failure
 transition.failed     parent shell, derived=true
 token.moved           child failure exit → parent failure output / parent input
 ...                    逐层由内向外重复
```

每个事件 payload 至少包含：

```ts
{
  tokenId: string;
  transitionId?: string;
  refinementId?: string;
  fromPlaceId?: string;
  toPlaceId?: string;
  outcome?: "success" | "failure";
  depth: number;          // leaf = 0，向外逐层增加
  derived: boolean;
  causeOpIndex: number;
}
```

若 deepest leaf 的输出不是当前 refinement exit，batch 只产生 leaf transition 和 token movement 事件，ancestor shell 保持 active。任一层展开验证失败时，整份 patch 和全部派生事件一起丢弃，不允许留下事件前缀。

## V2 GraphPatch 操作

保留 Slice 1 的全部操作，并增加：

```text
set_transition_planning
refine_transition
supersede_refinement
```

建议 payload：

```ts
interface SetTransitionPlanningOp {
  op: "set_transition_planning";
  transitionId: string;
  planning: Partial<Omit<TransitionPlanning, "source">>;
  reason: string;
  evidenceIds?: string[];
}

interface RefineTransitionOp {
  op: "refine_transition";
  refinementId: string;
  parentTransitionId: string;
  nodeIds: string[];
  arcIds: string[];
  entryPlaceId: string;
  successExitPlaceId: string;
  failureExitPlaceId?: string;
  reason: string;
  evidenceIds?: string[];
}

interface SupersedeRefinementOp {
  op: "supersede_refinement";
  oldRefinementId: string;
  newRefinementId: string;
  reason: string;
  evidenceIds?: string[];
}
```

Schema 2 的 `create_transition` 必须携带 `planning: Omit<TransitionPlanning, "source">`；`source` 始终由 reducer 根据 patch actor 或 migration 分配，调用方不能自行声称为 Human 来源。`set_transition_planning.planning` 必须至少包含一个实际变化字段，空更新拒绝。

subnet 的 Place、Transition 和 Arc 仍由同一 GraphPatch 中的 `create_*` 与 `connect` ops 创建；`refine_transition` 只声明这些 `nodeIds/arcIds` 构成的 scope 和 entry/exit mapping。嵌套 refinement 通过同一 patch 中多个按顺序排列的 `refine_transition` ops 表达。

Promotion/activation 的具体原子 batch 为：

```text
set_transition_planning(committed, reason, evidenceIds?)
set_transition_contract(...)
refine_transition(...)            # 仅需要下钻时
activate_transition(active leaf)
```

`complete_transition` 或 `fail_transition` 只指向 deepest leaf；父 shell 的递归 completion/failure 和 token unwind 由 reducer 生成同一 CommittedBatch 内的派生 DomainEvents，不要求 Agent 为每一层提交额外 op。

### `set_transition_planning`

版本化更新：

- commitment；
- dependsOn；
- reconsiderWhen；
- refinementTrigger。

`source` 由 reducer 从 actor/provenance 派生，不是可编辑 planning 字段。操作必须携带 `reason`；当 reason 包含对测试、Gate 或外部事实的主张时还必须携带 `evidenceIds`。因此 promotion 原因存在于权威 batch，而不是只存在于派生时间线。

规则：

- provisional/directional → committed 允许，但激活时仍校验 contract 和 dependency；
- committed → provisional 只允许未激活节点；
- active 节点不能降级，必须先 complete、fail 或 supersede；
- event payload 持久化 changed fields、reason 和 evidence references；
- before/after 值由相邻有效 state 确定性派生。

### `refine_transition`

为一个 shell 增加合法 Petri subnet，并声明 entry、success exit 和可选 failure exit。操作必须保持二部图和单 token 不变量。

### `supersede_refinement`

保留旧 subnet，停止其成为未来候选；新 subnet 使用新的稳定 ID。历史图仍可查看旧 refinement。

## 图形投影

### 权威边界

三种对象的职责不同：

```text
Pi 当前 branch 上已验证的 committed checkpoints = branch authority
CommittedBatch / GraphPatch ops                    = 可回放变更历史
WorkflowState                                      = 对历史 fold 后的当前快照
SQLite / JSON / Mermaid / SVG / ASCII / Pane       = 可重建缓存或投影
```

重建时只接受 hash、schema、batch envelope、严格 revision continuity 和确定性 replay 均有效的 checkpoint；WorkflowState 不能覆盖 branch history，SQLite 也不能反向修复 Pi branch。GraphPatch 是命令输入，只有进入 committed checkpoint 后才属于权威历史。

`update_action_path.execute()` 产生的 state 只是 tentative。SQLite、投影文件、Pane listener 和正式 UI 状态只能在 Pi `message_end` 已经能从 `sessionManager.getBranch()` 读到对应 tool-result entry 后发布，并使用真实 session entry ID；进程在此之前退出时不得留下 phantom revision。

渲染失败不得：

- 回滚已经成功提交的 patch；
- 阻止 branch checkpoint 写入；
- 使 SQLite audit 与 Pi branch authority 分叉。

### Mermaid 投影

使用 `beautiful-mermaid` 生成横向 `graph LR`，同一 Mermaid source 服务于 SVG 和终端 ASCII。

建议视觉符号：

| 语义 | 符号 |
|---|---|
| active leaf | `▶` |
| committed next | `◆` |
| provisional future | `⧖` |
| directional future | `◇` |
| completed | `✓` |
| failed | `✕` |
| superseded | `↪` |
| blocked gate | `⛔` |

状态不能只依赖颜色，因为 ASCII、低色终端和无障碍环境必须仍可理解。

### Current Stack 与完整网分开投影

V2 不要求 Mermaid renderer 必须正确支持任意层级的 nested subgraph。投影器先生成两个逻辑区域：

```text
CURRENT STACK
Goal > Stage > Task > Subtask > ▶ Active Leaf

LOCAL NET
recent past → current leaf → committed/provisional future branches
```

完整 SVG 查看器可以进一步显示所有 subnet；Pi Pane 始终优先保证 current stack breadcrumb 和 local net 可读。

## 历史与变更追溯

### 原则

节点可以改变，但历史不能被覆盖。

- 投影器从 committed batches 派生 `createdRevision` 和 `lastChangedRevision`，第一版不把这两个索引重复写入核心节点；
- contract、planning、status 和 refinement 的修改原因保存在对应 patch op/event payload；
- superseded/retired 节点继续存在；
- 用户可以查看“当时计划是什么”和“后来为什么改变”；
- Mermaid 文件不承担历史存储。

### NodeChange 投影

从相邻 checkpoint state、GraphPatch ops 和 CommittedBatch 派生：

```ts
interface NodeChange {
  revision: number;
  committedAt: string;
  checkpointEntryId: string;
  parentEntryId?: string;
  patchId: string;
  actor: "agent" | "human" | "observer" | "external";
  targetId: string;
  operation: string;
  opIndex: number;
  eventIndex: number;
  summary: string;
  before?: unknown;
  after?: unknown;
  reason?: string;
}
```

示例：

```text
r21  + 创建暂定路径「增加 Pi Pane」
r22  ~ reconsiderWhen: 等待 ASCII 可读性验证
r24  ↪ 原路径被替代：外部查看器已经满足完整图需求
r25  + 新增方向节点「提供轻量会话内定位」
```

### 文件投影

默认写入当前活动 branch 的派生视图：

```text
$XDG_STATE_HOME/intent-petri/sessions/<session-id>/active/
├── current.json                  # 唯一跨文件 commit marker
├── current.mmd                   # 方便人工读取的镜像
├── events.jsonl                  # 当前 branch 镜像
├── generations/<generation-id>/  # 不可变完整代次
└── revision-cache/               # 按 entry/hash 复用的不可变历史快照
```

`current.json` 必须包含 `branchHeadEntryId`；每条 NodeChange 包含 `checkpointEntryId` 和 `parentEntryId`。因为 Pi 没有保证提供稳定的 branch ID，`active/events.jsonl` 是**当前 branch 的可重建投影**，branch 切换时整体原子重写，不宣称是跨分支 append-only 权威日志。真正的追加审计仍由 branch checkpoints 和 SQLite batches 承担。

约束：

- 完整 generation 和 revision cache 先写完，再以临时文件 + rename 原子替换根目录 `current.json` commit marker；
- viewer 只监听 `current.json`，因此不会混读旧 branch metadata 与新 branch events；
- 同一 branch head 下增量复用不可变 revision cache，但必须能从 branch checkpoints 全量重建；
- revision 只在同一 lineage 内有序，跨分支定位使用 `(sessionId, checkpointEntryId, revision)`；
- 默认权限 `0600`；
- branch 切换时重建整个 `active/`；
- 文件损坏时从 Pi branch checkpoint 重建；
- SQLite 仍是私有审计缓存，不是 branch authority。

## 外部实时查看器

外部查看器是完整图、历史和横向浏览的主界面。

建议用户入口：

```bash
nubx intent-petri-viewer --follow
```

开发模式可以直接运行 TypeScript entry：

```bash
nub viewer.ts --follow
```

查看器职责：

- 监听当前 session 的原子文件更新；
- 使用 `beautiful-mermaid` 渲染 ASCII 或 SVG；
- 完整展示 Current Stack、过去路径和未来计划锥；
- 自动跟随 active leaf；
- 支持横向滚动；
- 显示 revision 和最近 NodeChange；
- 允许切换 full graph / causal spine；
- 允许查看 superseded/retired 路径；
- renderer 失败时显示原始 Mermaid 与错误，不影响扩展继续运行。

Extension factory 不启动长驻 HTTP server，也不承担查看器生命周期。用户在隔壁终端显式启动查看器。

## Pi 会话内悬浮 Pane

Pi Pane 是常驻定位工具，不是完整历史浏览器。

### 默认行为

- 锚定右上角；
- non-capturing，不抢走编辑器输入；
- 终端宽度不足时自动隐藏；
- 每次成功 patch、branch 切换或重建后刷新；
- 自动跟随 active leaf；
- 只显示 current stack 尾部和局部 Petri corridor；
- 每一行严格裁剪到 Pi 传入的 width；
- 渲染失败时退化为 NOW / WHY / NEXT / WATCH 文本。

建议局部窗口：

```text
CURRENT  Goal > Stage > Task > ▶ Leaf

✓ past ──▶ ▶ active ──▶ ◆ committed
                       ├─▶ ⧖ 暂定 A
                       └─▶ ⧖ 暂定 B

r25 · future is provisional
```

### 聚焦与滚动

默认 passive auto-follow。用户显式聚焦后才启用：

- 左右滚动；
- 展开/折叠 refinement；
- 切换 recent/full local slice；
- 查看最近 revision diff。

退出聚焦后立即把输入交还 Pi editor。

建议命令契约：

```text
/intent-petri pane on
/intent-petri pane off
/intent-petri pane focus
/intent-petri pane passive
```

实现使用 Pi overlay API 的 `top-right`、percentage width、`maxHeight`、responsive `visible` 和 `nonCapturing` 能力；`onHandle` 保存 `OverlayHandle`，通过 `focus()/unfocus()/hide()` 管理聚焦和关闭。Pane component 订阅 runtime revision 更新并调用 `requestRender()`；`session_shutdown` 必须解除订阅并隐藏 overlay。V2.3 首先增加一个针对 Pi 0.80.6 的交互 smoke test，验证 pane 开启后 editor 仍可输入、聚焦后方向键只作用于 Pane、退出聚焦和 resize 后 editor focus 可恢复。

## Agent Path Protocol V2

### 建立新目标或阶段时

Agent 应声明：

- North Star 或阶段目标；
- 当前确定的 active stack；
- active leaf 完整 contract；
- 1–3 个 immediate committed/provisional next；
- 必要的成功和失败 Place；
- 1–3 个 directional outcomes；
- 哪些 future 节点依赖当前证据。

不要求一次生成完整项目计划。

### 深入当前任务时

当一个 Transition 需要多个有意义的子步骤时，提交 refinement：

- 子步骤必须能改变工作状态；
- 子步骤必须有可观察的退出条件；
- 纯工具动作不建节点；
- active stack 随 leaf 深入而增长；
- 完成子网后折叠为 parent causal spine，但历史不删除。

### 当前结果改变未来时

Agent 应在下一个安全边界：

1. 提交当前 leaf 的证据和结果；
2. retire 或 supersede 不再适用的 future；
3. 更新 provisional 分支的依赖和说明；
4. 将被选择的 next 提升为 committed；
5. 激活新的确定 leaf。

用户看到的不是“Agent 原计划错了”，而是：

```text
未来计划已根据 r31 的测试结果调整
旧计划：修改 session middleware
新计划：先修复不稳定测试夹具
依据：测试在 middleware 执行前已随机失败
```

## Attention View V2

默认摘要从四项扩展为五项：

```text
STACK   当前从总体目标下钻到哪一层
NOW     active leaf 是什么
WHY     为什么现在执行
NEXT    committed next 与 provisional candidates
WATCH   gate、失败分支、计划调整触发器
```

`NEXT` 不再只取任意前三个后继，而是按以下顺序选择：

1. committed immediate next；
2. 与当前 expected evidence 直接相关的 provisional branch；
3. 主要 failure/recovery branch；
4. 用户 pin 的 directional outcome。

每个 provisional 条目必须带短标记：

```text
[暂定｜随当前结果调整]
```

## Schema 迁移

V2 将 WorkflowState schema 升级为 version 2。

重建 mixed-schema branch 时：

1. 先按 checkpoint 自身 schema 的规则验证原始 snapshot hash 和 parent lineage；
2. 只对已经验证的 state 调用纯函数 `migrateCheckpointToLatest()`；
3. migration 保持原 graph revision，不自动写 checkpoint、不生成领域事件；
4. 第一次成功提交 V2 patch 时才写入 schema 2 checkpoint，并在 checkpoint metadata 中记录 `migratedFromSchemaVersion: 1`；
5. migration 必须幂等；任何迁移失败都回退到最近一个可验证 checkpoint，并把当前位置显示为 `unanchored` 或 degraded，而不是写入半迁移 state。

schema 1 → schema 2 的映射：

- active/completed/failed Transition → `committed`；
- planned 且已有较完整 contract → 默认 `provisional`，并写入 `reconsiderWhen: Migration-inferred plan; confirm before activation`，避免产生既无 dependency 也无调整说明的无效 provisional；
- planned 且 contract 很少或为空 → `directional`；两种推断都必须标记 `source: migration_default`；
- retired/superseded 保留 status，并使用 `source: migration_default`；
- 没有 refinement record 的旧图视为单层 flat net；
- 迁移推断必须在 UI 中可见，允许后续 Agent/Human patch 明确确认或修正。

## 分阶段交付

### V2.1｜确定当前 + 暂定未来 + 外部图

包含：

- PlanCommitment；
- nested hierarchical refinement；
- Current Execution Stack projector；
- Future Planning Cone projector；
- `beautiful-mermaid`；
- `current.json/current.mmd`；
- 外部实时查看器；
- Agent Path Protocol V2 指引。

独立价值：立即解决节点太少、没有图、当前下钻位置不清和未来没有暂定标识。

### V2.2｜历史与 revision 浏览

包含：

- NodeChange projector；
- `events.jsonl`；
- before/after diff；
- revision 切换；
- superseded/retired 历史路径浏览；
- branch 切换后的历史重建。

独立价值：用户能回答“原来计划是什么、何时改变、为什么改变”。

### V2.3｜Pi passive pane

包含：

- 右上角 non-capturing overlay；
- active stack breadcrumb；
- bounded local graph；
- auto-follow；
- responsive hide；
- 聚焦滚动和 revision 摘要；
- fallback text view。

独立价值：不切换窗口也能持续知道自己处于哪一步。

## 明确不做

V2 不包含：

- 把每个工具调用变成节点；
- Mermaid 反向写入 WorkflowState；
- 让 Observer LLM 静默改写当前执行栈；
- 多 Agent split/join；
- 完整 Web 应用和远程服务；
- 自动预测所有未来步骤；
- 在窄终端强行展示完整长图；
- 删除被替代的历史路径；
- 展示隐藏 reasoning 或 chain-of-thought。

## 验收标准

选择一次至少包含以下特征的真实 session：

- 30 次以上工具调用；
- 当前执行链至少发生两次 hierarchical refinement；
- Current Execution Stack 至少 4 层；
- 至少 3 个 provisional future；
- 至少一次 future 被 supersede；
- 至少一次失败或 Gate 阻塞；
- 至少 15 个 graph revision。

用户应在 10 秒内正确回答：

1. 总体目标是什么？
2. 当前处于哪个阶段和第几层子任务？
3. active leaf 是什么，为什么现在执行？
4. active leaf 的成功证据和退出条件是什么？
5. 哪个下一步已经确定？
6. 哪些未来步骤只是暂定？
7. 暂定步骤会根据什么结果调整？
8. 最近一次计划变化发生在哪个 revision，为什么？
9. 哪些旧路径已经 superseded 或 retired？
10. 当前是否存在失败分支、Gate 或需要人类判断的事项？

工程验收：

- active leaf 和所有 active ancestor shells 永远是 committed；
- active transitions 只能形成一条 refinement chain，且只有 leaf 可执行；
- provisional/directional Transition 无法直接 activate；
- promotion、contract 补全和 activation 为一个原子 patch，并保留 reason/evidence；
- nested refinement replay 后得到相同 active stack；
- 递归 success/failure exit 能确定性向父 shell 展开，并产生固定 `(opIndex, eventIndex)` 顺序的完整领域事件；
- 任一 refinement mapping 无效时整批拒绝；
- future supersede 不删除旧节点；
- observer proposal 不能进入 active stack，除非被 Agent/Human 接受为正式 GraphPatch；
- mixed schema、分支分叉和 branch reconstruction 能恢复 planning、refinement 与 NodeChange；
- 切换 branch 后 `active/events.jsonl` 不混入另一条 lineage；
- 文件更新为原子写入，投影崩溃后可从 checkpoint 恢复；
- 查看器能发现当前 session，并在 patch 后 1 秒内刷新；
- Pi Pane 所有 render line 不超过传入 width；
- Pane passive/focus/resize/关闭后 editor 输入与焦点恢复正确；
- renderer 抛错不影响 patch commit；
- `nub run typecheck`、`nub run test`、`nub run package:check` 和 `nub run smoke:extension` 全部通过。

## 最大风险

### Agent 只声明当前 leaf，不声明合理未来

防御：新阶段建立时要求最少声明 immediate next 和一个 directional outcome；缺少 future 时显示“未来尚未声明”，不由系统伪造。

### Agent 产生过多细碎 refinement

防御：只有会改变条件、决策、证据状态或执行方向的步骤才能进入主图。工具动作继续归入 Evidence。

### provisional 被用户误解为承诺

防御：使用文字和符号双重标识；默认文案始终包含“暂定”和“随当前结果调整”。

### 深层当前栈挤占屏幕

防御：Current Stack 使用 breadcrumb；完整层级只在外部查看器展开；Pi Pane 只显示尾部若干层。

### 横向图随历史增长失控

防御：外部查看器支持 full graph / causal spine；Pi Pane 使用 bounded local slice；completed subnet 默认折叠但可回放。

### 渲染依赖失败

防御：Mermaid source、JSON projection 和文本 fallback 独立存在；`beautiful-mermaid` 失败只影响某次视图更新。

## 最终产品判断

V2 不再把 Intent Petri 理解为“一条只显示当前附近几个节点的 corridor”。它是：

> 一条可持续下钻、且最近一次有效声明中的执行位置结构上无歧义的执行栈，加上一片明确标注为暂定、会随证据持续变化的未来计划锥；没有 active leaf 或声明与行为失配时，系统同样明确显示这种状态。

完整图和历史在外部实时查看器中展开；Pi 会话内 Pane 负责持续定位。两者共享同一 WorkflowState 和 projection pipeline，不维护两套状态。
