契约冻结 v1:Manager/客户端 Swarm Run 查询契约(Refs #2 #14 #15)

回应 HM 驾驶舱(heicode-mananger #28/#45/#46)经 agent_swarm#14(runtime-contract)
+ #15(event-schema)提出的消费需求。HM 只读查询已落地(HM PR #53),唯一前置是本
仓契约冻结。本 PR 把回调契约冻结为 v1 并落代码 + 测试。

代码(orchestrator/):
- swarm_runtime.emit_event:回调 envelope 新增 **per-swarm 严格递增 `sequence`**
  (INCR 计数键 swarm_event_seq:{swarm_id},从 1、无空洞,供客户端 events?after= 去重/续传)。
- 新增冻结的客户端 13 类事件(FROZEN_CLIENT_EVENT_TYPES)中此前缺的 6 类,均**附加**发出
  (不动既有 deployment.status_changed,HM 仍用其更新 AgentDeployment.Status):
  · swarm.completed/failed(refresh_swarm_run_status 终态)、swarm.stopped(stop_run);
  · approval.approved/rejected(record_approval_decision 决定落地);
  · handoff.created(child 任务建立时)。
- artifact.created envelope 补扁平字段:created_at(默认 occurred_at)、task_id(回填)、
  size_bytes 透传(未知则省略,不伪造,规则 #9)。
- redis_client 新增原子 incr(真实 + 两处 fake stub)。

文档(docs/integration/,FROZEN v1):
- event-schema.md:envelope sequence、artifact 扁平字段、事件注册表标注 ⭐13 类 + 新增 6 类、
  对齐状态更新(title/threshold_pct/sequence/artifact 已在 emit 统一处理)。
- runtime-contract.md:冻结 stop 端点 + ID 映射;§4.1 新增**状态机映射表**——运行时不臆造
  preparing/degraded/verifying(规则 #9),由 HM/客户端按表映射真实状态
  (blocked→degraded、评审期→verifying 等);终态另发 swarm.* 事件。

测试:
- 新增 scripts/test-contract-freeze.py(hermetic):sequence 单调/每-swarm/无空洞、13 类
  round-trip、artifact 形状(含未知 size 不伪造)、approval.*/swarm.stopped 真实发出、
  明文凭据脱敏而 azkv secret_ref 透传。接入 CI + CLAUDE.md 提交前清单。
- test-workflow-e2e.py:全流程 e2e 额外断言 swarm.completed + sequence 无空洞。
- 两处 FakeRedis stub 补 incr。

影响范围:仅 agent_swarm(orchestrator + docs/integration + 测试 + CI + CLAUDE.md)。
- Manager:回调**新增** sequence 字段与 6 类事件——向后兼容(旧消费方忽略新字段/新类型即可);
  HM 注册表需登记新 6 类方能对外暴露(agent_swarm#15.2,已在 doc 列为剩余项)。
- 计费/审计:不涉及(查询面不计费由 HM 保证;本仓未改计费/审计字段)。
- 密钥:envelope 不含明文凭据;secret_ref 仍为 azkv 引用,HM 对客户端再脱敏。

Refs #2
Refs #14
Refs #15

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Songhaoz666
2026-06-10 17:35:04 +08:00
co-authored by Claude Opus 4.8
parent 8b5eea296a
commit 15fe5d379b
11 changed files with 350 additions and 38 deletions
+45 -29
View File
@@ -1,6 +1,12 @@
# Swarm 运行时事件 Schema(回调 / 事件流)
> 状态:**已对齐 HM 实现**(依据 `heicode-mananger/heicode/controller/agent_callback.go`,handler `POST /api/agent/callbacks/runtime-events`)。本文是 Swarm → HM 回调与 `GET …/{id}/events` 事件流的统一 schema。
> 状态:**FROZEN v1(契约冻结)** —— 回应 `agent_swarm#15`(来自 HM #28/#45/#46 客户端「任务驾驶舱」消费需求)。本文是 Swarm → HM 回调与 `GET …/{id}/events` 事件流的统一 schema,已对齐 HM 实现(`heicode-mananger/heicode/controller/agent_callback.go`,handler `POST /api/agent/callbacks/runtime-events`)。
>
> 冻结要点(实现见 `orchestrator/swarm_runtime.py`,契约测试 `scripts/test-contract-freeze.py`):
> 1. envelope 带 **per-swarm 严格递增 `sequence`**(从 1、无空洞,供客户端 `events?after=` 去重/续传,§2)。
> 2. **客户端 13 类事件**(§4 标 ⭐)冻结为 `FROZEN_CLIENT_EVENT_TYPES`(`swarm_runtime.py`)。
> 3. **artifact** 扁平字段 `{uri, checksum, task_id, size_bytes?, created_at}`(§3)。
> 4. **凭据红线**:`secret_ref`/`credential_ref`/`signing_secret_ref` 为 `azkv://` 引用(非明文),Swarm 侧 `_redact_sensitive` 透传引用、剔除明文 `*_token/_secret/_key/password`;客户端可见视图由 HM 再做递归脱敏。
>
> 配套:生命周期与签名见 [`runtime-contract.md`](./runtime-contract.md)。
@@ -22,6 +28,7 @@
{
"event_id": "evt-...", // 必填;去重主键
"idempotency_key": "...", // 可选;缺省回退 event_id
"sequence": 12, // 必填;per-swarm 严格递增(从 1、无空洞);客户端按此去重/升序/续传
"event_type": "task.completed", // 必填;取值见 §4
"deployment_id": "runtime-dep-...",// 运行时部署 ID
"swarm_id": "swarm-...", // 工作流 ID(事件按此持久化)
@@ -44,41 +51,47 @@
"artifact_type": "code_patch | document | deployment_manifest",
"title": "...",
"summary": "...",
"uri": "git://repo#branch | runtime://...",
"uri": "git://repo#branch | runtime://...", // 仅 uri,绝不含 secret_ref/凭据
"checksum": "<commit_sha>",
"task_id": "...", // 缺省回填 envelope.task_id(#15.4)
"size_bytes": 1234, // 可选;未知则省略(不伪造,规则 #9)
"created_at": "2026-06-08T...Z", // 缺省回填 envelope.occurred_at
"metadata": { "redacted": true, "agent_role": "...", "files_modified": [] }
}
```
> 客户端 artifact 视图(HM #28)取扁平 `{uri, checksum, task_id, size_bytes?, created_at}`;HM 由 `artifact.created` envelope 的上述字段直接产出,无 `secret_ref`/密钥。
## 4. 事件类型注册表(与 HM 一致)
⭐ = **冻结的客户端 13 类**(`FROZEN_CLIENT_EVENT_TYPES`,驾驶舱据此渲染;HM `agent_callback.go` 据此对齐注册表)。其余为 Swarm 也发的辅助/运行时事件(HM 仍消费,但非客户端必需)。
| event_type | payload 必填字段(HM 校验) | 分类 | Swarm 是否发出 |
|---|---|---|---|
| `deployment.status_changed` | `status` | deployment | ✅ |
| `phase.changed` | `stage` 或 `checkpoint` | ordinary_sub | ❌(暂未用) |
| `agent.started` | `agent_role` | ordinary_sub | ❌ |
| `agent.completed` | `agent_role` | ordinary_sub | ❌ |
| `agent.crashed` | `agent_role`, `reason` | ordinary_sub | ❌ |
| `task.created` | `task_id`, `title` | swarm_task_flow | ✅ |
| `task.claimed` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `task.running` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| ⭐ `task.created` | `task_id`, `title` | swarm_task_flow | ✅ |
| ⭐ `task.claimed` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| ⭐ `task.running` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| ⭐ `task.completed` | `task_id` | swarm_task_flow | ✅ |
| ⭐ `task.failed` | `task_id`, `reason` | swarm_task_flow | ✅ |
| ⭐ `handoff.created` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ✅(child 任务建立时,与 `handoff.requested` 并发) |
| ⭐ `approval.requested` | `approval_id`, `operation`, `risk_level` | approval | ✅ |
| ⭐ `approval.approved` | `approval_id` | approval | ✅(审批决定落地时) |
| ⭐ `approval.rejected` | `approval_id` | approval | ✅(审批决定落地时) |
| ⭐ `artifact.created` | `artifact_id`, `uri`, `checksum` | artifact | ✅(带 §3 扁平字段) |
| ⭐ `swarm.completed` | `status` | swarm_terminal | ✅(终态,与 `deployment.status_changed` 并发) |
| ⭐ `swarm.failed` | `status` | swarm_terminal | ✅(终态) |
| ⭐ `swarm.stopped` | `status` | swarm_terminal | ✅(`stop_run`) |
| `deployment.status_changed` | `status` | deployment | ✅(HM 用于 AgentDeployment.Status;客户端用 swarm.* 终态) |
| `task.heartbeat` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `task.blocked` | `task_id`, `reason` | swarm_task_flow | ✅ |
| `task.retried` | `task_id`, `attempt` | swarm_task_flow | ✅ |
| `task.released` | `task_id`, `agent_role` | swarm_task_flow | ⚠️ 未发出(见 §6) |
| `task.failed` | `task_id`, `reason` | swarm_task_flow | ✅ |
| `task.completed` | `task_id` | swarm_task_flow | ✅ |
| `task.released` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `handoff.requested` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ✅ |
| `handoff.completed` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ⚠️ 缺 `from_role`/`to_role`(见 §6) |
| `approval.requested` | `approval_id`, `operation`, `risk_level` | approval | ✅ |
| `artifact.created` | `artifact_id` | artifact | ✅ |
| `timeline.updated` | `title` | timeline | ⚠️ 发出 `summary`,缺 `title`(见 §6) |
| `sk_tool.called` | `tool_name`, `tool_invocation_id` | sk | ❌(无 SK 工具) |
| `sk_tool.completed` | `tool_name`, `tool_invocation_id` | sk | ❌ |
| `sk_tool.failed` | `tool_name`, `tool_invocation_id`, `reason` | sk | ❌ |
| `budget.alert` | `threshold_pct` | budget | ⚠️ 发出 `threshold`,缺 `threshold_pct`(见 §6) |
| `timeline.updated` | `title` | timeline | ✅(emit 时回填 `title`=`summary`) |
| `budget.alert` | `threshold_pct` | budget | ✅(emit 时回填 `threshold_pct`) |
| `phase.changed` / `agent.*` / `sk_tool.*` | — | ordinary_sub / sk | ❌(本运行时不产生) |
> 说明:评审/重做循环复用 `task.retried` + `timeline.updated`(`Review cycle N`)表达,无单独 review 事件类型;如 HM 前端需要独立 review 事件,列为待对齐项。
> 说明:评审/重做循环复用 `task.retried` + `timeline.updated`(`Review cycle N`)表达,无单独 review 事件类型;蜂群内部遥测(提案/竞价/交叉评审/收敛/health)**不经回调外发**(见各协议文档「事件不进 Manager 流」),故不在本表。`swarm.completed/failed/stopped` 与 `approval.approved/rejected`、`handoff.created` 为本次冻结**新增**,HM 注册表需据此登记(agent_swarm#15.2)。
## 5. HM 响应
@@ -86,13 +99,16 @@
{ "success": true, "event_id": "evt-...", "inserted": true, "idempotent": false, "deduplicated": false, "deployment_id": "dep_..." }
```
## 6. 已知对齐缺口(需在 Swarm 代码修正)
## 6. 对齐状态(冻结时已修正 / 剩余小项)
下列为本仓 `orchestrator/` 当前发出字段与 HM 校验的差异,应在对应 emit 处修正后再宣称完全对齐:
冻结时 `orchestrator/swarm_runtime.emit_event` 已统一处理下列历史缺口:
1. **`timeline.updated`**:HM 要求 `title`;当前发 `summary`。修正:payload 增加 `title`(可复用 `summary`)。
2. **`budget.alert`**:HM 要求 `threshold_pct`;当前发 `threshold`(如 `0.8`)。修正:增加 `threshold_pct`(百分比,如 `80`)。
3. **`handoff.completed`**:HM 要求 `from_role`/`to_role`;当前缺。修正:在 `finalize_parent_after_child` / handoff 完成处补 `from_role`/`to_role`。
4. **`task.released`**:HM 注册表含此事件;`task_queue.release_task` 当前静默。修正:释放任务时发 `task.released`(`task_id`, `agent_role`)。
1. ✅ **`timeline.updated`**:emit 时若缺 `title` 自动回填(复用 `summary`)。
2. ✅ **`budget.alert`**:emit 时若缺 `threshold_pct` 由 `threshold` 自动换算(≤1 视为比例 ×100)。
3. ✅ **`sequence`**:每事件带 per-swarm 严格递增序号(INCR 计数键 `swarm_event_seq:{swarm_id}`)。
4. ✅ **artifact 扁平字段**:`created_at`/`task_id` 自动回填,`size_bytes` 透传(未知则省略,不伪造)。
> 这些是确定性的小修,建议配 contract test(按 HM 必填字段断言每类事件 payload)。
剩余小项(不影响客户端 13 类冻结):
- `handoff.completed` 的 `from_role`/`to_role`:辅助事件,客户端 13 类用 `handoff.created`(已带 from/to);如 HM 仍消费 `handoff.completed`,在 handoff 完成处补两字段为后续小修。
> 契约由 `scripts/test-contract-freeze.py` 守护(断言 sequence 单调、13 类 round-trip、artifact 形状、审批/停止真实发出、明文凭据脱敏);`scripts/test-workflow-e2e.py` 在全流程 e2e 中额外断言 `swarm.completed` + sequence 无空洞。
+32 -8
View File
@@ -1,12 +1,16 @@
# Manager ↔ Swarm Runtime Contract(Swarm 侧拥有)
> 状态:**草案(Swarm 侧拥有,待 HM 对齐)** · 对齐对象:Heicode Manager Runtime Team
> 状态:**FROZEN v1(契约冻结)** —— 回应 `agent_swarm#14`(来自 HM #28/#45 客户端「任务驾驶舱」查询面:stop 端点 + ID 映射 + 状态机)。对齐对象:Heicode Manager Runtime Team。
>
> 冻结结论(供 HM #45 接 stop 真实调用):
> - **stop 端点**:`POST /api/agent/swarm/deployments/{id}/stop`(§3),已实现,幂等键 `X-Idempotency-Key`。
> - **ID 映射**:`deployment_id ↔ swarm_id ↔ manager_deployment_id`(§3.2),`get_run_by_identifier` 支持任一 id 查询。
> - **状态机**:运行时真实状态见 §4;与客户端 #28 期望(含 `preparing/degraded/verifying`)的**映射表见 §4.1**(运行时不新增臆造状态,规则 #9)。
>
> 依据:
> - HM 侧裁定 `heicode-mananger/docs/integration/heicode-swarm-deferred.md`:**HM 当前不实现 swarm runtime**;多 Agent 蜂群归 `agent_swarm` / `HeiCode-Swarm`(本仓);HM 侧待本仓给出正式 `/api/agent/swarm/*` 接口后,在 HM 的 `docs/integration/` 另立契约跟踪。
> - HM 侧裁定 `heicode-mananger/docs/integration/heicode-swarm-deferred.md`:**HM 当前不实现 swarm runtime**;多 Agent 蜂群归 `agent_swarm` / `HeiCode-Swarm`(本仓)。
> - 既有运行时集成范式 `heicode-mananger/docs/integration/heicode-am-contract.md`(单 Agent 模板 Agent,经 AM 启动)。本契约沿用其鉴权、回调、env、路径覆盖等约定。
>
> 本文是 HM 侧 deferred 锚点所要求的「Swarm 侧正式接口」起草。冻结需 Manager Runtime Team 评审。
> - 事件 envelope/类型见 [`event-schema.md`](./event-schema.md)(同批冻结,`agent_swarm#15`)。
## 1. 角色与边界
@@ -65,7 +69,23 @@ create 响应 `data`:`deployment_id`、`runtime_deployment_id`、`manager_depl
| `failed` | 存在失败且不可恢复 | 任务终态含 failed |
| `stopped` | Manager 主动停止 | `…/stop` |
每次状态变更通过回调 `deployment.status_changed` 推送(见 §5)。
每次状态变更通过回调 `deployment.status_changed` 推送(见 §5);**终态另发** `swarm.completed`/`swarm.failed`/`swarm.stopped`(客户端驾驶舱据此切终态横幅,见 event-schema §4)。
### 4.1 与客户端期望状态的映射(#14.3 决议:映射,不新增运行时状态)
客户端 #28 期望 `created/preparing/running/waiting_approval/degraded/verifying/completed/failed/stopped`。本运行时**不臆造** `preparing/degraded/verifying` 等中间态(规则 #9:无真实信号不造态),由 HM/客户端按下表映射现有真实状态:
| 客户端期望态 | 运行时真实来源 | 映射口径 |
|---|---|---|
| `created` | create 成功、尚无在途任务 | 初始 `running` 前的瞬态;HM 可在落 `runtime_swarm_id` 后、首个 `task.*` 前显示 |
| `preparing` | 同上(播种中) | 映射到 `running`(种子已注入、Agent 尚未自选);无独立状态 |
| `running` | `running` | 直通 |
| `waiting_approval` | `waiting_approval` | 直通 |
| `degraded` | `blocked` | `blocked`(移交/依赖/审批驳回阻塞)映射为 `degraded`;P-guard `run.metadata["health"]` 不健康亦可佐证 |
| `verifying` | `running` + 交叉评审进行中 | 评审期仍是 `running`;如需细分,由 `timeline.updated`(`Review cycle N`) / `run.metadata["cross_review"]` 提示 |
| `completed`/`failed`/`stopped` | 同名终态 | 直通;并有 `swarm.{completed,failed,stopped}` 终态事件 |
> 即:运行时真实状态集 = `waiting_approval/running/blocked/completed/failed/stopped`(§4 表);`created/preparing/degraded/verifying` 是**展示层别名**,不改变运行时语义,也不进入回调 `status` 字段。如客户端坚持要真实细分态,需另立工单评估状态机扩展(非本次冻结范围)。
## 5. 回调(已与 HM 处理器对齐)
@@ -88,9 +108,13 @@ HM 侧可用 env 覆盖:`AGENT_RUNTIME_BASE_URL`、`AGENT_RUNTIME_SERVICE_TOKE
- `env`/请求体不得含明文密钥;凭据经 `secret_ref`(`azkv://`)注入。详见 [`security-boundary.md`](./security-boundary.md)。
- Swarm 启动接口必须 HTTPS / 私网;回调走 HTTPS。
## 8. 待对齐项(冻结前需 Manager Runtime Team 确认)
## 8. 冻结状态与剩余对齐项
- [ ] resume / retry 是否需要独立外部端点,还是沿用 approvals + 内部重做。
本次冻结(agent_swarm#14)已定稿:stop 端点(§3)、ID 映射(§3.2)、状态机 + 客户端映射表(§4/§4.1)、终态 swarm.* 事件。HM #45 可据此接 stop 真实调用。
剩余非阻塞项(不影响 #45 Phase1 接入):
- [ ] resume / retry 是否需要独立外部端点,还是沿用 approvals + 内部重做(当前:无独立端点)。
- [ ] HM 是否以 AM 同款 `/agents` 生命周期(而非 `/api/agent/swarm/*`)调用 Swarm;若是,需路径映射。
- [ ] `event-schema` 中各 event_type 的必填字段与 HM 注册表逐项核对(见 event-schema.md)。
- [ ] HM 注册表(`agent_callback.go`)登记本次新增的 `swarm.completed/failed/stopped`、`approval.approved/rejected`、`handoff.created`(见 event-schema §4 ⭐)。
- [ ] 更新 HM 侧 `heicode-swarm-deferred.md` 锚点,登记本契约。
- [ ] #46 Phase2(SSE 长连 + 追加输入写语义):待本契约 + event-schema 冻结后由 HM 把轮询升级 SSE(见 frontend-event-api.md §4)。