Files
buzz/docs/agent-availability.md
Logan Johnson d5a73b9f35 fix(desktop): derive agent availability from relay presence (#7127)
🤖

## Requested rebase published — ef40744b

Rebased onto fetched main **47d068e2109d077414cbf2f4f1c927f6d051037a**,
published **ef40744b3aeb4baaf8c81416e1a644fb5b315f91** with the exact
expected-old `df7fad6a` force-with-lease. No merge.

Manual conflicts were additive: preserve main's exact-key identity
documentation alongside the availability contract, and retain both
Bestie props and the shared availability reader in
`UnifiedAgentsSection`. Range-diff confirms unchanged lifecycle policy:
exact-key action-time authority, Unknown versus Offline, rejected
shutdown retains record/memberships, and separate local/provider/owner
gates. Main's exact-key profile routing survives. Both test-only CI
synchronization repairs (natural toast expiry and bounded stderr wait)
are byte-identical to the prior head. All ten original
authors/messages/DCO/material coauthor trailers are preserved;
configured signing policy was not changed.

Fresh checks on the rebased candidate:
- TypeScript, Biome on 26 changed TypeScript files, differential
file-size gate, and diff whitespace: pass.
- Focused production-hook/card/profile units: **58/58**.
- Fresh E2E build, availability/deletion browser: **11/11**, no retries.
- Main exact-key profile cases plus failed-DM send/startup retries:
**6/6**, no retries.

Previously reviewed full Desktop/buzz-agent package and mutation
evidence is reused for unchanged behavior; no ceremonial full suite, new
native/provider test, or `just ci` pass is claimed. Local configs,
dependency links, and historical artifacts are preserved.

Hosted observation: **MERGEABLE**, **BLOCKED / REVIEW_REQUIRED**, no
new-head formal review. [CI
33699735990](https://github.com/block/buzz/actions/runs/33699735990) is
running (including Rust and Desktop lanes), not a completed success. DCO
and required Security aggregate passed at the observation; the separate
Codex advisory review was skipped. No completed failing check or new
inline feedback observed. Historical approvals are not new-head
approvals. No reviewer/security authorization or merge action was
performed.

---

## Feature summary and retained pre-rebase evidence

## Summary

In Buzz Desktop, an agent could look online just because it had been
started or deployed, even when there was no current sign it was
connected. Cards and profiles now show availability from the agent's
relay presence rather than a saved launch record, so you can distinguish
an online agent from one that was merely deployed.

Agents cards and profiles use presence reported through the shared
server (the relay). A successful presence read with no online agent
shows Offline; failed/disconnected evidence shows unknown, rather than
retaining a misleading cached Online state.

Lifecycle actions remain separate. An offline agent may still have a
Shutdown action because the deployment record exists. Shutdown reports a
**request**, not proof the process stopped. Offline does not imply that
starting a duplicate agent is safe, and Online does not promise a
response.

### Related issue

Independent base: `main`; no stack parent or child among the
replacements. Extracted from
[#7114](https://github.com/block/buzz/pull/7114), retained as historical
source (`98fe33ec`).

[Behavior
contract](f4bb2ed44e/docs/agent-availability.md).
Originating [Buzz
discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848)
· channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`.

### Testing

The same saved provider-backed agent, with only authored presence
changing. These are mock-browser states, not a before/after deployment
or live relay transport test; production UI is unchanged by the later
fixture repairs.

**No online presence:** gray dot, existing Shutdown control retained.

![Offline presence does not remove the deployment lifecycle
control](https://raw.githubusercontent.com/block/buzz/ed68b1b4597dde47118b7591b1e8ebf3702447ca/pr-7127--offline-deployment.png)

**Online presence:** green dot, same lifecycle control.

![Authored Online presence changes availability without changing the
deployment](https://raw.githubusercontent.com/block/buzz/ed68b1b4597dde47118b7591b1e8ebf3702447ca/pr-7127--online-deployment.png)

[Capture
details](https://github.com/block/buzz/pull/7127#issuecomment-5482264824).
To check manually, compare runtime-only transitions with presence
updates, then disconnect/fail the presence read and verify it does not
stay Online. A Shutdown request should not immediately claim confirmed
termination.

#### Historical pre-rebase evidence and limitations (df7fad6a)

Lifecycle production source remains
**`f4bb2ed44e5a989d93c5f51e93c0bbd2dca941be`**. Current published head
is **`df7fad6ae65dda78508317186a95522d1bb22ed9`**: the prior browser
synchronization at `b78d093e` plus an additive two-file Rust
test-harness synchronization described below. No production bytes,
dependency/configuration files, or prior commits were changed; no
rebase. Current live main `0dbd036f5bff33e7ade75e7639f3218d424a6e73` has
identical failing-test/toaster/send-flow source; the causal browser
comparison used latest successfully tested main
`04babf02655440b4dfd37f2e2df605ead0a030d8`.

**Lifecycle/deletion correction:** both Agents and actual profile
deletion now pass the shared exact-key availability reader, not raw
cached data. It reads the canonical query state and connection at action
time, including after awaited channel discovery.
Failed/disconnected/pending evidence and unqueried persona siblings are
unknown; successful missing means Offline only for a requested key.
Successful background refetch cache remains usable; settled failure
revokes it. No second cache or per-row polling was added.

Provider record + channel + Online/Away/**unknown** awaits shutdown
submission before local removal; rejection preserves record/membership
for retry. Established Offline preserves intentional no-request removal.
No route preserves warned local removal. Local agents retain native
stop-before-remove, independent of presence. Profile consent now
describes a shutdown **request**, not remote deletion or guaranteed
termination. Existing ownership and force gates are unchanged.

**Verified, reused exact-candidate validation:** the independently
approved eleven-file patch (SHA-256
`2f69fe12ef0420e62dea1fd8db28cfa22cde5eecaf8080e656310a3e60d0cf86`) was
committed without byte changes. Desktop **5,921 passed, 0
failed/skipped**, including **26 new mounted production hook/IPC
regressions**; rebuilt availability browser suite **11/11 passed, no
retries**, including four actual profile Delete journeys. Desktop check
(existing 4 warnings/5 infos), typecheck, production/protected-feature
artifact matrix, differential file-size/policy and diff checks passed.
No blanket rerun or new full-repository `just ci` is claimed for this
frontend correction.

Production regressions cover cached Online **and Offline**
failure/disconnection, genuine missing/Offline, pending, successful
inflight refetch versus settled error, retained reader, error during
awaited channel discovery, unqueried persona sibling, shutdown
rejection/order/cancel, no route and local authority. Browser fixtures
use safe mock IPC and a retained provider receipt, not a real
deployment. Three restored mutation controls fail: unknown → skip
shutdown (**15** regressions), Agents raw-cache reader (**6**), actual
profile raw-cache caller (**1 browser journey**, false removal on failed
cached Offline). Independent review approved the exact frozen bytes and
added **4/4 cached-empty failure/disconnection probes** across both
callers. This is local independent approval, not formal GitHub/A Team
clearance.

The prior native propagation/poll-count defects remain closed ([earlier
response](https://github.com/block/buzz/pull/7127#issuecomment-5512278755)).
The prior hover-popover correction at `b55423f6` remains covered by the
full 11-journey browser run: pending/failed/disconnected means no badge
or accessible status, genuine missing/Offline retains an Offline badge.
Its earlier fallback-restoration mutation failed as expected (badge
count 1 rather than 0); that historical witness is reused, not rerun.

**Reused unchanged native/system boundary:** local `just ci` at
`c59067d8` passed workspace/Tauri fmt/clippy, static/policy checks, Rust
unit recipe, native workspace **3,159 passed / 20 ignored**, Web build
and **2,019 mobile tests**. No native implementation changed in this
lifecycle correction. These are historical boundary results, not
new-head native/live certification. [Parent
CI](https://github.com/block/buzz/actions/runs/33650549130) passed with
**14 retry-recovered browser flakes**, not retry-free. Old-head
CI/reviews are not current-head clearance.

**Hosted gates:**
[CI33662151103](https://github.com/block/buzz/actions/runs/33662151103)
on `f4bb2ed4` **FAILED**: smoke shard1 had 322 pass, one failure, one
retry-recovered flaky, two skipped. The failed first-DM retry test timed
out on all three attempts because the error toast intercepted Send. That
failure is preserved, not waived; the scoped test repair below is
published as `b78d093e`.
[CI33668171165](https://github.com/block/buzz/actions/runs/33668171165)
on `b78d093e` subsequently **FAILED** the Rust unit budget regression
described below. Both original failures remain visible; neither was
retried to green. Exact `f4bb2ed4` and `b78d093e` APPROVED reviews cover
unchanged reviewed bytes, not formal approval of the new head. Fresh
exact-head CI and the established automated technical rereview are the
next gates for `df7fad6a`. Historical deletion responses remain
([5092381800](https://github.com/block/buzz/pull/7127#issuecomment-5513759316),
[5092391193](https://github.com/block/buzz/pull/7127#issuecomment-5513763497)).
No formal review dismissed. The [security
notice](https://github.com/block/buzz/pull/7127#issuecomment-5482231278)
and latest-push maintainer/codeowner policy remain separate actionable
gates: eligible Block organization members own current-range
authorization. No merge/security authority exercised.

**CI causal repair (`b78d093e`, test only):** the error `Message failed
to send: Mock first DM send failed.` is deliberately injected by the
existing fixture. CI screenshot and retry trace show the bottom-right
Sonner notification over the actual enabled Send button. `fill()` leaves
the pointer parked there; Sonner pauses its 4-second lifetime while
hovered. A fast run can click before animation settles (unchanged local
test passed in 2.7s; two actual tested-main CI cases passed first
attempt in 3.3s), which does not disprove the failure. Independent
controlled browser runs on `f4bb2ed4` and tested main `04babf` both
reproduced the same toast hit-test at Send `(1203,627,32,32)`,
persistent hover beyond 4s, and intercepted ordinary click with no
second send. Moving the real pointer to the editor allows natural expiry
and successful ordinary retry, preserving all original
DM-channel/recipient assertions. This same synchronization already
exists in the neighboring agent-startup-failure test.

The one-file correction keeps the visible error assertion, scopes its
toast locator, moves the pointer back to the editor and observes normal
toast removal (bounded 10s) before retry. No forced click, direct toast
dismissal, mocked clock, CSS override, skipped test, production behavior
change, or new backend mock. Six focused browser executions pass
(first-send/startup-failure, three repeats each, no retries); the
held-toast control fails on original bytes at the Send click while the
exact repaired test passes. Biome and diff checks pass. Reuse unchanged
5,921 Desktop / 11 availability browser / four independent probes above;
no semantic production change warrants repeating those suites. Original
failed CI attempt/retries, local fast pass, deliberate failing control
and all traces remain in `WORK_LOGS/AVAILABILITY_CI_B9210A40`. Browser
evidence is mock-IPC Chromium, not native/live-relay certification. The
UI still temporarily overlays Send while a notification is hovered; the
test exercises its real move-away/expiry recovery, not immediate
click-through.

**Rust CI causal repair (`df7fad6a`, test only):** [original Rust / Unit
Tests failure,
job100375291370](https://github.com/block/buzz/actions/runs/33668171165/job/100375291370)
tested GitHub merge `223dee91a396d8cb4ebf18b9b8559e5a54951235`.
`context_recovery_budget_exhaustion_surfaces_the_error` failed at
`regressions.rs:2756` in **0.091s** because its immediate stderr
snapshot lacked `context recovery budget spent`. ACP context-error
assertions had already passed. The captured prefix shows all three
budgets **32768 → 16384 → 8192 bytes**, above the 4096-byte floor, and
ends during the third attempt. This is **not evidence of floor
exhaustion**. The collector is an independent Tokio task; a stdout
response is not a stderr barrier. Recovery, harness and test blobs were
identical across the compared base/head/merge parents; no production
regression was implicated.

The shared test Harness now provides a bounded event/condition wait,
registering for collector notifications before reading the buffer to
avoid lost wakeups. The budget and adjacent terminal floor assertions
wait for their own diagnostic and retain the matching snapshot. The
budget test still requires the provider's ACP context error and exactly
three recovery rungs, now corroborated by **exactly four provider
calls** and no floor diagnostic. Timeout remains a real failure with
captured stderr. No fixed sleep, weaker assertion, skip,
provider-limit/logging change, dependency/config edit, or production
change.

**Deterministic causal control:** the same real agent/HTTP-provider/ACP
scenario holds only stderr collection behind a one-shot gate until after
stdout responds. The old immediate snapshot fails the original budget
assertion (intentional exit101); the repaired wait explicitly remains
Pending while held, then passes after release. No scheduler-speed
assumption or fixed sleep. This reproduces the observation race under
controlled delay, **not the exact historical CI schedule**. A
missing-diagnostic test proves the wait actually times out. Original
failure and deliberate failing-control logs/patch remain in
`WORK_LOGS/RUST_TRIAGE_06E32D2D` and `WORK_LOGS/RUST_SYNC_BC5758B9`.

**Final candidate validation:** focused recovery **9/9**, floor **1/1**,
absent-diagnostic timeout **1/1** pass. One full touched-package run,
`cargo test --locked -p buzz-agent`: **695 passed, 0 failed, 1 existing
ignored**, including all **54 regressions**. Local nextest was
unavailable, so this uses the repository-supported cargo-test fallback,
not a claim of nextest reproduction. `cargo fmt --check`, package-scoped
Clippy all-targets with `-D warnings`, differential file-size/policy and
diff checks pass. Previously reviewed availability production and the
Desktop/browser evidence above are unchanged and reused; no all-native
blanket rerun. The test-only delta was self-reviewed against collector
ordering, timeout and falsification evidence. Existing production
approval remains valid for those bytes; exact-new-head technical/CI
clearance is not assumed. The required **Security aggregate** is
distinct from optional Codex advisory feedback; no security
authorization, human review contact, or merge was requested.

**Remaining policy limits:** shutdown submission is not harness
acceptance or process termination; confirmed Offline/no-route local
removal may leave a remote process; route discovery is best effort,
membership cleanup uses `Promise.allSettled`, and multi-instance
deletion is sequential/non-atomic. No distributed singleton,
provider-health, tenant-switch cancellation, live relay TTL or packaged
WebView/VoiceOver certification is claimed. The pre-existing DM-header
raw-presence fallback (`ChannelScreenHeader`/`useActiveChannelHeader`)
remains outside this repair and uncertified. Screenshots above remain
historical mock-browser illustrations, not new deletion or native
transport evidence.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-02 21:53:15 -04:00

8.0 KiB
Raw Permalink Blame History

Agent availability and lifecycle

Availability means conversational presence on the relay, not process health. A retained deployment receipt, PID, or running record cannot turn an agent's availability dot green. A successful presence snapshot with no entry means Offline only for an identity included in that snapshot’s requested keys. An unqueried identity is unknown, never implicitly Offline. An initial pending read, failed read, or disconnected relay means unknown, including when the cache previously contained Online. Online and Away require presence.

Agents cards and profiles share the existing presence query and live subscription; the Agents action owner queries its full managed set once and passes its reader to persona/custom/unknown cards. Members owns one full-roster query shared by rows, menus and bulk actions. The profile's actions use its existing snapshot, and its popover also preserves failed/disconnected reads as unknown. Native query errors reject the IPC call rather than fabricating an empty snapshot. A live update or self heartbeat cannot heal a failed aggregate snapshot by marking cached siblings successful; those reads remain unknown until a successful snapshot retry. No per-row polling is needed, and there is no second availability cache or substrate poller. Lifecycle controls remain separate: a deployed provider agent still offers Shutdown while offline. Shutdown sends a request, not a confirmed termination, and absence of presence is not permission to deploy a duplicate body. Local Stop/Start routing is unchanged.

A locally stopped record with current exact-key Online or Away presence does not establish local process ownership. Its card replaces Start (including a stale restart/error badge) with the presence dot; its profile and member menu retain the lifecycle-derived Start label but disable activation with an explanation. No Stop is invented. Local/pair-owned running controls keep their existing Stop/Restart routing, even when presence is Offline. The same positive presence guard covers list/profile callbacks and member bulk respawn so the visible affordance is not the only fence. This is a UI startup guard, not a distributed singleton lock: missing, expired, failed or disconnected presence cannot certify that starting another body is safe. Existing relay query/live subscription freshness remains authoritative; no separate heartbeat or cache is introduced. Runtime details remain accessible through the card/profile.

Lifecycle decision authority

Presence does not grant management authority. Backend type, deployment receipt, local/pair ownership and the existing owner gates still decide which operations are offered and where they route. All presence-dependent decisions use the same surface-owned reader, not raw query data. The reader checks the canonical query cache and current relay connection at invocation, including after asynchronous channel discovery and between persona siblings. Display observers refresh on query data/status and connection changes (without the warning-banner debounce). A successful cached snapshot remains usable during an in-flight background refetch under the existing query policy; a settled error revokes it even though TanStack retains the old data. A live single-author update cannot heal a failed aggregate. This is not a new freshness cache or a distributed lock.

For deletion of a provider agent with a deployment receipt:

Availability / route Before local record deletion
Online or Away, channel available Await a shutdown request; warn it does not confirm termination.
Unknown, channel available Also await shutdown; explicitly identify unknown availability in the action confirmation, never claim Offline.
Established Offline Preserve intentional offline removal without a shutdown request; warn the remote deployment may still exist.
No channel No shutdown route; warn the remote deployment may still run, without claiming it will.

A shutdown request failure propagates and leaves the management record and channel memberships intact for retry; it is not silently converted into force removal. A successful request still requires the existing orphan confirmation (or the profile’s already-accepted equivalent) before forced local record deletion. The profile confirmation describes this policy without asserting the current availability or promising remote deletion. Cancelling after a request retains the record; it cannot unsend the request. Channel removal follows record deletion. Local deletion does not consult presence and still delegates to the native stop-before-remove boundary. Custom-persona native cascade continues to refuse provider-deployed targets; built-in persona instance removal uses these same per-instance rules, treating unqueried siblings as unknown.

Regression evidence

  • desktop/src-tauri/src/commands/profile_presence_tests.rs: actual command against loopback HTTP; empty success versus auth/rate-limit/storage/malformed response and transport failure, with authenticated kind-20001 requests.

  • UnifiedAgentsSectionCardTarget.test.mjs: three card types use one request and one polling observer, reordered while the snapshot is in flight; exact-key live updates, failed warm-cache poll, retry, changed author set and unmount.

  • agent-availability.spec.ts: real Agents/Members surfaces request-count check; failure uses the native string rejection shape, not a fabricated success.

  • desktop/src/features/agents/lib/useAgentAvailability.test.mjs: successful, missing, unavailable, and disconnected presence; lifecycle routing and badges.

  • desktop/tests/e2e/agent-availability.spec.ts: deployed/offline profile and card, stopped/Online and Away presence, disabled keyboard/click Start in profile and member menu, restored Offline startup, running-but-Offline, live updates and disconnected-state behavior. Its real hover-popover journey holds the single-key IPC read pending, then distinguishes successful omission and explicit Offline from failed cached Online and disconnection, checking visible badges plus accessible names and screen-reader status text. Restoring the popover's Offline fallback must fail this regression.

  • desktop/src/features/agents/ui/UnifiedAgentsSectionCardTarget.test.mjs: mounted persona/custom/unknown cards, exact-key versus other-key presence, Online/Away/offline/missing transitions and actual Start callback activation.

  • desktop/tests/e2e/agents.spec.ts: Start badge animation and avatar continuity, runtime-only negative control, then authored Online presence.

  • desktop/tests/e2e/profile.spec.ts: created-agent initial Online snapshot and live Offline/Online transitions alongside independent Stop/Start controls.

  • desktop/tests/e2e/onboarding.spec.ts: Welcome kickoff stays pending after runtime start alone, then greets the owner and addresses Honey/Pollen only after the scenario publishes their explicit presence. Healthy-team scenarios use tests/helpers/welcomeTeam.ts, without changing the product readiness wait.

Mock create/start/stop only model runtime bookkeeping. Scenarios that require availability use __BUZZ_E2E_EMIT_MOCK_PRESENCE__ to seed the snapshot and emit kind 20001 with the agent as author. Updating a directory row or posting a chat message is not presence. Mock browser tests do not certify native relay TTL, provider termination, or substrate health.

  • managedAgentDeletionAvailability.test.mjs: mounted production Agents and profile deletion hooks through safe mock IPC, including failed/disconnected cached Online and Offline, pending, genuine missing/Offline, Online/Away, channel-discovery suspension, unqueried persona siblings, successful in-flight refetch, shutdown failure/ordering/cancel, no-channel and local delegation.
  • agent-availability.spec.ts: real profile Delete dialog/action with failed or disconnected cached Online and failed cached Offline. A rejected shutdown retains the record; retry orders shutdown → deletion → membership removal. Genuine Offline preserves no-request deletion. All destructive IPC is mocked.