Skip to content

πŸ› [v6] contributor-first: a relay busy for another hive does not cover this hive's lane - #17402

Merged
hivecommons-hive[bot] merged 1 commit into
hivecommons:v6from
Danathar:fix/v6-contributor-first-busy-elsewhere
Nov 9, 2026
Merged

hivecommons-hive[bot] merged 1 commit into
hivecommons:v6from
Danathar:fix/v6-contributor-first-busy-elsewhere

Conversation

@Danathar

@Danathar Danathar commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Summary

A contributor relay subscribed to several hives works one task at a time. While it works hive B's task, hive A still sees a connected relay that holds its "Acting as" role, answers heartbeats and has been refused nothing, so A counts it as contributor-first cover and keeps its resident agent withheld while nobody works A's lane. With this PR the relay tells its other hives when it starts and finishes another hive's task (new busy_elsewhere_v1 capability), and the hub leaves such a relay out of coverage. Old relays and old hubs behave exactly as before.

What went wrong

Seen on 2026-11-09 with Danathar's relay, which subscribes to four hubs:

projectbluefin happened not to count the relay as cover, only because its one earlier ready had been answered no_matching_work, which started the two-minute idle run from #17284. If the relay's first action had been to take another hive's task, projectbluefin would have counted it for the whole seven minutes.

Root cause: RoleCoverage (src/pkg/dashboard/contribute_coverage.go) has no way to know a relay is busy for another hub, and the relay never says so.

What changes

  • Relay (bin/contributor-relay.js): sends {type: "busy_elsewhere", state: "busy", since} to every hub that advertises busy_elsewhere_v1, except the hub that owns the task, and state: "clear" when that task completes, fails or is revoked. All of those already end in writeRelayStatus(), which is where the quota-hold report (πŸ› [v6] contributor-first: a relay that is holding work does not cover a laneΒ #17233) runs too. It restates on auth_ok. The frame never names the other hive, repository or issue. The local PR-review cycle belongs to no hub and is not reported.
  • Hub: advertises busy_elsewhere_v1, records the flag on the connection, logs each change, and clears it on the relay's next ready, since a relay asks only when it holds no task.
  • Coverage: RoleCoverage skips a busy-elsewhere connection, in the same way it skips standby and quota hold, unless this hub has also handed it a task or a fix-pr task.

Why no timer on the hub: the flag lives on the connection, so a socket close or relay restart drops it. The relay clears it when the task ends, and a ready clears it as well. A wall-clock expiry would start counting a relay that is still busy on a long task again, which is the bug this PR fixes. A flag left set by mistake can only make the resident agent work the lane, which is what happens without contributor_first.

Design question: should a relay prefer the hive where it holds a role?

This PR does not change it. When choosing which hive to ask next, the relay could favour the hive where it is "Acting as" a role.

  • A. No automatic preference (recommended). Keep the commons strategy the contributor chose, and let busy_elsewhere keep coverage honest. With the default ranked strategy, a contributor who wants the role hive first can already list it first in HIVE_HUB.
  • B. Role hive first, as an opt-in strategy (for example role-first). A possible follow-up, but it needs a new hub-to-relay frame: today the relay learns about an owner's "Acting as" assignment only through a free-text notice, and its own HIVE_AGENT_ROLE applies to every hub.
  • C. Role hive only. This would strand the contributor's other hives.

I recommend A. Contributor-first exists so the lane falls back correctly when a relay is not working it. It was not meant to override where a volunteer chose to spend their tokens. B is not a one-line change, so I have left it out.

Files

  • bin/contributor-relay.js, bin/contributor-relay.test.js
  • src/pkg/dashboard/contribute_coverage.go, contribute_ws.go, contribute_protocol.go, contribute_coverage_test.go
  • src/pkg/governor/contributor_first.go (comment only)
  • src/docs/agent-configuration.md (coverage rules), src/docs/contributor-relay.md (new busy_elsewhere_v1 section), src/docs/design/agent-state-inventory.md (line citation moved by the new fields)
  • changelog.d/fixed-busy-elsewhere-relay-not-cover.md

Tests

I did not run these locally, because the repository's agent rules leave that to CI. Only gofmt -l and node --check were run.

  • TestRoleCoverage_BusyElsewhereRelayDoesNotCover: its first assertion records today's behaviour: a role-holding relay that has sent no ready and no report is counted as cover. Then a busy report drops it and clear restores it. On the base commit there is no message or flag that can remove the relay from coverage, so the second assertion cannot pass there.
  • TestRoleCoverage_BusyElsewhereWithThisHubsTaskStillCovers: a relay that this hub has also handed a primary task or a fix-pr task still counts.
  • TestRoleCoverage_ReadyClearsBusyElsewhere: a real ready through handleReady clears the flag, so the relay counts again.
  • TestServerCapabilities_AdvertisesBusyElsewhere.
  • Relay: busy_elsewhere_v1 tests check that a task from hive B is reported to A only, with no task details, restated on A's reconnect and cleared on completion; that a revoke clears it; and that a hub without the token sees no frame.

Refs #17284, #17233

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1

…r this hive's lane

A relay subscribed to several hives works one task at a time. While that
task belonged to hive B, hive A still saw a connected relay holding its
"Acting as" role with a fresh heartbeat and no refusal, so RoleCoverage
counted it as contributor-first cover and A's resident agent stayed
withheld while nobody worked A's lane.

The relay now tells every other hub that advertises the new
busy_elsewhere_v1 capability when it starts and finishes another hive's
task (state busy/clear plus the task's start time; no hive, repo or issue).
It restates on auth_ok and clears on complete, fail and revoke, since all
of those end in writeRelayStatus. The hub records the flag per connection,
clears it on the relay's next ready, and RoleCoverage skips a busy-elsewhere
connection that holds no task or fix-pr task from this hub. No wall-clock
expiry: the flag dies with the connection, and a stale flag can only fall
back to the resident agent working the lane. Old relays and old hubs keep
today's behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Nov 9, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Nov 9, 2026

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the diff against v6 plus the relay and hub paths it hooks into. This looks correct to me; nothing blocking.

What I verified:

  • Hub flag lifecycle. handleBusyElsewhere (contribute_ws.go:3313) only sets busyElsewhere; busyElsewhereLocked (contribute_coverage.go:215) requires currentTask == nil && len(c.fixTasks) == 0, so a relay this hub has also handed work still counts. handleReady clears the flag under the same c.mu hold as currentTask/pendingExternalTask (contribute_ws.go:3744), so a lost clear frame cannot park a relay that is back asking this hub. Mirrors the quota_hold shape exactly.
  • Relay side. _hub is set on every task_assign (contributor-relay.js:10055 base) before writeRelayStatus(), and the complete/fail/revoke paths that null currentTask (base 5310, 8345, 8594, 10190) all end in writeRelayStatus(), so reportBusyElsewhereToHubs() sees each transition. The one currentTask = null that does not (shutdown, base 10452) drops the socket, and the flag dies with the connection β€” consistent with the "no timer" argument in busyElsewhereLocked's comment.
  • No false ready while busy. requestWork refuses to send ready when currentTask is set (contributor-relay.js:2444), so the ready-clears-flag rule on the hub cannot be tripped by the busy relay itself.
  • Frame carries only state/since; since is bounded to 40 chars before logging. No hive/repo/issue leaks between hubs.

On the design question in the body: option A (no automatic role-hive preference) is what the diff implements and it is the hive's own call, not a risk class β€” I'd leave it as-is.

β€” hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

@hivecommons-hive

hivecommons-hive Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Hive status β€” updated in place; edits do not notify. Full history is in the Activity Monitor.

Review findings from reviewer-2 (folded here because this head already carries its hive review):

Read the diff against the tree at be24159 (merge-base) and the relay callers; the change looks correct to me and does what the body says.

What I verified:

  • Relay transitions all reach reportBusyElsewhereToHubs. writeRelayStatus() (bin/contributor-relay.js:1876) is called after currentTask is set on task_assign after _hub is attached (:10097 β†’ :10099), and after currentTask = null on complete (:8634), fail (:8385) and revoke (:10234). The shutdown path (:10496) sends nothing, but the socket drop kills the per-connection flag on the hub, which is the documented behaviour.
  • No frame to a hub lacking the token. reportBusyElsewhere checks hub.serverCapabilities.includes('busy_elsewhere_v1'), and serverCapabilities is populated at :9848 before the restate at :9856, so the auth_ok restate sees the right list.
  • Hub-side ready clear is consistent with the relay. requestWork refuses to send ready while currentTask is set (:2484), so a ready arriving at hub A genuinely means the relay holds no task; handleReady clearing busyElsewhere (contribute_ws.go:3744) cannot race a live busy state. It also means the relay's own busyElsewhereSent is already false by then, so the two sides stay in agreement.
  • Coverage gate. busyElsewhereLocked() (contribute_coverage.go:215) keeps a relay counted when this hub has handed it a primary or fix-pr task, matching the stated rule; a relay that never sends the frame leaves busyElsewhere false, so old relays read exactly as before.
  • Docs (agent-configuration.md, contributor-relay.md), the contributor_first.go comment and the changelog fragment track the new rule.

On the design question in the body: option A (no automatic preference) is a hive engineering call and the PR leaves behaviour unchanged there; nothing in this diff depends on it.

Refs #17284, #17233 reads right β€” those issues describe neighbouring causes (idle-window, quota hold); this is a third cause with no issue of its own. I have not approved or merged anything.

Evidence bundle `#17402cfe210d` sha256:79bb7c4a26c65e3c8344af430969f72c57607bea0bf72607f2a969bc71e7e6d1 β€” download from the dashboard or `hivectl review evidence #17402`.

@hivecommons-hive
hivecommons-hive Bot merged commit 695252f into hivecommons:v6 Nov 9, 2026
72 of 77 checks passed
@hivecommons-hive

Copy link
Copy Markdown
Contributor

merged by Hive trusted-author auto-merge: author @Danathar holds role owner and GitHub permission maintain on repo; CI green at cfe210d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant