Skip to content

fix(desktop): enforce shared agent access across devices - #6086

Merged
wesbillman merged 13 commits into
mainfrom
carl/shared-agent-discovery
Aug 17, 2026
Merged

fix(desktop): enforce shared agent access across devices#6086
wesbillman merged 13 commits into
mainfrom
carl/shared-agent-discovery

Conversation

@wesbillman

@wesbillman wesbillman commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • discover shared managed agents from authenticated relay directory records instead of treating channel membership as sufficient proof
  • publish and refresh access-policy changes immediately so running clients converge across machines without a restart or five-minute poll
  • route profile edits through the exact managed instance and stop/restart runtimes around access changes so unrelated edits cannot silently widen access
  • keep mention send-time revalidation and Block owner-only build enforcement fail closed
  • explain invalid custom provider/model configuration instead of leaving Save silently disabled

Related issue

Fixes #3204

Known residuals

Testing

  • full Desktop unit suite: 4,961 tests passed
  • focused profile editor Playwright workflow passed, including Customize access edits and prompt-only edits after tightening an instance
  • Desktop TypeScript, Biome formatting, file-size ratchet, Tauri checks, and pre-push suites passed
  • independently reviewed for authenticated directory trust, live subscription teardown, runtime revocation ordering, fail-open edit paths, and per-agent provider deployment serialization

wesbillman and others added 8 commits August 16, 2026 10:22
Discover cross-machine agents from relay-signed bot memberships, then resolve exact runtime, owner, and managed-policy coordinates without unbounded scans or filter fanout. Preserve headless directory profiles and fail closed on malformed authenticated policy.

Co-authored-by: diegorumo <diegorumo@gmail.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Cover cross-identity discovery through a real relay and assert the final signed message carries the exact agent p tag. Flush the retained managed-agent policy during create so another identity does not wait for the background sweep, and expose the compiled owner-only capability for release verification.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Fail closed for deployed provider agents whose protocol cannot revoke an
access edit, refresh local or provider runtimes when another machine applies
an inbound policy, and preserve tightened access if a simultaneous rename
profile sync rolls back.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Clarify that provider revocation relies on deploy idempotency, persist inbound
payload-build failures on the agent record, and make rollback restart errors
name the access policy that was kept.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Use the exact profile-selected agent when a persona-linked Edit dialog changes
access, seed the dialog from that instance, and send the change through the
fail-closed managed-agent update path. Stop active local runtimes inside the
same store/process critical section before persisting the narrower policy.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Subscribe to managed-agent replacement events as freshness signals and
invalidate the authenticated relay directory so other clients converge when
access changes. Keep custom AI validation strict, but explain the missing
provider or model and cover the exact Customize edit path.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Route every profile-panel persona editor through the exact managed instance's access policy, so an unrelated definition edit cannot restore stale broader persona access.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman requested a review from a team as a code owner August 16, 2026 23:11
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Carl, an automated reviewer, commenting via Wes’s GitHub account.

I found three blocking failure modes in the cross-device access enforcement on exact head bd4d04c60660ef6d81a64626a1ca79d5dd378a4a.

  1. Inbound local revocations can be consumed without being applied. reconcile_inbound_persona_event_blocking commits the incoming kind:30177 head to retention before it stops local runtimes or saves the narrowed agent record (desktop/src-tauri/src/commands/personas/inbound.rs:217-230,249-305). stop_managed_agent_process can stop only some pairs and then return an error (desktop/src-tauri/src/managed_agents/runtime/stop.rs:153-185), which exits before save_managed_agents. The old policy remains saved and a failed-to-stop pair may remain live, but replaying the same head now returns InboundOutcome::Skipped because retention already advanced. This permanently suppresses recovery of a security-sensitive revocation. Commit the retained head only after teardown and save succeed, or otherwise preserve a retryable/transactional failure state. Add a regression test covering teardown failure followed by replay.

  2. Concurrent provider refreshes can finish out of order and restore broader access. startPersonaSync dispatches each inbound event without awaiting or sequencing reconciliation (desktop/src/features/agents/lib/usePersonaSync.ts:38-54). Each reconciliation captures an agent_json payload under the store lock, releases it, and then independently awaits provider deployment (desktop/src-tauri/src/commands/personas/inbound.rs:98-133,249-305). deploy_to_provider accepts that stale captured payload and records success without a generation/current-policy check (desktop/src-tauri/src/commands/agents.rs:454-509). If a broad head and then a restrictive head overlap, the older provider call can complete last and become effective. Serialize refreshes per agent or add a generation/current-policy guard that guarantees the newest policy is the final deployment. Cover the overlapping completion order.

  3. The global refresh signal lets any member amplify work across every connected Desktop. startRelayAgentPolicyRefresh subscribes to every community kind:30177 event without author or coordinate scope (desktop/src/features/agents/lib/useAgentsDataRefresh.ts:26-43). The 200 ms leading-edge throttle permits a sustained publisher to trigger roughly five directory invalidations per second (:86-92). Every refetch paginates the viewer’s membership records and performs batched directory/profile/policy queries (desktop/src-tauri/src/commands/agent_discovery/relay_directory.rs:91-140). Kind:30177 is an ordinary UsersWrite event and is not shared-gated (crates/buzz-relay/src/handlers/ingest.rs:345-353; crates/buzz-core/src/kind.rs:1075-1084), so an irrelevant authenticated publisher can impose repeated multi-request scans on all clients. Scope freshness signals to verified owners/coordinates, or retain polling; also enforce a hard minimum interval/single-flight guard and test that irrelevant event floods do not repeatedly refetch.

The authenticated owner-coordinate verification and malformed-policy fail-closed behavior looked sound in the paths reviewed. I did not rerun CI-equivalent suites; these are ordering and availability defects not established by a green broad suite.

Commit inbound policy heads only after local revocation persists, serialize
provider refreshes in relay order, and scope live freshness signals to exact
authenticated owner-agent coordinates with a bounded single-flight cooldown.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman force-pushed the carl/shared-agent-discovery branch from 47f2e84 to cab33f4 Compare August 17, 2026 00:00
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Carl, an automated reviewer, commenting via Wes’s GitHub account.

The previous local replay, provider ordering, and refresh-amplification findings are materially addressed on exact head cab33f4d1e3a88f2eae438298dd3c17726aaddeb, but the updated head still has two blocking access-enforcement failures.

  1. The exact profile-selected agent can be replaced by a sibling before Edit, so access tightening targets the wrong instance. UserProfilePanel now calls useCanonicalManagedAgentProfile without preferring the directly requested managed agent (desktop/src/features/profile/ui/UserProfilePanel.tsx:182-199). On first render, requestedInstancePubkey is still null, so preserveRequestedInstance is false. The hook therefore selects pickProfileAgent(personaInstances) rather than directManagedAgent (desktop/src/features/profile/lib/useCanonicalManagedAgentProfile.ts:51-56). When several agents share a persona, opening a specific sidebar agent can canonicalize to its preferred sibling before the editor opens. Tightening access then updates that sibling while leaving the requested broader-access instance unchanged. This is not theoretical: exact-head Desktop Smoke E2E (1) failed all three attempts in agent-access-warning.spec.ts:131-220, with no update_managed_agent command for the requested pubkey. Restore direct-instance preference for this profile caller, or otherwise make an explicitly requested pubkey authoritative from the first render, and keep the exact-instance E2E green.

  2. A failed provider redeploy consumes the revocation without enforcing it or preserving a retry path. The inbound path saves the narrowed local record and advances kind:30177 retention (desktop/src-tauri/src/commands/personas/inbound.rs:266-319) before the provider refresh runs asynchronously (:98-133). If payload construction or provider deployment fails, only an error is persisted/returned; the retained head remains applied. Replay then exits as Skipped at :234-237, while the provider can continue running the older broader policy indefinitely. Delaying retention alone does not repair this: because the narrowed local record was already saved, replay makes apply_inbound_managed_agent report access_changed = false (:547-579) and schedules no provider refresh. The workspace reconciliation path is not general recovery because it runs only in owner_only_access_build() (desktop/src-tauri/src/commands/agents/provider_access.rs:53-64). Preserve a durable pending provider-policy generation (or equivalent retryable enforcement state) until provider acknowledgement, and cover failure followed by replay/restart and successful redeploy.

The remediation does correctly serialize provider calls per agent and rebuild payloads from current saved state, so the earlier out-of-order stale-payload race is closed. Local stop/save failures also remain replayable because retention advances after successful local persistence. Authenticated refresh coordinates plus exact-pair checks and the five-second single-flight cooldown close the previous global refresh-amplification path. The remaining scheduler and injected local-stop replay coverage gaps are worthwhile but non-blocking.

wesbillman and others added 2 commits August 16, 2026 18:19
Keep sidebar-opened persona instances bound to their exact managed-agent record so access edits cannot target a sibling instance.
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
preferDirectManagedAgent pinned every direct-opened profile to the exact
clicked record, which fixed access edits targeting a sibling instance but
broke the parity contract: an avatar on an old message from a stopped
historical instance must open the persona's active instance, matching the
Agents library.

Resolve the conflict with one rule in pickDirectProfileAgent: keep the
direct instance whenever it is active (access edits stay exact), and
redirect an inactive direct instance to the persona's active instance
when one exists. Covered by node unit tests for all three branches; both
previously seesawing e2e specs (agent-access-warning:131 and
profile:1837) pass together.

Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A Team review — changes requested

Reviewed exact head 3183199695ccda3f397e530a9c1c9fea72ff9a77.

The current head fixes the direct-instance selection regression, but one security-sensitive provider enforcement failure remains.

P1 — a failed provider redeploy consumes the revocation without a general retry path

The inbound path saves the narrowed record and advances kind:30177 retention before the provider refresh runs (desktop/src-tauri/src/commands/personas/inbound.rs:266-319). Payload construction or deployment can then fail at inbound.rs:106-133; replay of the same event returns Skipped at :234-237, and the provider may continue running the older broader policy indefinitely.

The only startup/workspace reconciliation found is restricted to owner_only_access_build() (desktop/src-tauri/src/commands/agents/provider_access.rs:53-64), so ordinary shared-access builds do not have a durable recovery mechanism. Persist a pending provider-policy generation (or equivalent retryable enforcement state) until provider acknowledgement, and causally test failure followed by replay/restart and successful redeploy. A warning record is useful, but it is not enforcement.

Current CI is green, including Desktop/Rust/Windows, and git diff --check passes at the pinned clean head. Those gates do not exercise provider failure recovery. The documented inability to undeploy an older provider instance further increases the consequence.

wesbillman and others added 2 commits August 17, 2026 08:32
Persist access-policy enforcement before provider deployment, retry unacknowledged policies across restarts in every build, and clear the marker only when the deployed payload matches the current saved policy.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Trim stale comments in already-oversized managed-agent modules so the durable provider-policy field does not grow them past the enforced baseline.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman merged commit f716eef into main Aug 17, 2026
61 of 68 checks passed
@wesbillman
wesbillman deleted the carl/shared-agent-discovery branch August 17, 2026 16:51
tellaho added a commit that referenced this pull request Aug 17, 2026
…-correctness

* origin/main:
  fix(acp): replace Goose native system prompt (#5964)
  feat(workflows): add responsive library card actions (#6008)
  fix(desktop): enforce shared agent access across devices (#6086)
  feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
  docs: refresh agent development guidance (#6049)

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Aug 17, 2026
…p-repair

* origin/main:
  Rename Bumble agent to Pollen (#5864)
  fix(desktop): resolve agent profiles through one archive-aware selector (#5706)
  fix(acp): gate relay-signed workflow messages on their attributed author (#6129)
  fix(acp): replace Goose native system prompt (#5964)
  feat(workflows): add responsive library card actions (#6008)
  fix(desktop): enforce shared agent access across devices (#6086)
  feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
  docs: refresh agent development guidance (#6049)
  feat(mobile): require device authentication for identity export (#5116)
  fix(desktop): hide the offcanvas-collapsed sidebar so it stops painting over the community rail (#5947)
  Polish mobile message threads and composer (#5645)
  chore(release): release Buzz Desktop version 0.5.14 (#5917)
  ci(release): remove desktop smoke gate (#5914)
  chore(release): release Buzz Desktop version 0.5.13 (#5912)
  fix(ci): read Playwright version without nested shell quoting (#5910)
  fix(desktop): restore the agent trading-card mint button (#5900)
  Projects v3: unify sharing, discussions, and issue ownership (#5792)
  chore(release): release Buzz Desktop version 0.5.12 (#5903)
  fix(mobile): unwrap batched observer telemetry (#5805)
  perf(desktop): update active turns incrementally (#5897)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src-tauri/src/migration.rs
morgmart added a commit that referenced this pull request Aug 17, 2026
…graphy-staging

* origin/main:
  Remove GitHub security advisory commitment (#6144)
  Rename Bumble agent to Pollen (#5864)
  fix(desktop): resolve agent profiles through one archive-aware selector (#5706)
  fix(acp): gate relay-signed workflow messages on their attributed author (#6129)
  fix(acp): replace Goose native system prompt (#5964)
  feat(workflows): add responsive library card actions (#6008)
  fix(desktop): enforce shared agent access across devices (#6086)
  feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.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.

Mention picker lists locally-managed agents instead of channel members, so an agent running on another machine can never be @-mentioned

2 participants