#56: a created run only seeds the task; if no expert agent connects, the run hangs
at `running` forever with no `agent.*` events and an empty /result — and the cause
(P-guard already detects NO_AGENTS_CONNECTED) was only in run.metadata["health"] /
/diagnostics, never on the surfaces HM/cockpit actually poll (/events, /result).
This makes a stuck run explain itself (no new Manager event type, no contract change):
- launch_swarm_agents now records run.metadata["agent_launch"]
{backend, planned, launched, launched_ids, model_key_resolved, note}. The note
pinpoints WHY there are 0 agents — e.g. AGENT_LAUNCH_BACKEND=none (no auto-launch),
k8s launch failed (kubectl/RBAC + Pod Workload Identity, #16/#60 A.3), or the model
key didn't resolve. No secret recorded — only a model_key_resolved bool.
- /result and /diagnostics now carry `health` (P-guard blockers) + `agent_launch`.
- assess_swarm_health emits ONE `timeline.updated` per distinct blocker-set (registered
event; dedup by summary, reset when healthy) so /events and the cockpit (#39) show
"swarm blocked: no_agents_connected" instead of silence. Still NO unregistered
swarm.health event.
Scope: this surfaces the diagnosis. Actually executing a run still requires the
deployment to set AGENT_LAUNCH_BACKEND=kubernetes AND the #16/#60 A.3 Pod Workload
Identity / KV grant (infra, cross-team) — called out in the launch note.
Tests (scripts/test-swarm-guard.py): blocked run emits timeline.updated exactly once
(dedup) + still no swarm.health; /result + /diagnostics carry health w/ no_agents_connected;
launch_swarm_agents records the backend=none note. test-contract-freeze + test-agent-launcher
still green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The capability table claimed "🔴 规划中(采集器尚未落地)", but the benchmark
collector is in fact implemented under benchmark/: metric formulas (metrics.py:
S_swarm/G_E/G_E,c/Benchmark_Agent + governance/collaboration/communication/
robustness), live-run collection (collectors/), the 4 baseline runners (runners/),
G_E/G_E,c comparison (baselines.compare), self-cert merge (selfcert_collector),
and telemetry export to Cosmos/Blob (export/).
Updated the row to 🟡: collector landed; the remaining piece is the scoring
standard — the O (observability) formula, BASE_COEFFICIENTS, and S_gain thresholds
must be empirically calibrated from accumulated real user usage data (long-term
ticket). Keeps the honesty rule: metrics lacking real inputs return NaN, never
fabricated scores.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real-time event stream for the cockpit, reverse-proxied by HM to the client
EventSource (orchestrator SSE → HM → client). The SSE overlay over the existing
GET /events?after=<sequence>:
- Replays history after `after`, then holds the connection and pushes new events.
- Reuses the existing swarm_events:{swarm_id} store + per-swarm INCR sequence — no
new storage, no schema change.
- Each frame: `id: <sequence>` / `event: message` / `data: <envelope JSON>`, where
data is byte-identical to /events so SSE and polling share one cursor space (a
dropped SSE can fall back to /events?after=<last id> with no gap/dup).
- Supports Last-Event-ID header (takes precedence over the `after` query) for
end-to-end resume through HM.
- Heartbeat `: ping` every ~15s + X-Accel-Buffering:no to survive nginx ingress /
HM reverse-proxy buffering.
- Closes after a terminal event (swarm.completed/failed/stopped); releases on client
disconnect (request.is_disconnected).
- Auth: require_runtime_auth (service token) — caller is HM, never the client direct.
Adds TERMINAL_CLIENT_EVENT_TYPES to swarm_runtime. Events are already redacted at
emit time, so frames are streamed as-is. No change to the callback POST path,
event schema, sequence, or terminal definitions (event-schema v1 frozen).
Verified: contract-freeze / runtime-contract / merge-smoke / workflow-e2e all pass;
plus a dedicated SSE check (history replay, id ordering, Last-Event-ID resume,
byte-identical envelope, terminal close).
Part of #40 (the SSE half). HM reverse-proxy side = heicode-mananger#46.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>