Skip to content

fix(acp): report an agent left with no live channel subscription - #6365

Draft
mfethe1 wants to merge 1 commit into
block:mainfrom
mfethe1:fix/acp-startup-subscription-reach
Draft

fix(acp): report an agent left with no live channel subscription#6365
mfethe1 wants to merge 1 commit into
block:mainfrom
mfethe1:fix/acp-startup-subscription-reach

Conversation

@mfethe1

@mfethe1 mfethe1 commented Aug 20, 2026

Copy link
Copy Markdown

Problem

The startup deafness check ran before the subscribe loop and keyed on the
resolved channel filters:

let channel_filters = config::resolve_channel_filters(&config, &channel_ids, &rules);
if channel_filters.is_empty() {
    tracing::warn!("no channel subscriptions resolved — agent will sit idle");
}
let mut subscribed_channel_ids = HashSet::with_capacity(channel_filters.len());
for (channel_id, filter) in &channel_filters {
    if let Err(e) = relay.subscribe_channel(*channel_id, filter.clone()).await {
        tracing::warn!("failed to subscribe to channel {channel_id}: {e}");
    } else {
        subscribed_channel_ids.insert(*channel_id);
    }
}

So it caught only "no rule matched any channel". The strictly worse case went
unreported: resolve N channels, then fail every single subscribe_channel
call.
Each failure was logged individually, but the aggregate condition — this
agent can no longer hear anything — was never stated, and startup proceeded to
publish online presence.

That contradicts a contract this file already documents, a few hundred lines
below:

// Online means the harness can receive work, not merely that its socket is
// connected. Publishing after channel subscriptions gives desktop callers
// a durable readiness boundary before they send a startup mention.

An agent with zero live subscriptions cannot receive work, so the readiness
claim was false in precisely the case an owner most needs to know about.

Fix

Assess reach after the subscribe loop, against the subscriptions that
actually went out, and distinguish the two causes — they point at different
fixes (membership/rules vs. relay). Both messages now carry the subscribe mode
and counts instead of being a bare string.

The predicate is extracted as a pure function so the silent case is pinned by a
test rather than by reading the control flow.

Deliberately not done

  • Does not fail startup. Zero live subscriptions is a legitimate steady
    state for an agent that is in no channels yet.
  • Does not change presence. Withholding online would break the readiness
    boundary desktop callers wait on before sending a first mention — the failure
    mode would be a caller that blocks forever, which is worse than a
    well-described log line.
  • Does not add a lifecycle value. ManagedAgentRuntimeLifecycle is a strict
    enum on the desktop side, so a new variant emitted by a newer harness is
    rejected by an older desktop. That is a real forward-compatibility problem,
    but it is a separate one and does not belong in this diff.

Relationship to #5913

Complementary, and deliberately not stacked — there is no code dependency
(#5913 is relay.rs, this is lib.rs), so stacking would impose a review order
for nothing. Either can merge first.

#5913 verifies that a REQ the harness wrote was confirmed by EOSE. It cannot
see this bug, because when every subscribe fails there is no outstanding REQ to
time out — and the membership and observer-control subscriptions still confirm
normally, so the connection reports healthy. The two cover different halves of
"can this agent actually hear anything":

REQ written No REQ live
detects unconfirmed REQ #5913
detects zero reach this PR

Field evidence

From 46 local harness run logs spanning 17 days (~67,900 lines): 9
agent will sit idle reports, all from the resolved-zero path. The
all-subscribes-failed path is by construction absent from those logs — it
produced no such line, which is the bug.

Verification

Run on this branch, rebased on origin/main @ f88cda9:

  • cargo test -p buzz-acp channel_reach — 4 passed, 0 failed.
  • cargo clippy -p buzz-acp --all-targets -- -D warnings — clean.
  • cargo fmt -p buzz-acp -- --check — clean.

Full-crate suite, for honesty about the baseline: cargo test -p buzz-acp is
already red on Windows before this change — 37 failed / 763 passed on a
clean tree, versus 31 failed / 773 passed with the change, and the failing count
varies run to run (37/33/31/29). Diffing the failing-test name sets shows no
test fails with this change that does not also fail without it, and no
channel_reach test among them. All pre-existing failures are in
acp::tests::* (steer/goose/idle/keepalive).

Separately noticeable while running that suite: those tests write files named
C:UsersmfethAppDataLocalTemp... into crates/buzz-acp/, i.e. a Windows temp
path is being used as a literal filename. Untouched here; flagging it because it
pollutes the working tree on every local run.

just ci was not run — no desktop lint or build coverage from me. This is
Rust-only in one crate, but the full gate is unverified, which is why this is a
draft.

The startup deafness check ran *before* the subscribe loop and keyed on
the resolved channel filters, so it could only catch "no rule matched any
channel". The strictly worse case went unreported: resolve N channels,
then fail every single `subscribe_channel` call. Each failure was logged
individually, but the aggregate condition — this agent can no longer hear
anything — was never stated, and startup went on to publish `online`
presence.

That matters because the comment on the presence publish already states
the intended contract: "Online means the harness can receive work, not
merely that its socket is connected." An agent with zero live
subscriptions cannot receive work, so the readiness claim was false in
exactly the case an owner most needs to know about.

Assess reach after the subscribe loop instead, against the subscriptions
that actually went out, and distinguish the two causes — they point at
different fixes (membership/rules vs. relay). Both messages now carry the
subscribe mode and counts rather than being a bare string.

This is deliberately diagnostic only. Zero live subscriptions is a
legitimate steady state for an agent that is in no channels yet, so it
must not fail startup, and it must not withhold the `online` presence
that desktop callers wait on as their readiness boundary before sending a
first mention.

Observed in the field: 46 local harness run logs spanning 17 days contain
9 "agent will sit idle" reports, all from the resolved-zero path. The
all-subscribes-failed path is by construction absent from those logs,
because it produced no such line.

Signed-off-by: Michael Feth <michael@jira-flow.com>
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.

1 participant