Files
Agentswarm/docs/integration/event-schema.md
FastheiandClaude Opus 4.8 1edf73aba4 fix(swarm/#70): 任务超时透传+提默认 / 事件时间线降噪 / 失败 termination_reason 准确
真机端到端实测(#70)暴露三问题,本 PR 全部修复(仅 agent + orchestrator,不跨仓):

问题1【阻断】单任务超时只有 60s,生成类任务必挂
- agent/main.py: TASK_TIMEOUT_SECONDS 默认 60→300(仅对外部/独立启动 agent 生效)。
- agent_launcher.py: 新增 DEFAULT_TASK_TIMEOUT_SECONDS=300、_budget_duration_seconds、
  resolve_task_timeout(base=env 默认 300,与 run budget.duration_seconds 取较小);
  plan_launch_specs 把 TASK_TIMEOUT_SECONDS 透传进每个 agent env(非敏感,inline,
  k8s 不进 Secret)。

问题2【体验】事件时间线全是内部噪音(纯附加,未碰冻结契约)
- swarm_runtime.py: is_client_visible(=event_type∈FROZEN_CLIENT_EVENT_TYPES,单一真源);
  emit_event 给 envelope 加 metadata.client_visible 布尔 + 关键客户端事件回填可选
  payload.message(人话进度,仅取已有字段,不伪造)。task.heartbeat/retried/
  deployment.status_changed/timeline/budget 标 client_visible=false,仍持久化+回调
  但客户端据此过滤出时间线。冻结事件集/类型/sequence/artifact 形状一字未动。
- event-schema.md: 文档化两个附加字段 + 新增 §6.1,明确未解冻。

问题3【正确性】失败/超时 termination_reason 仍报 "tasks_completed"
- convergence.py: 新增 TIMEOUT/MAX_RETRIES_EXCEEDED/TASK_FAILED;classify_failure_reason
  按 timeout→max_retries→task_failed 取最具体(仅凭真实 per-task 信号);FAILED 分支
  再不会返回 tasks_completed(该 reason 仅用于成功),budget/rounds 仅在通用失败时才覆盖。
- task_queue.py: fail_task 永久失败时把 reason 落到 task.result({"success":false,"error":reason}),
  不覆盖已有结果,供 convergence 读取。
- main.py: compute_convergence_report 快照补 retry_count/max_retries。

测试:新增 test_resolve_task_timeout、扩 test-convergence(failed_timeout/max_retries/
generic + "FAILED 永不报 tasks_completed"不变量)。本地全过:test-agent-launcher /
test-convergence / test-runtime-contract / test-contract-freeze / test-merge-smoke /
test-workflow-e2e / test-security-boundary。

影响:agent + orchestrator + 文档;不动 Manager↔Swarm 冻结契约字段(问题2 纯附加)。
栈在 #64(agent_swarm git 注入)之上,#64 合并后本 PR base 自动转 main。

Closes #70

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:29:39 +08:00

12 KiB
Raw Permalink Blame History

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。

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

{
  "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": {                      // 自定义元数据(不得含明文密钥)
    "client_visible": true           // agent_swarm#70:是否客户端 cockpit 可见事件(= event_type ∈ FROZEN_CLIENT_EVENT_TYPES)。
                                      //   HM/客户端据此过滤内部噪音(heartbeat/retried/deployment.status_changed/timeline/budget),
                                      //   无需硬编码冻结集。**仅展示层过滤标记,不改变冻结事件集/类型/sequence/artifact 形状。**
  },
  "payload": {                       // 事件专属字段(见 §4)
    "message": "..."                 // agent_swarm#70:可选人话进度(仅客户端可见事件,缺省由运行时按 type+已有字段回填,不伪造新数据)
  },
  "artifact": { }                    // 可选;见 §3
}

3. Artifact 子对象(artifact.created 及完成事件可带)

{
  "artifact_id": "art_...",
  "artifact_type": "code_patch | document | deployment_manifest",
  "title": "...",
  "summary": "...",
  "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 一致)

⭐ = 冻结的客户端事件(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。

5. HM 响应

{ "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 完成处补两字段为后续小修。

6.1 agent_swarm#70 时间线降噪(附加,未触碰冻结契约)

回应 #70「事件时间线全是内部噪音」。未改冻结事件集 / 事件类型 / sequence / artifact 形状,仅两处纯附加增强(实现 swarm_runtime.emit_event,is_client_visible 为单一真源):

  1. metadata.client_visible(envelope,附加):每事件带布尔标记 = event_type ∈ FROZEN_CLIENT_EVENT_TYPES。客户端 cockpit 只渲染 client_visible=true 的事件,task.heartbeat/task.retried/deployment.status_changed/timeline.updated/budget.alert 等内部/调度事件 client_visible=false,仍持久化+回调(HM 控制面照常消费)但不入客户端时间线。这是展示层过滤标记,不改变运行时语义。
  2. payload.message(附加,可选):关键客户端可见事件(task.created|claimed|running|completed|failed、swarm.completed|failed|stopped)回填一句人话进度,仅取已有 payload 字段(role/title/summary/reason/termination_reason),不伪造新数据(规则 #9);emitter 已提供 message 时不覆盖。

HM 侧无需改动即可消费(忽略 metadata.client_visible/payload.message 不影响契约);客户端可立即按 metadata.client_visible 过滤并展示 payload.message。如需把 client_visible 提升为 envelope 顶层字段或新增「内部事件」分类口径,属契约扩展,须与 HM 联调后另立工单(非本次范围)。

契约由 scripts/test-contract-freeze.py 守护(断言 sequence 单调、13 类 round-trip、artifact 形状、审批/停止真实发出、明文凭据脱敏);scripts/test-workflow-e2e.py 在全流程 e2e 中额外断言 swarm.completed + sequence 无空洞。