# v1.4 任务节点归一化 + 事件驱动调度 — 设计规格

> 本文档定义 v1.4 的完整实现规格:任务从"派发动作"归一化为"完整节点实体",调度从服务端轮询升级为**客户端事件驱动**,DAG 视图成为工作区实时编排视图。基于 v1.2/v1.3 扩展。
> 实施方式与前序版本相同:后端由宿主开发者实施,前端(SSE 订阅、客户端调度器、DAG 图渲染)由外包实施,契约在本文件。

---

## 1. 背景与动机

### 1.1 割裂

- `dispatch_task` 是"派发**动作**",`depends_on` 是挂在动作上的补丁参数;任务模块与 DAG 模块设计割裂。
- 排期由服务端 60s 轮询 + settle 事件触发,不是真正的事件驱动。
- 面板 2s 轮询 `/state`,不是事件驱动。

### 1.2 归一化

- **任务 = 节点实体**,天生携带完整属性:内容、前置依赖、验收标准、重试计数。
- **状态变更即事件**,调度由事件驱动。
- **DAG 图 = 工作区实时编排视图**,节点与任务状态实时绑定。

---

## 2. 任务节点归一化

### 2.1 节点属性(全部可选参数化)

| 属性 | 说明 |
|---|---|
| `content` | 任务内容 |
| `dependencies[]` | 前置依赖:投递前须完成的任务 id(≤16,同 workspace,无环) |
| `acceptanceCriteria` | **验收标准(新增)**:发起方对验收方的最低验收要求;reviewer 验收依据,执行方可见 |
| `retries` | 重试计数,由节点内建负责(settle failure → 同 id 回待执行,retries++) |
| `status` / `outcome` / `mode` 等 | 现有 |

### 2.2 工具面

`dispatch_task` **消失**,任务创建归一化为 `create_task`:

```
create_task(target, content, {dependencies?, acceptanceCriteria?, reviewer?, mode?, task_id?})
```

- `task_id` 保留"回答 request_input"路径(与原 dispatch_task 一致)。
- `edit_task` 扩展:可改 `acceptanceCriteria`(与 content/depends_on 并列)。
- `list_tasks` / `get_task` 输出含 `acceptanceCriteria`。
- 其余工具(`report_task` / `settle_task` / `cancel_task` / `request_input` / `send_note` / `list_peers` / `update_card`)不变。

### 2.3 重试由节点负责

- settle failure → 同一任务 id 回「待执行」,`retries++`,沿用原验收标准与依赖集——**重试是节点的状态流转,不是外部新任务**。
- 无重试上限;发起方以 `cancel_task` 兜底(与现状一致,不引入 maxRetries)。

---

## 3. 状态机(schema v7)

### 3.1 新增「待投递」(queued)

A2A 8 态之外新增显式状态 **`queued`(待投递)**,作为 submitted 的投递前阶段:

```
创建 ──> queued(待投递,未入执行队列)
            │ 客户端调度器:全部前向依赖 已完成(success)/已归档
            ▼
        submitted(待执行,已投递入队) ──认领──> working(进行中)
            │                                │
            └────── settle failure(重做) ◄────┴── report ──> completed(待验收)
                                                               │ settle success
                                                               ▼
                                                        (结算完成,24h 后归档)
```

- **投递门禁**:仅当全部前向依赖 `completed + outcome=success`(含已归档的依赖,`isSettledSuccess` 对归档行仍成立)才允许 `queued → submitted`。
- `queued` 行无 messageId;`submitted` 行必有 messageId(投递即落)。
- 兼容:存量 `submitted + 无 messageId` 行在打开时迁移为 `queued`。
- A2A 对齐文档注明:queued 为 A2A submitted 的投递前阶段,词汇表扩展。

### 3.2 字段新增

```
acceptanceCriteria: z.string().max(2000).optional()   // 验收标准
```

domain version v6 → v7。**升级前先备份 `agent_bus.json`(v1.3 §6 保险)**,再执行重建流程;打开时对旧行做 `submitted+无messageId → queued` 迁移。

---

## 4. 事件模型

### 4.1 事件定义

任务台账每次状态/结算变更产生一条事件,经 SSE 推给面板客户端:

```
TaskChanged {
  taskId: string
  from: string          // 旧状态(queued/submitted/working/completed/…)
  to:   string          // 新状态或结算标记(working/completed/failed/… / settled-success / settled-failure)
  at:   string          // ISO 时间
}
```

事件覆盖的变更面:

| 变更 | 事件示例 |
|---|---|
| 创建(进入待投递) | `- → queued` |
| 投递 | `queued → submitted` |
| 认领 | `submitted → working` |
| 提交(待验收) | `working → completed` |
| 重做 | `completed → submitted`(retries++) |
| 结算成功/失败 | `completed → settled-success` / `completed → settled-failure`(outcome 变更也是事件) |
| 终态 | `→ failed` / `→ canceled` |
| 编辑 | `edited`(dependencies/acceptanceCriteria 变更) |

### 4.2 服务端接口(webServer 注册,与 `/state` 并列)

| 端点 | 方法 | 说明 |
|---|---|---|
| `/plugins/dsh-agent-bus/events` | GET | SSE 事件流,长连接推送 TaskChanged |
| `/plugins/dsh-agent-bus/dispatch` | POST `{taskId}` | 执行投递:`queued → submitted`(recordDelivery + deliverTask),**幂等**:非 queued 或已投递则跳过 |

- 事件源:ledger 所有写路径(record/transition/settle/editTask/recordDelivery)后 emit。
- SSE 断线:客户端重连;事件期间错过的事件由服务端兜底 sweep 补偿(见 §5.3)。

---

## 5. 客户端调度器(事件驱动,前端)

### 5.1 调度规则

```
收到 TaskChanged 事件 E:
  取 E.taskId 的所有 dependents(依赖它的任务)
  对每个状态为 queued 的 dependent:
    检查:全部前向依赖 isSettledSuccess → POST /dispatch {taskId}
    否则:留在 queued,等待后续事件
```

- 每次事件只检查**受影响子集**(direct dependents),不扫描全表。
- 多面板并发:投递接口幂等,重复 POST 安全。

### 5.2 实时刷新

- 事件流同时驱动 DAG 图刷新(节点徽章、边状态、归档标记)与面板状态区——**替代 2s 轮询**。
- 事件驱动为主,轮询降级为兜底(SSE 断线时恢复 2s 轮询)。

### 5.3 服务端兜底

- 60s sweep 保留:对 queued 任务重算依赖,满足即投递(幂等)——覆盖客户端离线/事件丢失/SSE 断线场景,不再是主排期器。

---

## 6. DAG 视图渲染契约(前端外包)

### 6.1 节点集规则

```
visibleNodes = 全部未归档任务 ∪ 每个未归档任务的递归依赖祖先链
```

- 例:A→B→C→D→E→F,F 未归档:A–E 即使全部已归档也显示(依赖链完整性)。
- 已归档祖先节点:**淡显 + 归档标记,不可交互**;未归档节点实时状态徽章。
- 没有任何未归档任务引用到的已归档任务:不显示。

### 6.2 图布局

- 边 = `dependencies` 前向引用;弱连通分量 = 一张图;**全部图同时渲染**(无图数上限)。
- 实时绑定:TaskChanged 事件驱动节点徽章/边状态更新。
- 归档动作(结算 24h 到点)由事件触发,节点转淡显或移除(取决于是否仍在某未归档节点的祖先链上)。

### 6.3 数据边界

- 数据源仅 `/state` 快照(tasks 含全部任务:status/outcome/dependencies/acceptanceCriteria/archived 标记)。
- **send_note 消息渠道对 DAG 图零影响**(不产生任务行,无依赖可引用)。

---

## 7. 流程(Flow):DAG 容器(本次扩展)

### 7.1 模型

```
Flow { id, name, description?, createdBy, createdAt, workspacePath }
Task 新增可选字段 flowId
```

- **流程由 agent 创建**(`create_flow`),任务创建时归入流程(`create_task.flow_id`,可选)。
- **一个流程一定是一个 DAG**:流程内任务 + 前向依赖构成的依赖图整体无环(创建/编辑时环检测覆盖全图)。
- **前向依赖限定在流程内已有任务的子集,可为空集**:不能选择不在流程中的任务作为依赖——需要依赖某任务时,先把它加入流程(`edit_task.flow_id`)。**跨容器依赖天然禁止**(validateDependencies 强制:任务有 flowId 时,依赖必须同 flowId)。
- 无流程任务:依赖不受限(现状),**不进入任何 DAG 视图**(列表/面板其余部分照常可见)。

### 7.2 生命周期与归档

- 流程状态**派生**,不落库:`active`(存在未归档任务)/ `archived`(流程内全部任务已归档)。
- 流程内全部任务归档 → 流程自动进入**归档区**;向归档流程添加新任务 → 自动回到活跃区(派生状态自然翻转,无人工操作)。
- 任务归档规则沿用 §6(结算 24h+ / 终态立即归档)。

### 7.3 工具面

| 工具 | 职责 |
|---|---|
| `create_flow(name, description?)` | 创建流程,返回 flowId |
| `list_flows()` | 流程列表:名称、任务数、未结算数、归档标记 |
| `create_task(…, flow_id?)` | 创建任务时归入流程 |
| `edit_task(…, flow_id?)` | 改任务归属(依赖须随迁:新流程须包含任务的全部依赖) |

### 7.5 交接文档(handoff,链上结构化传递)

- 任务验收通过后,settle 通知列出该任务提供的**全部后向任务**;执行方为每个后向任务调用 `submit_handoff(task_id, to_task_id, document)` 提交交接文档。
- 校验:调用方须为 task_id 的 executor;to_task_id 的 `dependencies` 必须含 task_id(仅后向可收)。
- 交接文档挂在**后向任务**上(`tasks.handoffs`,schema v9);该任务投递时自动拼接进投递内容(「前置任务交接文档」段)。
- `get_task` 输出含 handoffs(读取路径完整);面板 TaskView 不含(投递内容已携带)。

### 7.6 自我执行

- `target == caller` 允许(流程可调度发起方本人执行);但 **reviewer 必须为第三方**(`reviewer !== executor`),create_task 强制校验——独立验收不破。
- 通知:settle 回执与 scheduler 派发等**同任务通知在 3 秒窗口内合并为一条**投递(NoticeMerger);reminder 冷却基于任务 `updatedAt`(重启免疫)。
- 终局通知:流程最后一个任务结算时,创建者收到全流程结果汇总。

### 7.4 视图(前端)

- **流程界面以流程为单位**:流程列表(活跃区 + 归档区)→ 点选流程 → 渲染该流程内全部任务节点组成的 DAG。
- 无流程任务不出现在 DAG 视图。
- 节点集/祖先链淡显规则沿用 §6(流程 DAG 的节点全部是流程内任务)。
- 流程进入/离开归档区由 TaskChanged 事件驱动列表刷新。

---

## 8. 模型面指引(USAGE_TEXT 变更)

### 8.1 工具路由分级(按任务规模)

模型面以"规模路由"开篇,agent 先判断需求大小再选通道:

| 规模 | 判定 | 通道 | 生命周期 |
|---|---|---|---|
| **SMALL** | 一句话沟通:消息/提问/确认/协调 ping,无需可验收产出 | `send_note` | 无记录、无验收、无恢复 |
| **MEDIUM** | 单个可验收交付物:对方产出、你验收 | `create_task` | 完整:report → settle → rework/cancel + 超时兜底 |
| **LARGE** | 多步骤工程:需要规划与排序 | `create_flow` | 先 plan 完整文档 → 建流程 → 拆分任务(flow_id + dependencies)自动排期;失败自动传播 |

- 禁止过重/过轻:聊天当任务 → 永久卡 working;任务当聊天 → 丢失问责生命周期。
- 工具描述带规模标注(send_note=SMALL、create_task=MEDIUM、create_flow=LARGE),与 USAGE_TEXT 路由段一致。

- `create_task` 段落:可选 `dependencies`(前置依赖)与 `acceptanceCriteria`(验收标准)与 `flow_id`(归入流程);说明待投递语义(依赖未清时任务停留「待投递」,由调度自动投递)。
- `edit_task` 补充 acceptanceCriteria 与 flow_id 可编辑。
- `create_flow` / `list_flows` 段落:流程 = DAG 容器;依赖限定在流程内已有任务。
- `list_tasks` 补充:依赖未清的任务显示「待投递」;输出含验收标准。
- 工具清单:`list_peers, send_note, create_flow, list_flows, create_task, edit_task, list_tasks, get_task, report_task, settle_task, cancel_task, request_input, update_card`。

---

## 8. 验收清单

- [ ] 创建带依赖的任务 → 状态 `queued`(待投递),无 messageId;面板/图上显示「待投递」。
- [ ] 依赖结算 success → 客户端收到事件 → 自动投递(`queued → submitted`,auto 标记),链式递归直至末端。
- [ ] 依赖终态失败 → 传播(服务端,现状保留),dependent 自动 failed。
- [ ] 归档祖先链完整性:未归档 F 的已归档祖先 A–E 在图上淡显显示。
- [ ] acceptanceCriteria:创建/编辑可写,list/get 可读,settle 提示 reviewer 对照验收。
- [ ] 存量 `submitted+无messageId` 行打开时迁移为 `queued`。
- [ ] dispatch 接口幂等:重复 POST 不重复投递;SSE 断线后 sweep 兜底投递。
- [ ] dispatch_task 工具消失,create_task 全量替代;工具面清单更新。
- [ ] 全量单测通过,构建通过,e2e(链式自动投递 + 失败传播 + 祖先链显示)通过。
- [ ] 流程:create_flow/list_flows;create_task.flow_id 归入;依赖限定流程内(跨流程引用拒绝);流程内整体无环。
- [ ] 流程归档派生:全部任务归档 → 流程进归档区;加新任务 → 回活跃区。
- [ ] DAG 视图按流程过滤;无流程任务不出现在 DAG 视图。
