* dream(swarm): #3242 fix AgentPool health checks that could never detect a dead agent performHealthChecks() stamped lastHeartbeat = now on every "still healthy" tick, and updateAgentHeartbeat() (the only other legitimate way to advance that field) has zero callers anywhere in v3/ — so the check's own tick was the sole thing keeping every pooled agent's heartbeat fresh, meaning timeSinceLastActivity could never exceed one health-check interval and replaceUnhealthyAgent() could never fire for any agent, in any pool, ever. Fixes the self-stamp; matches the correct pattern already implemented in UnifiedCoordinator.checkHeartbeats() in the same package. New deterministic test file (fake timers): baseline fails 2/3 tests, candidate passes 3/3, 223/223 full package suite, tsc clean. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv * dream(swarm): fold in adversarial-critique caveats, re-verify, ACCEPT-scoped Independent critic (separate context) confirmed the fix and re-derived everything itself (zero-callers grep, full suite, tsc, stash-isolated baseline/candidate). Two disclosed, non-blocking caveats folded into the gist: (1) @claude-flow/swarm is not currently a dependency of the shipped @claude-flow/cli, so tonight's fix is real within the swarm package's own UnifiedSwarmCoordinator/tests but has no reachable path through the actual CLI today; (2) health still optimistically recovers +0.1/tick inside the grace window even with zero real heartbeats (pre-existing, out-of-scope, mirrors UnifiedSwarmCoordinator's own no-tick-recovery design). Also fixes a cosmetic class-name typo in the source comment (UnifiedCoordinator -> UnifiedSwarmCoordinator). Re-ran full suite (223/223) and tsc (clean) after the comment edit; recomputed the witness stamp over final content. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv * dream(swarm): update LEDGER.md for #3242/#3243 Records tonight's row plus verified GitHub state for the trailing nights (MCP tools, not inferred) and the 2026-09-08 gap note (same ledger-rows-only-reach-main-via-merge structural issue tracked since 2026-08-19). Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv --------- Co-authored-by: Claude <noreply@anthropic.com>
22 KiB
Swarm SOTA Report — 2026-09-09
TL;DR: Tonight's swarm deep-dive found that AgentPool.performHealthChecks() (v3/@claude-flow/swarm/src/agent-pool.ts) has been silently unable to ever detect a dead/hung agent, in every agent pool the coordinator creates, since the method was written: its "still healthy" branch stamped pooled.agent.lastHeartbeat = now on every tick, and updateAgentHeartbeat() — the only other legitimate way to advance that field — has zero call sites anywhere in the v3/ tree. The health check's own tick was therefore the sole thing keeping every pooled agent's heartbeat "fresh," so timeSinceLastActivity could never exceed one health-check interval, the healthCheckIntervalMs * 3 unhealthy threshold could never be crossed, and replaceUnhealthyAgent() could never fire — for any agent, in any pool, ever — regardless of whether the agent was actually still alive. Fixed by removing the self-stamping side effect, matching the correct pattern already implemented next to it in the same package (UnifiedCoordinator.checkHeartbeats(), which never touches lastHeartbeat on its "still fine" branch). Five research roles ran in parallel tonight; the deep-researcher/competitor-analyst role's top pick (a gossip-consensus per-key-versioning correctness bug) and three other candidates are documented below as strong candidates for future nights, not folded into tonight's patch, per repo convention (one conceptual change per PR) and the current PR backlog.
What's New in 2026
| Finding | Source | Confidence |
|---|---|---|
| CP-WBFT: confidence-weighted vote tallying (not static trust) tolerates up to 85.7% faulty-node rate across topologies in LLM-agent swarms | arXiv:2511.10400 (Nov 2025) | C (single paper) |
| Self-Anchored Consensus: decentralized filter-and-refine with formal (F+1)-robustness; corroborates CP-WBFT's general direction | arXiv:2605.09076 / OpenReview (2026) | B (two independent papers converge on "confidence/plausibility filtering beats naive majority vote under Byzantine LLM agents") |
| Token Coherence (MESI-for-agents): cache-coherence-style lazy invalidation cuts broadcast token cost 84–95% vs. full-state rebroadcast | arXiv:2603.15183 (2026) | C (simulation only) |
| CoAgent/MTPO: classical 2PL/OCC "surrenders nearly all concurrency gains" for LLM-agent transactions; monotonic-trajectory pre-order + speculative repair holds 95% serial correctness at 1.4x speedup | arXiv:2606.15376 (2026) | C (single paper's own benchmark) |
| CRDT-for-LLM-agent-state is a thin literature (StateFuse, CodeCRDT are the only direct hits) | arXiv:2607.05844 (2026) | C — explicitly thin, no repo/production adoption found |
| Kairos: workflow-aware priority + memory-aware dispatch cuts multi-agent end-to-end latency 17.8–28.4% vs. prior SOTA | arXiv:2508.06948 (Aug 2025) | C (single paper, no public repo found) |
| Gossip re-litigated as the missing decentralized-diffusion layer for agentic coordination, complementing (not replacing) structured A2A/MCP-style RPC | arXiv:2508.01531, arXiv:2512.03285 | B (two independently-authored papers converge) |
| CrewAI hierarchical-mode load balancing does not actually happen — manager runs tasks sequentially, not by intelligent routing (GitHub issue #4783, filed Mar 2026) | github.com/crewAIInc/crewAI/discussions/1220 + independent TDS analysis | B (issue + 2 independent write-ups) |
Explicit thin-search notes from tonight's deep researcher: CRDT-for-multi-agent-LLM and "SwarmX" mechanism detail are both under-corroborated (single source, no independent reproduction, no public repo found) — treated as leads, not settled claims.
Ruflo Current Capability
AgentPool (v3/@claude-flow/swarm/src/agent-pool.ts) is wired live into UnifiedCoordinator.initializeAgentPools() (unified-coordinator.ts:624-660), which creates one pool per AgentType and one per AgentDomain, both during initialize() — this is not orphaned code, every coordinator instance runs it. performHealthChecks() (line 368, invoked every healthCheckIntervalMs via startHealthChecks()'s setInterval) is the pool's only dead-agent detector: it checks timeSinceLastActivity = now - pooled.agent.lastHeartbeat against unhealthyThresholdMs = healthCheckIntervalMs * 3, decrementing health and eventually calling replaceUnhealthyAgent() if an agent goes silent. Before tonight's fix, the "still healthy" branch also executed pooled.agent.lastHeartbeat = now, and grepping the entire v3/ tree for .updateAgentHeartbeat( calls (the only other public API that legitimately advances this field) returns zero hits — so this self-stamping was the only thing touching lastHeartbeat after agent creation, for every agent pool in the system. The sibling implementation in the same package, UnifiedCoordinator.checkHeartbeats() (line 956), gets this right: it only advances lastHeartbeat from handleHeartbeat() (a genuine external event) or recoverAgent() (an explicit reset after recovery), never merely from "the check ran and found things fine."
Competitor Comparison
| System | Topology/orchestration | Consensus/coordination | Fault tolerance / retry | Load balancing |
|---|---|---|---|---|
| LangGraph | Explicit graph, supervisor/swarm patterns | None — single supervisor decides | Built-in RetryPolicy/TimeoutPolicy, checkpointed durable resume |
Deliberately delegated to Temporal/Bedrock AgentCore in 2026 practitioner patterns |
| AutoGen → MS Agent Framework | Peer-to-peer → explicit handoff-graph (GA Apr 2026) | None — routing is agent-made at handoff time | AutoGen placed in maintenance mode Oct 2025 (real gap: unmaintained-fork risk) | Not addressed in either lineage |
| CrewAI | Sequential/Hierarchical (auto-manager) | Manager "decides" delegation but documented broken (issue #4783, Mar 2026) — runs sequentially, not by routing | Weaker than LangGraph per a 2026 comparison | Real, recent, corroborated gap: hierarchical mode's stated load-balancing purpose doesn't work in practice |
| OpenAI Agents SDK | Handoff-based, no central graph | None by design — single-owner control transfer | Guardrails + Tracing substitute for retry | Deliberately avoided — conversational baton pass, not a worker pool |
| Ray Serve | Independently autoscaling deployments per component | None — Ray actor model/supervision trees | Strong: fault isolation per component | Solved well — per-component autoscaling on the same signal, different thresholds |
| Temporal | Durable-execution outer layer, agents as activities | None — whatever the workflow code implements | Best-in-class: Nexus GA + Multi-Region Replication GA (99.99% SLA, early 2026) | Load-balances workflow tasks, not agent-reasoning routing |
Synthesis: none of the six mainstream frameworks implement formal multi-agent consensus (raft/BFT/gossip) the way Ruflo's consensus/ package does — they substitute a single decision-maker or infrastructure-level durability instead. That's a genuine differentiator, but also means there's no competitor prior art to benchmark Ruflo's consensus code against; the academic literature above is the more relevant comparison set for that specific package.
Hypothesis
Given an
AgentPool-managed agent that has stopped reporting real liveness (in production, every agent, sinceupdateAgentHeartbeat()has zero callers), whenperformHealthChecks()'s "still healthy" branch is changed to no longer stamplastHeartbeat = nowas a side effect of merely running the check, then an agent silent for longer thanhealthCheckIntervalMs * 3should be correctly flagged unhealthy (status: 'error', health decremented) and, once health reaches 0, replaced viareplaceUnhealthyAgent()— relative to today's baseline where this never happens regardless of how long an agent has been silent — subject to: (1) an agent receiving genuineupdateAgentHeartbeat()calls within the grace window continues to be treated as healthy (no regression for legitimately-alive agents); (2) the health+0.1recovery increment for agents within the grace window is preserved; (3) zero added latency, $0 cost, fully deterministic test coverage; (4) the full existing test suite remains green.
Frozen before evaluation; not modified after seeing results.
Benchmarks / Evaluation
evaluated: accepted (ACCEPT-scoped — see adversarial critique below for the scoping caveats). Real evaluator: Vitest 4.1.8, deterministic (fake timers), zero LLM calls, $0 cost.
Candidate diff: v3/@claude-flow/swarm/src/agent-pool.ts (1 line removed + explanatory comment). New test file: v3/@claude-flow/swarm/__tests__/agent-pool.test.ts (3 tests, ~100 lines — no test file existed for this class before tonight).
Baseline vs. candidate, isolated via git stash of just the source file (test file kept): 2 of 3 tests fail against baseline exactly as predicted — a silent agent (no updateAgentHeartbeat() calls, 5 health-check ticks past creation) stays status: 'idle'/health: 1.0 instead of becoming 'error', and a fully-silent agent is never replaced (replaceUnhealthyAgent() never fires, confirmed via a zero-length agent.replaced event list) even after 9 ticks — enough time for 5 unhealthy decrements (1.0 → 0.0 in 0.2 steps) under the fix. The 3rd test (a genuinely-alive agent receiving real updateAgentHeartbeat() calls every tick stays healthy) passes both ways, confirming the fix doesn't change behavior for agents that already report real liveness — it only stops treating silence as liveness.
Full @claude-flow/swarm package suite: 223/223 passing (220 pre-existing + 3 new), 0 regressions. tsc --noEmit: 0 errors.
Independent adversarial critique (STEP 10), re-run by a separate subagent with no authoring context: verdict CONFIRMED-WITH-CAVEATS. The critic independently re-verified the zero-callers claim, re-ran the full suite and typecheck from scratch, reproduced the baseline-fails/candidate-passes stash isolation itself, and hand-recomputed the tick arithmetic. Two disclosed caveats, neither blocking: (1) @claude-flow/swarm is not currently a dependency of the shipped @claude-flow/cli package — only one string-literal reference exists in cli/src (an ownership-table entry, not an import) — so tonight's fix is real and exercised by the swarm package's own UnifiedSwarmCoordinator/tests (initializeAgentPools()/initializeDomainPools() → pool.initialize() → startHealthChecks(), and assignTaskToDomain() → pool.acquire()), but has no reachable path through the actual npx ruflo/claude-flow CLI today — its present blast radius is the swarm package's public API, not an active CLI hot path; (2) health still optimistically increments +0.1 every tick for an agent inside the 3x grace window even with zero real heartbeats — health isn't strictly liveness-derived until the threshold is first crossed. This is a smaller, pre-existing, out-of-scope characteristic (detection/replacement is now correctly gated purely by lastHeartbeat timing, independent of the health score) mirroring UnifiedSwarmCoordinator.checkHeartbeats()'s own no-tick-based-recovery design, not a residual instance of tonight's bug. Corrected verdict: ACCEPT-scoped — scoping to "the self-stamping detection bug is fixed and tested, real within the swarm package's own coordinator" rather than an unqualified claim about the shipped CLI's current hot path.
Darwin Results
Skipped — same class as most recent nights: this is a binary correctness fix (an agent is either correctly detected as unhealthy or it isn't), not a continuous/categorical parameter with a fitness gradient for Darwin's real interface (npx ruvector harness darwin --help: routing/topology/prompt/memory/tool/tier/context/coordination genome parameters against an LLM-scored bench corpus) to search over.
Flywheel Evidence
No .claude-flow/flywheel/ state or signed @metaharness/flywheel bundle exists in this repo (confirmed via npx ruvector harness flywheel --help — real interface exists for verify/gate but targets a replay bundle this deterministic code-correctness fix doesn't produce). Evidence retained as: the 3 new tests, this gist, the linked issue, and the stash-isolated baseline-fails/candidate-passes result above — consistent with every accepted dream-cycle night since 2026-08-18. Classified: OBSERVATION (self-stamping lastHeartbeat = now confirmed via direct code read + zero-callers grep on updateAgentHeartbeat) / MEASUREMENT (baseline fails exactly the 2 discriminating assertions, candidate passes all 3, 220/220 pre-existing tests unchanged) / INFERENCE (production blast radius is total — every agent pool, every domain/type — but this session cannot measure how often a real agent actually goes silent for 3+ health-check intervals in live traffic) / DECISION (ship as a 1-line, additive-comment fix) / REJECTION (none this round).
Reward Hack Check
No standalone reward-hack CLI reachable this session (npx ruvector harness --help lists status/route/flywheel/darwin, no generic diff/benchmark scanner — consistent with recent nights). Manual checklist: no test weakened (new file, existing 220 tests untouched and re-verified green); no gold-label leakage (no LLM/gold-answer path in this code); no cherry-picking (full package suite reported both ways, all pre-existing tests identical); no seed manipulation (deterministic fake-timer arithmetic, hand-verified: 1.0 → 0.0 in 0.2 steps starting at tick 4 for a 1000ms interval); zero cost (no model calls anywhere in the fix or its tests). Independently re-verified by an adversarial-critic subagent with no authoring context (see Security Review).
Security Review
Not primarily security-sensitive, but directly reliability-relevant: before this fix, a hung/crashed agent in any pool would be silently retained forever by the pool's own bookkeeping (never marked 'error', never replaced), meaning tasks could keep being routed to (or the pool could keep reporting healthy capacity around) an agent that has actually stopped functioning — an availability/correctness gap, not a new attack-surface, credential-exposure, or authority-boundary change. The fix itself is a pure deletion (one line) plus a comment; no new I/O, network, or filesystem surface introduced.
Scan Findings: ruview-integration
No live "ruview" runtime path exists in this codebase. Grep across the repo shows exactly two kinds of surface: documentation/ADRs (v3/docs/adr/ADR-325/326/327, v3/docs/examples/dossiers/ruvnet/ruvnet.md) describing RuView as an external rUv project a hypothetical future "Cognitum product plane" would federate with, and a metaharness scaffold string vertical:ruview (plugins/ruflo-metaharness/skills/harness-mint/SKILL.md:42) — unchanged since the 2026-07-24 scan, still just a template label. New since that scan: v3/@claude-flow/security/src/policy/product-plane.ts (1,381 lines, added 2026-08-13) implements a real, well-tested (36 passing cases) policy/schema layer for RuView product-plane actions (RuViewSemanticObservationV1, validateRuViewSemanticObservation(), scopes like ruview.space.read/ruview.device.command) — but it is declared-but-unenforced: exported via policy/index.ts with zero callers outside its own test file, no CLI command, no MCP tool, and no actual network client or device-command executor anywhere in the codebase. Net: since the last scan, this surface went from "template label only" to "a real, unwired policy schema for a system with no runtime caller" — worth flagging precisely because the ADR + tested-validator combination is easy to mistake for a live enforcement path.
Scan Findings: ruvector-integration
Three previously-flagged findings (MoE computeLoadBalanceLoss()/routingEntropy computed-never-applied in moe-router.ts:749-760/413, SONAManager.consolidateEWC() decaying a never-populated Fisher map in sona-manager.ts:566-577, orphaned PersistentSonaCoordinator) remain unaddressed, confirmed unchanged tonight. New finding: v3/@claude-flow/cli/src/ruvector/q-learning-router.ts:82 declares eligibility?: Float32Array on QEntry, commented "Eligibility trace for TD(lambda)" — but nothing in the 935-line file (or any caller in cli/src/__tests__) ever sets, reads, or decays it; updateQValue() (lines 526-549) is plain one-step TD(0), and there's no lambda field anywhere in QLearningRouterConfig. A smaller-scope sibling of the known MoE/SONA "computed-but-unused" pattern — this one fully vestigial (never computed at all) rather than computed-and-dropped. Also confirmed correctly wired and unremarkable tonight: router-calibrator.ts, router-parallel-recorder.ts, lora-adapter.ts's version-gated checkpoint detection, diskann-backend.ts/vector-db.ts fallback paths.
Additional candidates surfaced but not selected tonight (STEP 3.2)
Scored under 0.25·Ruflo_fit + 0.20·testability + 0.20·measurability + 0.15·production_value + 0.10·novelty + 0.10·reviewability (1-5 each); tonight's architecture-reviewer finding (AgentPool heartbeat self-reset) scored a maximum 5.0/5.0 (5 on every dimension — universal blast radius across every pool, a clean 1-line fix, and a pre-existing correct sibling implementation in the same package to pattern-match against) and was selected over the deep-researcher's top external-literature pick:
- Gossip consensus per-key-versioned merge (score 4.65,
consensus/gossip.tshandleStateMessage/antiEntropy) — a single scalarnode.versiongates overwriting the entire state map, so an unrelated key update from a peer can clobber a locally-newer key; three independent 2026 papers (Token Coherence, StateFuse, CoAgent) converge on "ship deltas with fine-grained ordering, not whole-state." Real correctness bug, deterministic test plan ready — strong pick for a future swarm night, contingent on how much production traffic actually uses gossip- vs. raft-mode consensus (CLAUDE.md's documented default is raft). - Byzantine confidence-weighted vote tally (score 4.55,
consensus/byzantine.tsvote()) —ConsensusVote.confidence(types.ts:222) is declared and populatable but never read invote()'s binary tally; CP-WBFT/SAC (2025-26) show confidence-weighting beats binary majority under Byzantine LLM agents. Flagged risk: superficially resembles the already-fixedweightedConsensus()trust-weight bug (different file, different mechanism — static trust vs. per-vote confidence) — a reviewer could reasonably ask "didn't we just do this." - Broadcast-path retry/failure blackhole (score 4.55,
message-bus.tshandleDeliveryError) — the repo's own code comment (added by the 2026-08-19 merged fix) already discloses that broadcast retries re-queue under a'broadcast'-keyed queue nobody drains, silently losing a failing broadcast subscriber's retries/failure event. Lowest novelty (self-disclosed, not newly discovered) but narrowest, safest scope. - AgentPool.acquire() load/health-aware selection (score 3.75) — picks from
availablevia bareSetiteration order, ignoringusageCount/health/workload; Kairos/SwarmX (2025-26) show workload-aware dispatch cuts latency 17.8-28.4% in shared-serving settings (lower direct transfer to Ruflo's local-process pool). - Phi-accrual adaptive heartbeat detector (score 3.55,
unified-coordinator.tscheckHeartbeats()) — replaces the textbook-naive fixed3x intervalcutoff with a jitter-aware suspicion score (Hayashibara 2004, used in Cassandra/Akka/Hazelcast); weakest "genuinely new" claim of the five (foundational, not a 2026 paper) but a real robustness improvement, and complementary to tonight's fix (tonight fixes whether the detector can fire at all; this would fix how well-tuned its threshold is).
Also noted, lower priority: TaskOrchestrator.getTaskMetrics()'s totalQueueTime is declared and included in averageQueueTime's denominator but never incremented anywhere (coordination/task-orchestrator.ts) — always reports 0; and getQueuePosition()'s local priorityOrder map uses 'normal' where the real TaskPriority type (and the correct maps in getNextTask()/getPriorityQueue() in the same file) use 'medium' — the task-queued telemetry event silently mis-ranks the default priority. Both are real but lower production value (metrics/telemetry only, not behavior-affecting) than tonight's pick.
SOTA Proof & Witness
| Field | Value |
|---|---|
| Session commit | e341ec8c4aba8ea616499180dee53035af7e295c |
| Gist SHA-256 (pre-witness content) | 8b8b5639fc6b2cbe87a143b0b5bd3c56ce72723ab1da977c15dcac12fbf61ac7 |
| Witness stamp | 008c96a0f16f7b39516a8903e367624ddb1c149463d1561502dee84a92c47d99 |
Verifier procedure: fetch docs/dream-cycle/dream-gist-2026-09-09.md from this branch, strip the witness table's filled values back to PENDING, SHA-256 it, concatenate with the session commit above, SHA-256 again — result must equal the witness stamp.
Recommended Next Steps
- Merge the linked draft PR (human review required) — a 1-line, fully reversible fix restoring dead-agent detection across every
AgentPoolinstance the coordinator creates, with a discriminating baseline-fails/candidate-passes regression test and zero existing-test churn. - Pick up the gossip per-key-versioning correctness bug (candidate 1) for a future
swarmDEEP night — it's a real lost-update bug backed by converging 2026 evidence, not just a perf tweak. - Merge backlog: as of tonight, 3 swarm-surface PRs remain unmerged from prior nights — #3086 (2026-08-24, weightedConsensus, now 16 days old and STALE per the >14-day rule, 0 follow-up comments) and #3123 (2026-08-29, rebalanceHybrid, 11 days old, has reviewer comments, not yet stale). Trailing-14-night merge signal has improved materially since the "0 of last 14" note in earlier gists: 3 of the last 9 dream-cycle PRs (09-03, 09-05, 09-07) merged in the past week.
- Wire or remove the two lower-priority
TaskOrchestratortelemetry bugs (deadtotalQueueTime, wrong'normal'/'medium'priority key ingetQueuePosition()) — small, safe, good "single-file" candidates for a future night. ruviewproduct-plane policy layer (product-plane.ts) is real and tested but completely unwired — worth a future decision (wire it into a real CLI/MCP entry point, or explicitly document it as forward-looking-only) so it doesn't accumulate as orphaned surface the wayPersistentSonaCoordinatordid.