# Swarm 运行时事件 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. **客户端事件集**(§4 标 ⭐)冻结为 `FROZEN_CLIENT_EVENT_TYPES`(`swarm_runtime.py`):#28 的 13 类核心 + #34 新增 4 类评审/返工(`review.started`/`review.decision_made`/`rework.requested`/`rework.completed`,**脱敏投影**,见 §4)= 17。 > 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)。 ## 1. 传输与鉴权 - Swarm → HM:`POST {callback.url}`(create 时下发)。 - 鉴权二选一:服务令牌 `X-Agent-Service-Token`(或 `Authorization: Bearer`),或 HMAC 签名。 - **HMAC 签名(机制已实现,规范与 HM 对齐;待主链路端到端联调验收)**: - 头:`X-Agent-Timestamp`(Unix 毫秒)、`X-Agent-Signature`、`X-Agent-Event-Id`、`X-Correlation-ID`(均含 `X-Agnet-` 兼容别名)。 - 规范串:`canonical = f"{timestamp}.{event_id}.{raw_body}"`。 - 签名:`X-Agent-Signature = "sha256=" + hex(HMAC_SHA256(secret, canonical))`。 - secret:`AGENT_CALLBACK_SIGNING_SECRET`(兼容 `AGNET_…`,支持 `…_REF` 的 `azkv://` 解析)。 - 时间容差:HM 默认 300s(`AGENT_CALLBACK_SIGNATURE_TOLERANCE_SECONDS`)。 - **幂等去重**:HM 依次按 `X-Agent-Event-Id` 头 → body `event_id` → `idempotency_key`。重复返回 `{inserted:false, idempotent:true, deduplicated:true}`。 ## 2. 事件 Envelope ```jsonc { "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(事件按此持久化) "agent_instance_id": "...", // Agent 实例/角色实例 ID(可选) "task_id": "...", // 任务相关事件填 "occurred_at": "2026-06-08T...Z", // 事件时间 "correlation_id": "corr-...", // 追踪 ID(X-Correlation-ID) "source": "heicode-swarm-runtime", // 事件来源标识 "metadata": { }, // 自定义元数据(不得含明文密钥) "payload": { }, // 事件专属字段(见 §4) "artifact": { } // 可选;见 §3 } ``` ## 3. Artifact 子对象(`artifact.created` 及完成事件可带) ```jsonc { "artifact_id": "art_...", "artifact_type": "code_patch | document | deployment_manifest", "title": "...", "summary": "...", "uri": "git://repo#branch | runtime://...", // 仅 uri,绝不含 secret_ref/凭据 "checksum": "", "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 一致) ⭐ = **冻结的客户端事件**(`FROZEN_CLIENT_EVENT_TYPES`,17 类 = #28 的 13 核心 + #34 的 4 评审/返工,驾驶舱据此渲染;HM `agent_callback.go` 据此对齐注册表)。`review.*`/`rework.*` 为**脱敏客户端投影**(仅 reviewer id/verdict/failed_criteria/affected_tasks/recommended_rework/root_cause + 安全标量;**不含** evidence/summary/rework_reason 自由文本)。其余为 Swarm 也发的辅助/运行时事件(HM 仍消费,但非客户端必需)。 | event_type | payload 必填字段(HM 校验) | 分类 | Swarm 是否发出 | |---|---|---|---| | ⭐ `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`(+ `summary`/`deliverable` 可选) | swarm_terminal | ✅(终态;**携带用户面结果** = 综述 + deliverable 摘要,客户端可直接取;完整见 `GET …/{id}/result`,#40) | | ⭐ `swarm.failed` | `status` | swarm_terminal | ✅(终态) | | ⭐ `swarm.stopped` | `status` | swarm_terminal | ✅(`stop_run`) | | ⭐ `review.started` | `cycle`, `reviewer_count` | review | ✅(#34,脱敏投影;交叉评审一轮开始) | | ⭐ `review.decision_made` | `accepted`, `rework_targets` | review | ✅(#34,脱敏投影;仲裁结论 + 逐评审者 verdict/criteria,无 evidence/summary) | | ⭐ `rework.requested` | `task_id`, `root_cause` | review | ✅(#34,脱敏投影;无 rework_reason/evidence) | | ⭐ `rework.completed` | `task_id`, `root_cause` | review | ✅(#34,脱敏投影;返工被重新接受) | | `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 | ✅ | | `handoff.requested` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ✅ | | `timeline.updated` | `title` | timeline | ✅(emit 时回填 `title`=`summary`) | | `budget.alert` | `threshold_pct` | budget | ✅(emit 时回填 `threshold_pct`) | | `swarm.pool_terminated` | `user_id`, `secret_ref` | lifecycle | ✅(HM #60 模型 key 吊销握手;**非**客户端事件,见下) | | `phase.changed` / `agent.*` / `sk_tool.*` | — | ordinary_sub / sk | ❌(本运行时不产生) | > 说明:评审/重做循环复用 `task.retried` + `timeline.updated`(`Review cycle N`)表达,无单独 review 事件类型;蜂群内部遥测(提案/竞价/交叉评审/收敛/health)**不经回调外发**(见各协议文档「事件不进 Manager 流」),故不在本表。`swarm.completed/failed/stopped` 与 `approval.approved/rejected`、`handoff.created` 为本次冻结**新增**,HM 注册表需据此登记(agent_swarm#15.2)。 > > **`swarm.pool_terminated`(HM #60 模型 key 吊销握手)**:HM 控制面生命周期事件,**非**客户端 cockpit 事件、**不**入 `FROZEN_CLIENT_EVENT_TYPES`、不渲染到驾驶舱。当某用户**所有 swarm run 均被 stop**(completed/failed 可经 `…/input` 重开故仍保 key)时,运行时发恰好一次,载 `user_id` + `secret_ref`(`azkv://` 引用,非明文 key)。HM 需在 `subscribed_events` + `agent_callback` 登记,收到即吊销该用户 per-user `sk-` + 清 KV。口径见 [runtime-contract §3.3.1](./runtime-contract.md)。 ## 5. HM 响应 ```json { "success": true, "event_id": "evt-...", "inserted": true, "idempotent": false, "deduplicated": false, "deployment_id": "dep_..." } ``` ## 6. 对齐状态(冻结时已修正 / 剩余小项) 冻结时 `orchestrator/swarm_runtime.emit_event` 已统一处理下列历史缺口: 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` 透传(未知则省略,不伪造)。 剩余小项(不影响客户端 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 无空洞。