Skip to content

feat(mobile): manage Builderlab remote agents - #8626

Merged
brow merged 7 commits into
mainfrom
mobile-remote-agents
Oct 27, 2026
Merged

brow merged 7 commits into
mainfrom
mobile-remote-agents

Conversation

@brow

@brow brow commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Builderlab remote agents to mobile: a member signed in to Builderlab can list, create and delete their own hosted agents from the channels header, and active remote agents show as Online.

Everything is gated by HostedEnvironment.enabled (the build-time BUZZ_AUTH_DOMAINS define). App Store and internal release builds do not set it, so they render and fetch nothing new.

Behavior

  • Agents button in the channels header (native iOS bar and Material header) for hosted communities only. It opens a sheet listing the community's agents; tapping one pushes its profile inside the same sheet.
  • Create: for the member's own agents (the NIP-OA owner is the member, not the community). Registers the agent with Beekeeper, attests it to the community, saves optional private instructions, subscribes it to the relay, then publishes the kind 30177 registration with a NIP-OA owner proof. A failed step resumes with the same agent identity on retry; instructions stay in secure storage, never in preferences.
  • Delete: removes the relay registration, confirms it is gone, then deletes the Beekeeper agent. A partial failure keeps a retry row in the list.
  • Presence: an ACTIVE agent with a live kind 30177 registration on the active relay shows Online in profiles, channel rows, DMs and mentions, since remote agents wake on messages. Local presence data is unchanged.
  • Sign-in reuses the shared hosted flow. The account's saved Buzz key must match the server binding; nothing is adopted or rewritten.
  • BuilderlabAccountClient gains an allowlist for the six Beekeeper routes desktop already uses.

Not included from the prototype: the separate entrypoint and second sign-in, test-community creation, staging-only hosts, QR-code agent links, and the USB proxy.

Validation

  • Added tests: build-time gate, account/key binding before listing, setup resume with private instructions, deletion contract and retry, owner-proof verification, status parsing, presented presence requiring a live registration in the active community and following registration publish and removal (mutation-checked), account-change clearing, load retry, recovery of a pending setup whose instructions were lost, a lost registration response that can only resume with the same idempotency key (from any community), an HTTP-rejected registration that can be discarded, deletion of other agents while a setup is unfinished, rejection of owner proofs whose signed conditions or profile signature fail, registration publish and removal over a loopback WebSocket relay for an http://-saved community, and widget tests for in-sheet detail navigation, creation overlay with failure then resume, and delete confirmation.
  • The new tests also run in the CI lane with BUZZ_AUTH_DOMAINS configured, so the enabled path is covered.
  • Full mobile format, analyze and test suite pass locally.
  • Independent agent review: no gate leak or key-handling issue found; its robustness findings (discarding a setup that never got a server identity, clearing registry state on account change, retrying a skipped load, guarded notifications) are fixed; discard and account change have added tests.
  • Simulator: a BUZZ_AUTH_DOMAINS debug build launches to Builderlab sign-in. The signed-in flow was not re-run on this branch.
  • Codex Security Review: an earlier head received a published review, and its two findings were fixed. On 745ad85 and be2e31b the review hit its 45-minute timeout and was cancelled without a result, so the final head has no published security review.
  • Human app test: not applicable under the mobile testing exception. The end-to-end create, mention and delete flow was demonstrated on the prototype branch against Builderlab; this PR keeps the same Beekeeper and relay contracts.

🤖 Generated with Claude Code

@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@brow
brow marked this pull request as ready for review October 27, 2026 15:45
@brow
brow requested a review from a team as a code owner October 27, 2026 15:45
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T15:49:59.080037Z e424c73 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e424c73676

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jedwards27 jedwards27 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.

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES

Reviewed: b14884980e9ff70c201ba97d459bed05286fdfd9..71d2c3dba2b8934dff486b68fc44820b9ed652c9 (exact head 71d2c3dba2b8934dff486b68fc44820b9ed652c9)

Risk: high — this adds hosted-agent management, Builderlab account binding, relay registration/deletion, and a global mobile presence override. The main risk is tenant/owner authorization and stale cross-boundary state between Beekeeper and the relay.

Behavior/contracts traced: HostedEnvironment gating, active community identity storage, community role resolution, Builderlab identity binding, Beekeeper register/attest/update/subscribe/delete flows, kind 30177 relay registration/removal, NIP-OA owner proofs, partial create/delete retry behavior, and presentedPresenceProvider.

Blocking findings

1. Non-owner hosted-community members can reach owner-only agent management, and the local create/delete guards accept their own identity

The Channels header renders the Agents entry whenever supportsRemoteAgents(activeCommunity) is true (mobile/lib/features/channels/channels_page.dart:456-458). supportsRemoteAgents only checks the build gate, hosted relay, and community.pubkey != null (mobile/lib/shared/agents/remote_agents.dart:27-31). But hosted admission stores the current signed-in user's key in Community.pubkey (mobile/lib/shared/hosted/hosted_admission.dart:135-141), not the community owner's role/provenance. The mutation guards then compare that same Community.pubkey to the Builderlab-bound local identity in create/delete (mobile/lib/shared/agents/remote_agents.dart:297-302, mobile/lib/shared/agents/remote_agents.dart:481-483; UI precheck at mobile/lib/features/agents/delete_agent_action.dart:30-35). The actual role source already exists separately in currentCommunityRoleProvider (mobile/lib/shared/community/community_membership_provider.dart:115-121).

A plain member in a hosted community therefore gets the owner-management surface. If they sign into Builderlab with their own matching account, the local community.pubkey == owner checks pass for that member identity. Even if a downstream service later rejects the operation, the client is presenting an owner-only control and enforcing the wrong authorization model; if the backend accepts it, this becomes unauthorized create/delete for another owner's hosted community.

Author action: gate the header/sheet management affordance and create/delete mutation paths on the actual active user's owner role/provenance, not Community.pubkey. Fail closed while the role is loading or errored unless product explicitly chooses another behavior. Update the error copy so non-owners are not told that signing into Builderlab can grant community ownership.

Verification owner: author. Add regressions where Community.pubkey equals the current member's identity and Builderlab binding matches, but currentCommunityRoleProvider == CommunityMemberRole.member; assert the Agents button/Add/Delete are hidden or disabled and controller create/delete refuse. Also cover the owner positive path.

2. Remote agents can be shown Online after their live relay registration is absent

The new availability path scopes Beekeeper ACTIVE rows through registeredCommunityAgents (mobile/lib/shared/agents/remote_agents.dart:46-68), but registeredCommunityAgents only fetches kind 0 profiles and accepts any profile whose NIP-OA owner tag verifies to the signed-in owner (mobile/lib/shared/agents/remote_agents.dart:72-84). It never requires the live owner-authored kind 30177 registration for the active relay/community.

That conflicts with the create/delete contract in the same file: create publishes kind 30177 only after Beekeeper activation/subscription (mobile/lib/shared/agents/remote_agents.dart:348-362), while delete removes that kind 30177 registration and confirms it is absent before deleting the Beekeeper runtime (mobile/lib/shared/agents/remote_agents.dart:510-525). If create fails after Beekeeper attest/profile but before publishPolicy, or delete removes 30177 but Beekeeper deletion is disabled/unavailable, Beekeeper can still list the agent as ACTIVE while the community has no live registration. Mobile then forces that pubkey to online through presentedPresenceProvider (mobile/lib/features/profile/presence_cache_provider.dart:245-252).

The regression currently reinforces the bug: “availability counts only agents registered in this community” feeds only kind 0 profile events and expects the agent to count (mobile/test/shared/agents/remote_agents_test.dart:314-351). Removing any kind 30177 requirement would still pass.

Author action: require both (1) a latest verified kind 0 NIP-OA owner profile and (2) a live owner-authored kind 30177 event with #d == agent_pubkey on the active relay before including an agent in availability/presence.

Verification owner: author. Add a regression where Beekeeper returns ACTIVE and the kind 0 profile exists, but the kind 30177 query is empty/tombstoned; expected availability is empty and presented presence does not override local presence. Add the positive control with a live 30177 policy present. Mutation target: removing the 30177 requirement must fail behaviorally.

Validation

  • Exact checkout: local HEAD was 71d2c3dba2b8934dff486b68fc44820b9ed652c9; worktree clean.
  • Static preflight: git diff --check b14884980e9ff70c201ba97d459bed05286fdfd9...HEAD passed.
  • Focused enabled-domain mobile gate: after activating Hermit and running from mobile/, this passed:
flutter test \
  --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example \
  --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz \
  test/shared/agents/remote_agents_test.dart test/features/agents
# All tests passed
  • Live PR head still matched 71d2c3dba2b8934dff486b68fc44820b9ed652c9 when checked through GitHub. Mobile-related CI was green at that head: Clients / Mobile (Checks), Clients / Mobile (Tests 1/3..3/3), Clients / Mobile (Android), Mobile Swift (Native), and Mobile Swift (Release). No failing required check was observed; unrelated Desktop E2E shards and Codex Security Review were still in progress at the last check.

Manual/native evidence

I did not run a physical-device journey or a live signed-in Builderlab create/delete. Those remain confidence gaps, not the blockers; the blockers above are source-level authorization/registration-contract defects with deterministic local evidence.

Residual risk

After the authorization and registration-presence fixes, the end-to-end live Builderlab flow should still be exercised against the exact fixed head or release artifact because these changes span mobile, Beekeeper, and relay contracts.

brow and others added 3 commits October 27, 2026 09:39
Adds an Agents sheet to the channels header for hosted communities: list,
create (register, attest, subscribe, publish the owner registration) and
delete remote agents, and show ACTIVE remote agents as Online. Everything is
gated by HostedEnvironment.enabled, so builds without BUZZ_AUTH_DOMAINS are
unchanged.

Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
Online status now counts only ACTIVE agents with owner-attested profiles on
the active relay, so agents from another community stay offline. The account
client publishes subject changes, which clear the previous account's registry
immediately. A failed registry load is retried on the next load or relay
reconnect.

Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
…ation

Availability now also requires the owner's kind 30177 registration on the
active relay, so an agent whose registration is absent (interrupted create
or delete) keeps its local presence. The Agents list still shows owned
agents without one, so an interrupted delete remains retryable. Clarifies
that agents belong to the member who owns them, not the community.

Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
@brow
brow force-pushed the mobile-remote-agents branch from 890664d to a897b6e Compare October 27, 2026 16:44
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is c477663211142cabd82329c97346b7f64f827a97...be2e31bb7022ea4586f9422e226c1b22c64f43d8.
A new review must complete for this exact range. When manual authorization
is required, a user with write access must comment exactly
@buzz-security-review be2e31bb7022ea4586f9422e226c1b22c64f43d8 to authorize a new review.
Any previous review applies only to its recorded range.

Relay publication and removal now derive the WebSocket URL through
RelayConfig, so communities saved with an https relay URL work. Finished
and discarded setups remove their pending record before deleting its
instructions, so an interrupted cleanup cannot strand the account.

Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>

@jedwards27 jedwards27 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.

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES

Reviewed: ff9eb8f4dc56c312983287321ea8748b93c62145..a897b6e7d094104ea31d43bcc94a043c197b8208 (exact head a897b6e7d094104ea31d43bcc94a043c197b8208)

Risk: high — this adds mobile controls for hosted Builderlab agent lifecycle, crosses Builderlab account binding, Beekeeper registry calls, relay registration/deletion, local secure storage, and global presence presentation. The high-risk boundary is owner authorization plus recovery when Beekeeper, relay, and local durable state disagree.

Behavior/contracts traced: hosted build/relay gating, active community identity and role resolution, Builderlab identity binding, create/delete UI affordances, Beekeeper register/attest/update/subscribe/delete flows, kind 30177 registration/removal over relay sockets, NIP-OA owner proofs, pending setup persistence, secure-storage private instructions, delete retry behavior, and presented presence.

Blocking findings

1. Non-owner hosted-community members can still reach owner-only agent management, and create/delete still authorize on the member key

The header still renders the Agents entry from supportsRemoteAgents(activeCommunity) alone (mobile/lib/features/channels/channels_page.dart:456-458). supportsRemoteAgents checks only hosted build support, a hosted relay, and community.pubkey != null (mobile/lib/shared/agents/remote_agents.dart:27-31). That is not the community owner role; hosted community admission stores the signed-in member identity there. The mutation guards then compare the same member key to the Builderlab-bound owner identity in create (mobile/lib/shared/agents/remote_agents.dart:323-327) and delete (mobile/lib/shared/agents/remote_agents.dart:506-510), and the delete UI precheck mirrors the same predicate (mobile/lib/features/agents/delete_agent_action.dart:30-35). The actual active-user role source already exists separately as currentCommunityRoleProvider (mobile/lib/shared/community/community_membership_provider.dart:115-122) but is not used for this surface or these mutations.

A normal hosted-community member with Community.pubkey set to their own key and a matching Builderlab binding will see the owner-management sheet and attempt create/delete as if they owned the community. Backend rejection would only reduce blast radius; the client-side owner-only contract is still enforced with the wrong authority source, and if the backend accepts the request this becomes unauthorized hosted-agent creation/deletion.

Author action: gate the Agents header/sheet affordance and the create/delete mutation paths on the active user's actual community owner role/provenance, not Community.pubkey. Fail closed while role is loading or errored unless product explicitly chooses another behavior. Update the non-owner failure copy so it says the action is owner-only rather than implying a different Builderlab sign-in can grant ownership.

Verification owner: author. Add member-negative and owner-positive regressions where Community.pubkey equals the signed-in member's identity and the Builderlab binding matches, but currentCommunityRoleProvider == CommunityMemberRole.member; the Agents entry/Add/Delete/create/delete must not be reachable or must refuse. Removing the role gate should fail behaviorally.

2. Hosted communities persisted as https:// can show Agents but fail create/delete registration writes

Hosted relay support accepts https as well as wss (mobile/lib/shared/hosted/hosted_environment.dart:50-67), and RelayConfig documents that persisted communities may carry HTTP(S) while wsUrl is the WebSocket address for relay I/O (mobile/lib/shared/relay/relay_provider.dart:33-64). However publishPolicy and removePolicy pass the raw stored relay string as wsUrl to submitSignedEventOnce (mobile/lib/shared/agents/remote_agents.dart:447-451, mobile/lib/shared/agents/remote_agents.dart:470-475). RelaySocket then opens exactly that URI (mobile/lib/shared/relay/relay_socket.dart:78-82). Dart WebSocket.connect rejects https:///http:// URLs as unsupported WebSocket schemes.

That means an existing hosted community persisted as https://... passes supportsRemoteAgents, so the UI offers remote-agent management, but create/delete cannot publish/remove the kind 30177 relay policy over WebSocket. This can strand pending create/delete recovery behind a transport-shape bug.

Author action: canonicalize the stored relay through RelayConfig(baseUrl: relay).wsUrl or equivalent before calling submitSignedEventOnce for publish/remove, while keeping hosted auth headers scoped to the same canonical relay config.

Verification owner: author. Add coverage for a hosted community stored as https://... proving create publishes and delete removes over wss://....

3. Pending setup cleanup can permanently wedge remote-agent management after restart

Pending setup persists both a prefs record and, when present, secure-storage instructions (mobile/lib/shared/agents/remote_agents.dart:294-309). On restore, ensureBinding treats hasInstructions with missing secure instructions as fatal before it restores pending or _scope (mobile/lib/shared/agents/remote_agents.dart:276-291). Successful create deletes secure instructions before removing the durable pending prefs record (mobile/lib/shared/agents/remote_agents.dart:391-395), and discardPending does the same unsafe ordering (mobile/lib/shared/agents/remote_agents.dart:409-415).

If the app is killed, the OS terminates it, or prefs removal fails after the secure instructions are deleted but before $scope:pending is removed, the next launch sees a pending record requiring missing instructions. refresh, create, and delete all hit ensureBinding and fail with Saved agent instructions are unavailable. Because pending was never restored, the UI also cannot offer the discard path for that broken record. One torn cleanup and the management surface wedges shut.

Author action: remove or complete the durable pending record before deleting secure instructions, or persist a recoverable completion state first. Also make a pending record with missing instructions user-recoverable/discardable instead of fatal before state restoration.

Verification owner: author. Add restart-shaped tests for pending-with-hasInstructions but missing secure storage and for prefs-remove failure ordering. Deleting instructions before clearing pending should fail mutation testing.

Prior blocker re-check

The previous Online-without-live-registration issue appears repaired in this head: liveCommunityAgents now requires both verified owner profile evidence and an owner-authored kind 30177 registration with #d == agent_pubkey before including the remote agent in availability (mobile/lib/shared/agents/remote_agents.dart:90-109), with negative/positive coverage in mobile/test/shared/agents/remote_agents_test.dart:315-382. I am not carrying that finding forward.

Validation

  • Exact checkout reviewed: local HEAD was a897b6e7d094104ea31d43bcc94a043c197b8208; live GitHub headRefOid was still a897b6e7d094104ea31d43bcc94a043c197b8208 immediately before submission.
  • Authenticated reviewer / PR author checked as jedwards27 / brow.
  • Static preflight: git diff --check ff9eb8f4dc56c312983287321ea8748b93c62145...HEAD passed at a897b6e7d094104ea31d43bcc94a043c197b8208.
  • Focused configured-domain remote-agent Flutter suite was run at this head and passed:
cd mobile && flutter test \
  --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example \
  --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz \
  test/shared/agents/remote_agents_test.dart test/features/agents
  • Mobile local gates reported at this head: just mobile-install passed; just mobile-check passed (dart format --output=none --set-exit-if-changed . and flutter analyze).
  • GitHub mobile-related checks at this head were green: Clients / Mobile (Checks), Clients / Mobile (Tests 1/3..3/3), Clients / Mobile (Android), aggregate Mobile, Mobile Swift (Native), Mobile Swift (Release), and DCO. At final status refresh, Desktop aggregate checks were failing; I did not investigate those enough to use them as separate blocking findings because the source-level mobile defects above are already author-actionable.

Manual/native evidence

No live signed-in Builderlab create/delete journey was run on a device or simulator for this head. That remains residual risk, not the basis for this verdict; the blockers above are deterministic source-level authorization, transport, and durable-state defects.

Residual risk

After these fixes, the exact fixed head or release artifact should still be exercised through a live Builderlab create/mention/delete flow because this feature spans mobile, Beekeeper, relay policy events, and local recovery state.

@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026
Preferences can outlive device-only secure storage, so a pending setup
whose private instructions are missing is restored with the instructions
field re-enabled instead of failing every registry operation. Adds a
loopback relay test that registration publish and removal reach an
http(s)-saved community over a WebSocket URL.

Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Responses to the latest review:

1. Non-owner hosted-community members can still reach owner-only agent management, and create/delete still authorize on the member key

Declining. Remote agents belong to the member who creates them, not to the community, so there is no community-owner contract to enforce here. "Owner" in this code means the agent's NIP-OA owner. The PR description said "community owner", which was wrong; it is corrected, communityOwner is renamed member, and the copy now reads "Sign in with the Builderlab account you use in this community."

  • The relay gates kind 30177 as Scope::UsersWrite, the same per-user scope as a kind 0 profile, with no community role (ingest.rs). It is owner-authored and keyed by (pubkey, kind, d_tag).
  • Each registration is published as respond_to: owner-only, so the agent answers only the member who owns it.
  • Create and delete act only on the member's own registry entries and their own 30177:<member>:<agent> address, after the saved member key matches the Builderlab binding. Nothing touches another member's agents or community state.
  • Gating on currentCommunityRoleProvider would block ordinary members from having their own agents, which matches neither desktop managed agents nor the relay contract.

2. Hosted communities persisted as https:// can show Agents but fail create/delete registration writes

Fixed in 3697d52: publish and removal use RelayConfig(baseUrl: relay).wsUrl and that config for auth headers. 745ad85 adds a loopback WebSocket relay test with an http:// stored URL that observes the kind 30177 publish and kind 5 removal. Reverting to the raw URL fails it.

3. Pending setup cleanup can permanently wedge remote-agent management after restart

Fixed. 3697d52 removes the pending record (and checks the result) before deleting its instructions, in both create and discard. 745ad85 makes a pending record with missing instructions recoverable: it restores with the instructions field re-enabled, and Resume saves the re-entered text. A restart-shaped test deletes the secure instructions, restarts, refreshes successfully, and resumes with new instructions; restoring the fatal path fails it. A failing preference removal can't be injected through the shared-preferences mock, so that ordering is covered by code review and by the fact that a torn cleanup now recovers.

@brow
brow dismissed stale reviews from jedwards27 and jedwards27 October 27, 2026 17:34

blockers resolved

@brow
brow requested a review from jedwards27 October 27, 2026 17:34

@jedwards27 jedwards27 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.

🤖 :bot: Jude’s code review agent — Request changes for head 745ad85b6f478bee631895f20d1480961becd0a1.

I found one unresolved author-actionable authorization defect.

Blocking finding — owner-only remote-agent management still gates on the member key, not the community owner role

The mobile owner-only Agents surface and create/delete paths are still authorized by Community.pubkey, but hosted admission stores the signed-in member’s key there. That is not proof that the active user owns the community.

Evidence at this head:

  • Hosted admission writes the signed-in account key into Community.pubkey (mobile/lib/shared/hosted/hosted_admission.dart:135-141).
  • The actual role/provenance source exists separately as currentCommunityRoleProvider from relay membership (mobile/lib/shared/community/community_membership_provider.dart:115-122).
  • The channel header enables the Agents entry from supportsRemoteAgents(activeCommunity) only (mobile/lib/features/channels/channels_page.dart:456-458).
  • supportsRemoteAgents checks only the hosted build gate, hosted relay support, and community.pubkey != null; it does not consult the active user’s community role (mobile/lib/shared/agents/remote_agents.dart:27-31).
  • Create/delete still authorize by comparing the Builderlab-bound local identity to community.pubkey (mobile/lib/shared/agents/remote_agents.dart:330-335, mobile/lib/shared/agents/remote_agents.dart:524-529), and the delete UI mirrors the same predicate (mobile/lib/features/agents/delete_agent_action.dart:30-35).

Failure scenario: a normal hosted-community member whose Builderlab binding matches their own saved Community.pubkey passes the local checks, sees the owner-management controls, and can attempt create/delete as though they owned the community. A backend rejection would reduce blast radius, but the mobile owner-only contract is still enforced with the wrong authority source; if Beekeeper accepts the request, this becomes unauthorized remote-agent creation/deletion for the community.

Author action: gate the header/sheet affordance, Add/Delete UI, and controller create/delete paths on currentCommunityRoleProvider == CommunityMemberRole.owner or an equivalent authoritative owner-provenance source. Fail closed while the role is loading or errored unless the product contract explicitly changes. Update copy so non-owners see an owner-only explanation rather than “sign in with another Builderlab account.”

Verification owner: author. Add member-negative and owner-positive regressions where Community.pubkey equals the current member key and the Builderlab binding matches, but currentCommunityRoleProvider == CommunityMemberRole.member; assert the Agents entry/Add/Delete are hidden or disabled and controller create/delete refuse. Removing the owner-role gate should fail behaviorally.

Rechecked prior blockers

I am not carrying forward the other previous blockers at this head:

  • Live presence now requires an owned profile plus owner-authored kind 30177 registration before showing available.
  • Policy publish/remove now canonicalize persisted http(s):// relay URLs through RelayConfig.wsUrl for WebSocket transport.
  • Missing secure-storage instructions now restore as a recoverable instructionsMissing state and cleanup keeps the management flow reachable.

Validation / residual risk

  • Exact head refreshed before review: GitHub headRefOid remained 745ad85b6f478bee631895f20d1480961becd0a1; PR author brow, authenticated reviewer jedwards27.
  • Mobile-related GitHub gates observed green at final refresh; some broader non-mobile/Codex jobs were still in progress and are not the basis for this request-changes verdict.
  • I did not perform a live signed-in Builderlab create/delete run or native device UX pass for this head. That is release-confidence work after the owner-role contract is fixed, not the source of this blocker.

@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Response to the latest review:

owner-only remote-agent management still gates on the member key, not the community owner role

Declining, per Tom's product decision: remote-agent management is per-member, not community-owner-only. Each member creates and deletes their own agents, which answer only them (respond_to: owner-only). There is no community-owner contract on mobile for this to violate. The earlier reply has the full reasoning. In short:

  • The relay admits an agent through its NIP-OA owner and cascades a ban on that owner to their agents (auth.rs). Kind 30177 is a per-user UsersWrite with no community role (ingest.rs).
  • VISION_PROJECTS.md: "Agents inherit access from their owner via NIP-OA."
  • Create and delete act only on the signed-in member's own Builderlab agents and their own 30177:<member>:<agent> address, after the saved member key matches the Builderlab binding. The failure scenario (a member creating or deleting agents "as though they owned the community") cannot reach another member's agents or community state.

@brow
brow dismissed jedwards27’s stale review October 27, 2026 18:27

blockers resolved

@brow
brow requested a review from jedwards27 October 27, 2026 18:27
@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review 745ad85

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026

@jedwards27 jedwards27 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.

🤖 :bot: Jude’s code review agent — Request changes for head 745ad85b6f478bee631895f20d1480961becd0a1.

I rechecked the same head after the author response. The earlier concern that this should be community-owner-only is withdrawn: the PR body, VISION_PROJECTS.md, NIP-OA docs, hosted admission storage, relay 30177 handling, and mobile create/delete paths support the clarified product contract that remote agents are owned and managed by the signed-in member. I also found no remaining author-actionable product/UI defect under that member-owned model.

However, I found three concrete code defects in the new mobile remote-agent flow that should be fixed before merge.

Blocking findings

1. Ambiguous register-agent success can still be discarded, orphaning the server identity

create() persists the pending request/idempotency key before calling Beekeeper, but it only saves the returned pubkey/agent_id after the register-agent response is received (mobile/lib/shared/agents/remote_agents.dart:347-382). If the server commits the registration and the response is lost or the client sees an ambiguous transport/server failure, the pending record still has no local pubkey. That exact state exposes Discard unfinished setup (mobile/lib/features/agents/create_agent_sheet.dart:162-175), and discardPending() deletes the only saved idempotency/recovery handle whenever setup['pubkey'] == null (mobile/lib/shared/agents/remote_agents.dart:423-438).

That can leave an un-attested Beekeeper agent identity that mobile can no longer recover or finish. The existing discard test covers a definitive rejected registration, not the ambiguous “request may have committed but response was lost” case (mobile/test/shared/agents/remote_agents_test.dart:325-358).

Author action: distinguish definitive no-identity rejection from ambiguous post-registration-attempt outcomes. After a registration attempt has been sent, do not allow blind discard until the client reconciles by idempotency key and either recovers/finishes the identity or durably cleans it up.

Verification owner: author — add a regression where register-agent commits/allocates an identity and then throws or loses the response, followed by discard. The test should fail before the fix and pass after.

2. A pending setup can block deletion of unrelated agents account-wide

deleteAgent() rejects every delete whenever pending != null (mobile/lib/shared/agents/remote_agents.dart:537-538). Pending setup is account-scoped, not community/agent-scoped, and a pending setup that already has a pubkey cannot be discarded (mobile/lib/shared/agents/remote_agents.dart:423-428) and can only resume in its original community (mobile/lib/shared/agents/remote_agents.dart:355-357).

If a member starts setup in community A, registration succeeds, and then community A or that relay/backend path becomes unavailable, they cannot discard that setup. The current guard also prevents deleting an unrelated existing agent in community B. That is an account-wide wedge, not a tenant-local conflict guard.

Author action: scope the delete block to the pending agent/community that could actually conflict, or allow deletion of unrelated registry rows while preserving the pending setup’s retry record.

Verification owner: author — add a regression with a pending setup for community A plus a separate listed agent in community B; deleting B should proceed and should not clear A’s pending recovery record.

3. The new owned-agent admission path uses the low-level NIP-OA tag helper instead of the profile-proof helper

ownedCommunityAgents() accepts a registry row as owner-backed when verifiedOaOwnerPubkey(event.tags, event.pubkey) == owner (mobile/lib/shared/agents/remote_agents.dart:73-85). That helper only validates the tag signature/syntax and explicitly says it is low-level; admission should use verifiedProfileOaOwnerPubkey (mobile/lib/shared/crypto/nip_oa.dart:36-50). The stricter helper verifies this is a signed kind:0 profile event and evaluates the signed kind= / created_at conditions against the actual profile (mobile/lib/shared/crypto/nip_oa.dart:8-33), matching the desktop profile admission contract (desktop/src-tauri/src/nostr_convert.rs:58-100).

As written, a profile carrying a valid owner auth tag whose signed conditions do not apply to that profile, or a forged profile envelope at this boundary, can still count as “owned” for the new remote-agent list/availability path. The shared crypto tests already demonstrate the stricter helper rejects those cases (mobile/test/shared/crypto/nip_oa_test.dart:29-66), but this new path bypasses it.

Author action: change this admission path to require verifiedProfileOaOwnerPubkey(event) == owner, and add remote-agent-path regressions for condition mismatch/expired created_at and forged profile envelope rejection.

Verification owner: author — add mutation-shaped tests around ownedCommunityAgents()/liveCommunityAgents() and rerun the configured remote-agent suite.

Validation

Reviewed exact base/head: ff9eb8f4dc56c312983287321ea8748b93c62145..745ad85b6f478bee631895f20d1480961becd0a1 (live PR head confirmed immediately before review). Authenticated reviewer / PR author: jedwards27 / brow.

Local exact-head checks from a clean worktree:

git rev-parse HEAD
# 745ad85b6f478bee631895f20d1480961becd0a1

gh pr view 8626 --repo block/buzz --json headRefOid,baseRefOid,author,reviewDecision,mergeStateStatus,isDraft,state,statusCheckRollup
# confirmed base/head above; Mobile Checks/Tests/Android, Mobile Swift Native/Release, Security Domain, Semgrep OSS, zizmor, and DCO were success at this head

cd mobile && flutter test \
  --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example \
  --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz \
  test/shared/agents/remote_agents_test.dart test/features/agents
# All tests passed! (17 tests)

cd mobile && flutter test \
  --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example \
  --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz \
  test/shared/crypto/nip_oa_test.dart
# All tests passed! (13 tests)

Residual risk: I did not rerun a live signed-in Builderlab/device create → mention/wake → delete journey. That remains a confidence/release proof item, not the reason for this request-changes verdict. The blocking items above are source-level, author-actionable defects.

A sent registration may have allocated an identity even if its response
was lost, so only an explicit server rejection can be discarded; Resume
recovers the identity with the same idempotency key. An unfinished setup
now blocks deleting only its own agent, or any agent while its identity
is unknown. Owned-agent admission uses the profile-level NIP-OA check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Responses to the latest review. All three are fixed in 1c852ed.

1. Ambiguous register-agent success can still be discarded, orphaning the server identity

Fixed. The pending setup is marked as registering and saved before register-agent is sent. Only an explicit server rejection, meaning a status other than UNATTESTED and no agent_pubkey, clears that mark. A thrown or lost response keeps it. canDiscardPending is now false, both in the UI and in discardPending(), so the only path is Resume, which reuses the same idempotency key. The regression has the fake registry commit the identity and then throw. It asserts discard is refused and that Resume sends the same key and recovers the same agent. Removing the mark or the guard fails it.

2. A pending setup can block deletion of unrelated agents account-wide

Fixed. Deletion is blocked only for the setup's own agent, or for any agent while the setup's identity is still unknown, because the agent being deleted could be the lost one. Resume resolves that state at the Beekeeper registration step without needing the original community. The regression keeps a pending setup for community A. It refuses deletion while the identity is unknown. After Resume it refuses the setup's own agent, deletes another agent from community B, and keeps A's pending record. Restoring either the old account-wide guard or the unscoped check fails it.

3. The new owned-agent admission path uses the low-level NIP-OA tag helper instead of the profile-proof helper

Fixed. ownedCommunityAgents now requires verifiedProfileOaOwnerPubkey(event) == owner. Test profiles are now real signed kind 0 events. The availability test adds three profiles that must be rejected: one with a kind=1 condition, one with an expired created_at<50 condition, and one with a forged signature. Reverting to verifiedOaOwnerPubkey fails it.

The full mobile suite, format and analyze all pass. The remote-agent suites pass with and without BUZZ_AUTH_DOMAINS.

@brow
brow dismissed jedwards27’s stale review October 27, 2026 19:12

blockers resolved

@brow
brow requested a review from jedwards27 October 27, 2026 19:12
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026

@jedwards27 jedwards27 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.

🤖 :bot: Jude’s code review agent

Verdict: REQUEST CHANGES
Reviewed: ff9eb8f4dc56c312983287321ea8748b93c62145..1c852eda3b4567eec729cb0ce1e07efefc4ece32 (exact head 1c852eda3b4567eec729cb0ce1e07efefc4ece32)
Risk: high — this adds mobile management for hosted Builderlab agent identities, relay 30177 registrations, account-wide registry recovery, and destructive runtime deletion.
Contracts traced: mobile create/resume/discard/delete state, Beekeeper idempotency and HTTP failure mapping, member-owned NIP-OA/30177 authority, private-instruction storage boundary, and remote-agent availability/presence presentation.

Blocking findings

1. Definitive HTTP register-agent rejection can leave setup non-discardable and block unrelated deletes

create() persists registering: true before sending /v3/beekeeper/register-agent (mobile/lib/shared/agents/remote_agents.dart:367-374) and only removes that marker when the call returns a JSON body with a non-UNATTESTED status and no agent_pubkey (mobile/lib/shared/agents/remote_agents.dart:378-383). But BuilderlabAccountClient maps HTTP 400/404/409/422 to AccountFailure.rejected before returning a body (mobile/lib/shared/hosted/builderlab_account.dart:348-352). That exception bypasses the marker-clear path.

The resulting pending setup has no pubkey but still has registering: true, so canDiscardPending remains false (mobile/lib/shared/agents/remote_agents.dart:431-436), the create UI hides Discard unfinished setup (mobile/lib/features/agents/create_agent_sheet.dart:162-173), and delete refuses any listed agent while the pending setup’s identity is unknown (mobile/lib/shared/agents/remote_agents.dart:555-562). A definitive validation/name-conflict rejection can therefore wedge the user in “maybe committed” recovery even though the server already refused the request.

Author action: catch AccountFailure.rejected around the register-agent call, clear registering while preserving or clearing the no-identity pending setup so the user can edit/discard it, and keep ambiguous transport/server failures non-discardable. Add a regression where register-agent throws AccountFailure.rejected after registering is persisted and verify recovery without app-data wipe and without blocking unrelated deletes.

Verification owner: author for code/tests; reviewer to re-check focused remote-agent behavior after patch.

2. Unknown registration recovery still requires the original community, so account-wide deletion can remain wedged

The recovered pending setup is rejected before idempotency replay unless the current community matches the original relay URL (mobile/lib/shared/agents/remote_agents.dart:355-357). Separately, deleteAgent() blocks any delete while a pending setup has unknown identity and registering: true (mobile/lib/shared/agents/remote_agents.dart:555-562).

That preserves the original failure shape when the response to registration was lost after the server committed: if setup started in community A and A is now unavailable or inaccessible, the user cannot replay the idempotency key from community B to learn the committed identity, and also cannot delete a separate known agent in B. The registry is account-wide; the unknown pending setup should not indefinitely freeze unrelated community management.

Author action: either allow register-agent idempotency replay/reconciliation before the original-community relay check, then require the original community only for publish/subscribe; or scope the delete guard so unrelated known registry rows remain deletable while the unknown pending setup is preserved for recovery. Add a regression covering lost registration in community A followed by recovery from community B and/or deletion of a separate B agent while A is unavailable.

Verification owner: author for code/tests; reviewer to re-check the recovery/deletion boundary.

3. Remote presence can remain stale when the live 30177 registration changes but the registry ACTIVE set does not

availableRemoteAgentsProvider captures controller.activeAgents and invalidates only when that set changes (mobile/lib/shared/agents/remote_agents.dart:46-67). Publishing/removing the owner’s 30177 policy has no separate revision or provider invalidation (mobile/lib/shared/agents/remote_agents.dart:486-519). On delete, the relay registration can be removed and confirmed absent (mobile/lib/shared/agents/remote_agents.dart:579-593), then Beekeeper deletion can fail as disabled/not-confirmed (mobile/lib/shared/agents/remote_agents.dart:594-607). In that path the account registry row stays ACTIVE, so activeAgents does not change; DeleteAgentAction invalidates only agentDirectoryProvider (mobile/lib/features/agents/delete_agent_action.dart:70-75); and profile/DM/mention presence continues to overlay the cached remote set as online (mobile/lib/features/profile/presence_cache_provider.dart:248-252). The opposite stale direction is also possible when an already-listed ACTIVE row gets a newly published 30177 registration: the cached availability can stay offline because the ACTIVE set was unchanged.

Author action: introduce a registration/policy revision or equivalent invalidation after successful publishPolicy and removePolicy, and have availableRemoteAgentsProvider re-read live 30177 on that signal even when the Beekeeper ACTIVE set is unchanged. Add provider-level regressions for: remove-policy succeeds + delete-agent fails => agent is no longer presented Online; publish-policy succeeds for an already-listed ACTIVE row => agent becomes Online without reconnect.

Verification owner: author for code/tests; reviewer to re-check truthful presence presentation after patch.

Validation

  • Repo preflight at the exact head: ../../scripts/buzz-pr-review-preflight 8626 matched base ff9eb8f4dc56c312983287321ea8748b93c62145, head 1c852eda3b4567eec729cb0ce1e07efefc4ece32, merge base ff9eb8f4dc56c312983287321ea8748b93c62145, and reported no unresolved review threads.
  • Source inspection was performed at clean HEAD=1c852eda3b4567eec729cb0ce1e07efefc4ece32.
  • Supporting exact-head local gates reported for this review: just mobile-check PASS; cd mobile && flutter test --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz test/shared/agents/remote_agents_test.dart test/features/agents PASS (17 tests).
  • CI observed at this head: Mobile Checks/Tests/Android PASS, Mobile Swift Native/Release PASS, Security Domain PASS, DCO PASS. Semgrep OSS and zizmor were still queued in the final refresh, so I am not using them as evidence.
  • Manual/native evidence: no live signed-in Builderlab/device create → wake/mention → delete journey was run. That is residual risk, not the basis for this request-changes verdict; the blockers above are source-proven.

An HTTP rejection of register-agent is as definite as a rejected status,
so it clears the registering mark and the setup can be discarded. A
registration whose response was lost can be replayed from any
community to learn its identity; finishing still needs the original
community. Publishing or removing a registration now refreshes remote
availability even when the registry's ACTIVE set is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
@brow

brow commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Responses to the latest review. All three are fixed in be2e31b.

1. Definitive HTTP register-agent rejection can leave setup non-discardable and block unrelated deletes

Fixed. AccountFailure.rejected from register-agent now clears the registering mark before it is rethrown, the same as an explicit rejection status. Transport and other failures still keep the mark. In the regression, the fake registry throws AccountFailure.rejected after the mark is saved. The test asserts canDiscardPending and then discards. Removing the catch fails it.

2. Unknown registration recovery still requires the original community, so account-wide deletion can remain wedged

Fixed. While the identity is unknown, create() allows the idempotency replay from any community. Once the identity is recovered it stops with "Agent identity recovered. Finish the setup in its original community." Attest, subscribe and publish still require the original community, and so does a setup whose identity is already known. The regression loses the registration in community A, then resumes from B with the same idempotency key and recovers the identity. It then deletes a separate agent in B while A's pending record stays. A second Resume from B makes no Builderlab calls. Restoring the original-community check before replay fails it.

3. Remote presence can remain stale when the live 30177 registration changes but the registry ACTIVE set does not

Fixed. create() and deleteAgent() increment registrationRevision after a successful publishPolicy or removePolicy. availableRemoteAgentsProvider invalidates on that revision as well as on ACTIVE-set changes. Both directions are covered in the provider-level presence test. In the first, removal succeeds and Beekeeper delete is disabled: the agent is still ACTIVE but no longer presented Online. In the second, a publish for the still-ACTIVE agent shows it Online again without a reconnect. Removing the invalidation, or either increment, fails the test.

The full mobile suite, format and analyze all pass. The remote-agent suites pass with and without BUZZ_AUTH_DOMAINS.

@brow
brow dismissed jedwards27’s stale review October 27, 2026 20:10

blockers resolved

@brow
brow requested a review from jedwards27 October 27, 2026 20:10
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Oct 27, 2026

@jedwards27 jedwards27 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.

🤖 :bot: Jude’s code review agent — Approve for head be2e31bb7022ea4586f9422e226c1b22c64f43d8.

I re-reviewed the fresh head after the prior exact-head approval was dismissed as stale. The systems/integration and product/UI review coverage agree: no unresolved author-actionable defect remains.

Integrated review findings

  • The prior definitive register-agent rejection blocker is fixed. create() now catches AccountFailure.rejected, removes registering, preserves the no-identity pending setup, and leaves it discardable (mobile/lib/shared/agents/remote_agents.dart:393-407, 466-481). Regression coverage includes JSON rejection, HTTP rejection, and discard recovery (mobile/test/shared/agents/remote_agents_test.dart:328-357).
  • The prior lost/unknown registration recovery blocker is fixed for the reviewed contract. Unknown registering setups can replay the original idempotency key from another hosted community before the original-community finish guard applies (remote_agents.dart:368-427), and the regression proves cross-community recovery plus unrelated delete while the pending setup remains (remote_agents_test.dart:359-420).
  • The prior live 30177 availability/presence invalidation blocker is fixed. availableRemoteAgentsProvider now observes registrationRevision as well as the ACTIVE registry set (remote_agents.dart:46-71), and create/delete bump it after publish/remove (224-230, 448-449, 614-615). Regression coverage proves cached Online -> registration removed/Beekeeper disabled -> Offline, and publish for still-ACTIVE row -> Online (remote_agents_test.dart:502-559).
  • Member-owned authority still matches the clarified model: hosted admission stores the admitted member key in Community.pubkey (mobile/lib/shared/hosted/hosted_admission.dart:135-141); mobile create/delete compare against that Builderlab-bound member key (remote_agents.dart:343-347, 578-582); delete UI only renders for an owned listed registry row in the active community (mobile/lib/features/agents/delete_agent_action.dart:27-35); relay-managed-agent 30177 state remains user-owned (crates/buzz-relay/src/handlers/ingest.rs:545-552, 773-779). I found no remaining path in the reviewed source/test evidence that lets one member manage another member’s remote agent.
  • Private instructions remain private in the reviewed persistence/publish paths: pending prefs strip instruction text and store it in secure storage (remote_agents.dart:313-330), and relay-visible 30177 policy publishes metadata only (remote_agents.dart:526-535).
  • The member journey has understandable exits and does not strand unrelated agent management: no-identity definitive failures expose discard, possibly committed setups stay protected from accidental orphaning, and the recovery path no longer requires reopening the original community before unrelated deletes can proceed.

Validation considered

  • Exact head checked before submitting: be2e31bb7022ea4586f9422e226c1b22c64f43d8; base ff9eb8f4dc56c312983287321ea8748b93c62145; PR author brow; authenticated reviewer jedwards27.
  • Repository review context read by the review lanes: AGENTS.md, VISION.md, VISION_MOBILE.md, VISION_REMOTE_AGENTS.md, VISION_AGENT.md, VISION_PROJECTS.md, docs/nips/NIP-OA.md, TESTING.md; no mobile/AGENTS.md exists.
  • git diff --check ff9eb8f4dc56c312983287321ea8748b93c62145..HEAD — pass.
  • just mobile-install mobile-check — pass.
  • Focused configured-domain tests — pass, 17 tests:
    cd mobile && flutter test \
      --dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example \
      --dart-define=BUZZ_AUTH_DOMAINS=buzz.xyz,builderlab.xyz,builderlabstaging.xyz \
      test/shared/agents/remote_agents_test.dart test/features/agents
  • Full just mobile-test — pass.
  • CI refresh at this head showed Mobile Checks/Tests 1-3/Android, Mobile Swift Native/Release, Security, Semgrep OSS, zizmor, DCO, and broader visible CI green. The Codex Security Review run was canceled/post skipped at refresh; I am not treating that as author rework because it did not produce an author-actionable defect. Merge readiness should still respect the repository’s required checks.

Residual confidence gaps

No live signed-in Builderlab/device create -> mention/wake -> delete journey or native accessibility pass was run as part of this review. Those remain release/manual confidence proof if required, not author-actionable code-review blockers from the evidence above.

Author action: none.
Verification owner: release/manual validation owner for any live Builderlab/device or native accessibility proof the team wants before rollout.

@brow
brow merged commit d4a0706 into main Oct 27, 2026
86 of 87 checks passed
@brow
brow deleted the mobile-remote-agents branch October 27, 2026 21:05
brow added a commit that referenced this pull request Oct 29, 2026
…b sign-in (#8650)

Follow-up to [#8626](#8626). Mobile
remote-agent creation now matches desktop.

- **Model and reasoning effort:** Create agent has Model and Reasoning
effort menus. Their options come from the remote service's catalog, and
nothing is preselected (same as desktop). The server checks the
selection against the catalog again before registering, and saves it on
the agent.
- **Builderlab gate:** creating a remote agent requires a hosted
community and Builderlab sign-in. The Agents directory, community bots
and the Bestie chooser stay available without it. Signing out closes an
open Create agent sheet; the Agents sheet stays open.
- **Enrollment:** `attest-agent` enrolls the agent in the requested
community, as on desktop, so the separate `add-buzz-subscription` call
is gone.
- **Quieter progress:** spinners replace the progress bars and the
"Working…" label. Status and success text ("is ready", "Remote agent
deleted.", setup steps) is gone, and only failures show text. Refreshing
an already-loaded agent list no longer blanks it.
- **Shared setting menu:** `BuzzSettingMenu` is a bordered setting row
that opens a Material 3 menu spanning the row's width. Opening it
dismisses the keyboard. The channel-TTL setting in Create channel uses
it too.

Agent avatars and QR/link-based creation follow in stacked PRs. Editing
existing agents comes later.

| Agents | Create | Model menu |
| --- | --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--1-agents.png"
width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--2-create.png"
width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/d0bee87c45d30b9bdd2163a75966ccd5d64f322a/pr-8650--3-model-menu.png"
width="240"> |

### Validation

- Checked on the staging iOS simulator with a Builderlab account: the
Agents list, the Create sheet, and the model and effort menus (including
with the keyboard open).
- Added tests: catalog-gated model selection (including switching models
clears an unsupported effort), the Builderlab sign-in gate, controller
creation and resume with a model, a failed listing reported instead of
an empty registry, and create sheet busy and failure states (including
the catalog reload after a rejected create).
- The hosted-only agent tests are skipped without Builderlab defines,
which `mobile-test` doesn't pass. They pass locally with the staging
defines (20/20).
- Not covered: Android.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Tom Brow <[email protected]>
Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co>

This branch was successfully deployed

No deployments
codex-review — be2e31bb Deployed Oct 27, 2026 by brow via Run Codex Security Review #10099
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.

2 participants