Files
ruflo/docs/dream-cycle/dream-gist-2026-09-09.md
rUvandClaude 40878cca35 dream(swarm): #3242 fix AgentPool health checks that could never detect a dead agent (evaluated, ACCEPT-scoped) (#3243)
* 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>
2026-09-09 13:25:33 +00:00

22 KiB
Raw Permalink Blame History

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, since updateAgentHeartbeat() has zero callers), when performHealthChecks()'s "still healthy" branch is changed to no longer stamp lastHeartbeat = now as a side effect of merely running the check, then an agent silent for longer than healthCheckIntervalMs * 3 should be correctly flagged unhealthy (status: 'error', health decremented) and, once health reaches 0, replaced via replaceUnhealthyAgent() — relative to today's baseline where this never happens regardless of how long an agent has been silent — subject to: (1) an agent receiving genuine updateAgentHeartbeat() calls within the grace window continues to be treated as healthy (no regression for legitimately-alive agents); (2) the health +0.1 recovery 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:

  1. Gossip consensus per-key-versioned merge (score 4.65, consensus/gossip.ts handleStateMessage/antiEntropy) — a single scalar node.version gates 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).
  2. Byzantine confidence-weighted vote tally (score 4.55, consensus/byzantine.ts vote()) — ConsensusVote.confidence (types.ts:222) is declared and populatable but never read in vote()'s binary tally; CP-WBFT/SAC (2025-26) show confidence-weighting beats binary majority under Byzantine LLM agents. Flagged risk: superficially resembles the already-fixed weightedConsensus() trust-weight bug (different file, different mechanism — static trust vs. per-vote confidence) — a reviewer could reasonably ask "didn't we just do this."
  3. Broadcast-path retry/failure blackhole (score 4.55, message-bus.ts handleDeliveryError) — 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.
  4. AgentPool.acquire() load/health-aware selection (score 3.75) — picks from available via bare Set iteration order, ignoring usageCount/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).
  5. Phi-accrual adaptive heartbeat detector (score 3.55, unified-coordinator.ts checkHeartbeats()) — replaces the textbook-naive fixed 3x interval cutoff 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.

  1. Merge the linked draft PR (human review required) — a 1-line, fully reversible fix restoring dead-agent detection across every AgentPool instance the coordinator creates, with a discriminating baseline-fails/candidate-passes regression test and zero existing-test churn.
  2. Pick up the gossip per-key-versioning correctness bug (candidate 1) for a future swarm DEEP night — it's a real lost-update bug backed by converging 2026 evidence, not just a perf tweak.
  3. 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.
  4. Wire or remove the two lower-priority TaskOrchestrator telemetry bugs (dead totalQueueTime, wrong 'normal'/'medium' priority key in getQueuePosition()) — small, safe, good "single-file" candidates for a future night.
  5. ruview product-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 way PersistentSonaCoordinator did.