Skip to content

dream(swarm): #3061 bound MessageBus retry attempts, fix unreachable message.failed (evaluated, ACCEPT) - #3062

Merged
ruvnet merged 4 commits into
mainfrom
dream/2026-08-19-swarm
Aug 21, 2026
Merged

ruvnet merged 4 commits into
mainfrom
dream/2026-08-19-swarm

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 19, 2026

Copy link
Copy Markdown
Owner

1. Hypothesis

Given a Ruflo MessageBus with a subscriber whose callback synchronously throws on every invocation, when addToQueue()'s re-queue path preserves the failed entry's attempts count (instead of resetting it to 0 on every re-queue), then the number of delivery attempts for that message should be bounded by config.retryAttempts and a message.failed event should fire exactly once, relative to the current unbounded-retry baseline, subject to: (1) delivery behavior for non-throwing subscribers is completely unchanged; (2) existing test suite remains green; (3) zero LLM/API cost.

Frozen before evaluation began; not modified after seeing results.

2. Candidate

v3/@claude-flow/swarm/src/message-bus.ts — MessageBus.handleDeliveryError() (:491-509) increments a queue entry's attempts counter and, if still under config.retryAttempts, re-queues via addToQueue(). But addToQueue() (:343-363) always constructed a fresh MessageQueueEntry with attempts: 0, discarding the count. A permanently-throwing subscriber retried forever instead of being bounded, and the message.failed event was structurally unreachable.

Fix: addToQueue() gains an optional existingAttempts: number = 0 parameter; the retry-path call now passes entry.attempts through instead of the implicit 0. The two fresh-message call sites in enqueue() are untouched (default preserved). Net diff: ~10 lines + a disclosure comment for a related pre-existing limitation found during adversarial review (see §9). Plus a new test file, v3/@claude-flow/swarm/__tests__/message-bus.test.ts (2 tests, ~110 lines).

3. Evaluation Receipt

evaluated: accepted. Real evaluator: vitest run, deterministic, $0, zero LLM calls. Full receipt in docs/dream-cycle/dream-gist-2026-08-19.md.

4. Baseline Comparison

Metric Baseline (pre-fix, git stash) Candidate (post-fix)
Callback invocations (500ms window) 96–98 (unbounded, still climbing) 3
message.retry events 96 (≈1:1 with invocations — every attempt retried) 2
message.failed events 0 1
Stability after +300ms n/a (still growing) Unchanged (3/2/1)
Healthy-subscriber control 1 delivery, 0 retry/failed (unaffected) 1 delivery, 0 retry/failed (unaffected)

Baseline independently reproduced twice: authoring session (98 invocations) and, separately, the adversarial critic via its own git stash (96 invocations, plus the additional observation that callbackInvocations === retryEvents exactly — the failure branch was provably unreachable).

5. Darwin Lineage

Skipped — scope mismatch. This is a deterministic correctness fix (thread an existing integer through one re-queue call), not a continuous/categorical parameter; @metaharness/darwin's real interface (darwin <config.json> --execute) evolves scoped tunable parameters against a benchmark corpus. Same class of skip as 4 of the last 5 dream-cycle nights.

6. Flywheel Evidence

No signed @metaharness/flywheel bundle — bespoke deterministic test, not an LLM-task corpus the replay/verify tooling targets, same as every algorithmic candidate the last 5 nights. Evidence retained as the committed test file + git-stash-verified before/after counts + the issue/gist.

7. Reward Hack Check

Manual checklist, independently re-verified by an adversarial critic (fresh session, no authoring context): no existing test weakened (new file only, 218 pre-existing tests untouched); no gold-answer leakage (test asserts public event counts / callback invocations only); no cherry-picking (both failure-path and healthy-path reported); no seed manipulation (fully deterministic); $0 cost. Critic specifically tried to construct a no-op/unrelated hack that would satisfy the exact-count assertions and concluded the actual diff is the minimal, principled fix — not a special-cased one.

8. Security Review

Not security-sensitive — confirmed by the critic. Purely in-process integer accounting (an optional parameter threaded through a private method, always called with a value already tracked in-process). No network/filesystem/credential/MCP-authority surface touched.

9. Regression Analysis

Full package suite: 220/220 passing (218 pre-existing + 2 new, 0 regressions), independently re-run by the critic. tsc --noEmit shows pre-existing environmental errors (missing @types/node/tsconfig lib config) identical with or without this diff (confirmed via git stash) — not attributable to this candidate, same class as multiple prior nights' documented environmental gap.

Disclosed, not fixed here: the critic's review surfaced a separate, pre-existing bug (present since commit 1d365d4, 2026-08-11, not introduced or worsened by this diff) — for a broadcast message, the retry re-queue uses the literal string 'broadcast' as the queue key (since message.to stays 'broadcast' on the message object) instead of the real per-subscriber agentId enqueue() fanned out to, so a failing broadcast subscriber's retries silently vanish into a queue nobody drains. Now flagged with an in-code comment at the fix site; not in scope for tonight's single-conceptual-change diff.

10. ADR

None created — single-file correctness fix to an existing retry-accounting mechanism, not an architectural decision, matching repo convention for scoped bug fixes.

11. Research Gist

docs/dream-cycle/dream-gist-2026-08-19.md (committed on this branch; no gist-creation tool available in this session, consistent with every dream-cycle night since 2026-08-14).

12. Issue

Closes #3061 (full research: 5-role parallel fan-out, ledger recovery for a 5-night append gap, ruview/ruvector scan findings, competitor comparison).

13. Witness

Field Value
Session commit 44b67bccdd23a99482aa093fbc52a0516bca7257
Gist SHA-256 5e61580eb87fe98f7a489e55eb1fa6dc6493dc78ebd7759b8d8aedb77b15fca9
Witness stamp 62c4fdf7750c8bda9cb3101a732fbe1ea39ca56cbd484a972ce5d8cb126ef052

Verifier procedure: fetch docs/dream-cycle/dream-gist-2026-08-19.md from this branch, SHA-256 it, concatenate with the session commit above, SHA-256 again — result must equal the witness stamp.

14. Merge Policy

Human review required. Do not self-merge. Do not autonomously promote Flywheel state. Verdict: ACCEPT — deterministic, independently-reproduced-twice evidence for a real correctness bug fix; adversarial critic verdict CONFIRMED-WITH-CAVEATS (caveats addressed via disclosure, not hidden); zero regressions; zero new attack surface.


Generated by Claude Code

claude added 3 commits August 19, 2026 06:07
The v2 live-entries section of docs/dream-cycle/LEDGER.md was never
appended to for 5 consecutive nights despite each night running to
completion (branch + draft PR + issue verified to exist for all 5 via
git ls-remote and GitHub search before concluding this, per the ledger's
own anti-inference rule). Backfilled directly from the PRs/issues.

Root cause of the append gap not diagnosed tonight; flagged in the
gist/issue as a candidate finding for a future automation/meta scan.
…e.failed

Given a subscriber whose callback always throws, MessageBus.handleDeliveryError()
incremented the queue entry's attempts counter but addToQueue() always
constructed a fresh entry with attempts:0 on re-queue, discarding it. Retries
were unbounded (96-98 invocations / 0 failures observed in 500ms, git-stash-
verified pre-fix) instead of capped at config.retryAttempts, and the
message.failed branch was unreachable.

Fix: addToQueue() takes an optional existingAttempts parameter; the retry
path in handleDeliveryError() now threads entry.attempts through instead of
resetting it. Fresh-message call sites are unaffected (default 0).

Candidate evaluated: exactly 3 invocations / 2 retries / 1 failure, stable
thereafter. 220/220 tests passing (2 new, 0 regressions). Independently
re-verified by an adversarial critic (fresh session, no authoring context):
CONFIRMED-WITH-CAVEATS — fix and scope match the claim; flagged (and now
disclosed in-code) a separate pre-existing, unfixed broadcast-retry queue-key
bug this diff does not touch.

Full research/evaluation writeup: docs/dream-cycle/dream-gist-2026-08-19.md
@ruvnet
ruvnet marked this pull request as ready for review August 21, 2026 15:18
@ruvnet
ruvnet merged commit dcea520 into main Aug 21, 2026
58 of 71 checks passed
@ruvnet
ruvnet deleted the dream/2026-08-19-swarm branch August 21, 2026 15:19
ruvnet added a commit that referenced this pull request Aug 21, 2026
Cuts a release train off main after the 3 dream cycles landed unversioned:
- #3062 dream(swarm): bound MessageBus retry attempts, fix unreachable
        message.failed (closes #3061)
- #3057 dream(memory): hybridSearch controller reachable via explicit
        opt-in — was silently null-returning despite config flag
        (closes #3056)
- #3049 dream(intelligence): discounted Thompson sampling for
        model-router bandit — opt-in prior decay recovers faster after
        workload shift (closes #3048)

No code changes in this commit — just the version bump across all three
publishable package.jsons.


Claude-Session: https://claude.ai/code/session_0118jMsYhwHD5dx2vStENsEB
ruvnet added a commit that referenced this pull request Sep 26, 2026
Ships the swarm fixes merged since alpha.8 (#3357 hierarchical leader
edges, #3378 event-driven UnifiedSwarmCoordinator wait, #3123, #3086,
#3243, #3062). The CLI does not depend on @claude-flow/swarm, so they
reach users only through this standalone publish.

Publishability fixes found while preparing the tarball:
- coordination/{agent-registry,task-orchestrator,swarm-hub}.ts used
  extensionless relative imports, so a plain `tsc` build of main
  throws ERR_MODULE_NOT_FOUND on `import('@claude-flow/swarm')`.
  Add the `.js` suffixes (the package is "type": "module").
- Restore the ADR-095 G2.2 transport re-exports on the root entry.
  alpha.8 was published from main@f8ab5a325 plus uncommitted work
  (later dacbe0e, never merged) that exported LocalTransport,
  FederationTransport, signMessage, etc. from the root. Without this,
  alpha.9 would silently drop 9 runtime exports alpha.8 users have.
- Add a `files` allowlist (dist, README.md, MIGRATION.md). alpha.8
  shipped src/, __tests__/, tmp.json, tsconfig.tsbuildinfo, a 1.5 MB
  ruvector.db and .claude-flow/ runtime state from a contributor's
  machine.
- Record the repo version as alpha.9 (repo was stuck at alpha.7 while
  npm had alpha.8).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Dream Cycle 2026-08-19] swarm: MessageBus retry-counter reset bug (unbounded redelivery) + ruview,ruvector scan

2 participants