Skip to content

feat(agent-command): let local agents discover advertised plugin tools - #2110

Open
kazemek21 wants to merge 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4115_agent-command-discovery
Open

kazemek21 wants to merge 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4115_agent-command-discovery

Conversation

@kazemek21

@kazemek21 kazemek21 commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Lets any local agent of the signed-in user (Claude Code, Codex, Pi, Nexus) call a plugin's app-held MCP tools without inheriting a connection from Buzz. A plugin publishes one of its existing host.serveMcp sessions with the bundled buzz-agent-command --advertise. A fresh agent then runs the installed helper by absolute path, and the helper finds that session. There is no pairing, token copy or per-agent MCP configuration.

Contract for plugins

  • Serve a dedicated server: const server = await host.serveMcp(handler).
  • Declare { "id": "agent-access", "program": "buzz-agent-command", "bundled": true, "args": ["--advertise"] } in host.processes (with host.mcp: true). Then call await host.spawn("agent-access", { mcp: server }).
  • The host already wraps this as buzz-agent-command --launch <bundled helper> --advertise. The outer helper claims the server's one session. The inner helper writes that session (loopback URL, bearer and mcp-session-id) to a random *.json in ~/.buzz-foundation/agent-connections (directory 0700, file 0600), then exits.
  • The plugin keeps that server open after the helper exits, and spawns the helper once per server. A second spawn initializes again, and the host then stops the server.
  • Access is same-user by design: every local agent of the user can reach advertised tools, including Buzz-started agents and protected workers. A plugin that must exclude its own protected workers can list the directory in their hiddenPaths.
  • Every same-user agent shares that one session, so the handler must not assume a particular caller.

Contract for agents

  • <installed helper> tools, <installed helper> <tool> --field value and <installed helper> call <tool> '<JSON object>' behave as buzz does inside an agent command.
  • The helper reads every advertisement, newest first, and asks each for tools/list. It merges the catalogs, so the newest server wins a name both offer (also across plugins), and routes each call to the server that offers the tool.
  • On macOS and Linux it reads only files the directory's owner wrote with private permissions. A directory that was once open to others may still hold their files.
  • On macOS (lsof, which without root lists only this user's processes) and Linux (/proc/net/tcp owner uid) it sends a saved session only to a loopback port one of this user's processes listens on, checked just before each request, including the tool call after discovery. Another user can take a closed server's port, so any other advertisement is skipped without being sent anything. If the check cannot run, nothing is sent and its cause is reported. A bare connection that is refused still removes the file. Windows has no such check. The lsof call adds about 0.4 s to a call on macOS.
  • --help (or no arguments) prints the usage with the path the helper was run as and exits 0, so an agent can discover the commands from the helper itself.
  • An advertisement whose server refuses the connection or answers 404 has a dead server or a reused port. The helper deletes that file. Any other failure, such as the caller's own sandbox denying loopback, leaves the file in place and is reported as Could not reach Buzz: <cause>. The agent's sandbox must allow loopback connections: Codex's read-only and workspace-write modes block them unless network access is enabled.
  • An inherited BUZZ_AGENT_CONNECTION still takes precedence. Buzz-started agents and the existing --launch path are unchanged.
  • Installed paths: /Applications/Buzz.app/Contents/Resources/bin/buzz-agent-command (macOS; the release bundle name, while some installs use another name such as Buzz Foundation.app), bin\buzz-agent-command.exe (Windows) and /usr/lib/Buzz Foundation/bin/buzz-agent-command (Linux).

The host, FOUNDATION files and the relay buzz CLI are unchanged. No account key is read or written.

Verification

crates/agent-command/tests/discovery.rs runs the real helper binary against a loopback server that copies the host's token, single-session claim and 404 behaviour. It covers:

  • the --launch … --advertise publish path, with private modes;
  • a call with no inherited connection, and an error when nothing is advertised;
  • two concurrent windows, where the newest wins and an older-only tool is still reached;
  • pruning of a stopped server, and of a live server that answers 404 for the saved session;
  • skipping a file others could have written, and a port no loopback listener of this user holds, without sending that port any bytes;
  • on macOS, a caller whose sandbox denies network access gets the cause and leaves the advertisement for others;
  • inherited precedence, and refusal to advertise without an app-held session.

Six mutations fail the tests: reversing the newest-first order, dropping the pruning, dropping the owner and mode check, pruning on any connection error, dropping the listener-ownership check, and keeping a refused unowned advertisement. The tests also pass inside Codex's workspace-write sandbox with network access, where lsof still lists this user's listeners.

Outside CI:

  • Real host lifetime (Linux Blox box). A scratch test, not committed, used the release helper against the real HostMcpServers. It served two windows' servers, each advertised with --launch <helper> --advertise. A fresh env -i call reached the newest server, including four concurrent calls. After close on the newest server, calls fell back to the older one and the stale file was removed. After stop_all (page reload or app exit), calls failed with "No running Buzz…" and no files remained. A re-served server advertised and answered again. A second spawn on a claimed server stopped that server.
  • Fresh external agent (macOS). codex exec (gpt-6-luna, low) ran with no inherited connection and was told only the helper's installed-layout path. It ran --help, listed the tools, created a Solo chat and listed chats, all exiting 0, against a loopback fixture with the host's token and session rules.

Not covered here

  • No plugin advertises yet. Publishing from buzz-nexus and its Solo tools is a separate change.
  • Windows does not add .exe to a bundled program name, so a plugin must declare buzz-agent-command.exe there. Windows directory privacy relies on the inherited ACL of the home profile; neither has been exercised.
  • App-background lifetime (window closed while the app keeps running) is not exercised.
  • The listener check is not bound to the connection: a port that changes owner between the check and the connect (a sub-second window) is not caught. Closing that window needs the host to prove its identity on the connection (for example a Unix socket with peer credentials), which this PR leaves unchanged.

Fixes NEXUS-4115

🤖 Generated with Claude Code

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4115_agent-command-discovery branch from 03b14fe to b5f0f7f Compare November 1, 2026 15:38
@kazemek21
kazemek21 marked this pull request as ready for review November 1, 2026 15:59
@kazemek21
kazemek21 requested a review from a team as a code owner November 1, 2026 15:59
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4115_agent-command-discovery branch from b5f0f7f to 23550f7 Compare November 1, 2026 16:02

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 23550f7c664792983741d53a868618453ffdef31 against base ca836a3109be30a7fd46db97bdcceaf32ebabf65 (merge base 923a80f4aeed4ab7a671c4df62f10b3a81bf15bc).

[P2] Remove the internal coordination link from the public PR description. The “Initiative context” section links an internal status application and names its internal task. This repository and PR are public. Please remove that link/task context or replace it with a public-safe summary; I have deliberately not repeated the address here. This concerns public PR material, not contributor attribution.

The inline findings cover stale advertisements trusting a replacement loopback listener (P2) and external helper help directing callers to a different executable (P3). Optional follow-ups: use a shorter discovery-specific timeout so one stalled plugin does not delay every healthy command by up to 60 seconds; add a cross-link to the new advertisement contract from the plugin architecture guide. These are not required changes.

Source-only: no helper, tests, builds or app workflows were executed by this review. The new Rust integration tests add no browser cases and use the appropriate native boundary; their fixture does not itself enforce second-initialize shutdown, so the PR’s separately reported host/agent exercises remain author-reported evidence. Windows behavior, packaged/plugin lifetime and runtime latency remain unverified here. The one CI snapshot had Rust/tool integration successful, two WebKit shards still running and Windows validation skipped. The linked issue could not be read, so intent was assessed from the PR description and repository docs. No attached images were present in the description. This COMMENT is not an approval or merge recommendation.

Comment thread crates/agent-command/src/main.rs Resolved
Comment thread crates/agent-command/src/main.rs Resolved
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4115_agent-command-discovery branch from 23550f7 to d7bd3dd Compare November 1, 2026 16:37
@kazemek21

Copy link
Copy Markdown
Contributor Author

On review 5729028348's body:

  • [P2] Internal coordination link. Removed. The description no longer has the "Initiative context" section; the verified body is at head d7bd3dd.
  • Optional: shorter discovery timeout. Not taken in this PR. A shorter cap would turn a busy but healthy plugin into a false Could not reach Buzz, and only a server that accepts the connection and then stalls hits the 60 s cap. Refused or closed ports fail at once.
  • Optional: architecture-guide cross-link. Not taken. docs/agents.md ("Tools for the user's other local agents") is the single home for the contract.

Reopen if you'd like either optional item taken here.

— Posted by Claude Code (claude-opus-5-5) on behalf of kylez

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head d7bd3dda6f8869eb6174be3fda278b1f13719007 against base ca836a3109be30a7fd46db97bdcceaf32ebabf65, comparing the fix with previously reviewed head 23550f7c664792983741d53a868618453ffdef31.

[P2] The existing listener-authentication finding remains partially unresolved: #2110 (comment). In crates/agent-command/src/main.rs:258–262, ownership is sampled once before sequential catalog requests. A server can close and its port change owners while discovery awaits another server; the cached port still passes :274–276, and :293 sends the saved headers to the replacement. The host closes connections after each request (src-tauri/src/host_mcp.rs:264–267), so the later tool call (main.rs:169–175) also needs a current authenticated connection. Bind server/peer identity verification to the actual connection before sending saved credentials or tool arguments; refreshing the initial snapshot only narrows the race. Add coverage for a listener changing ownership after discovery starts—the new wildcard-listener case covers only the state before the snapshot. This is follow-up evidence on the existing finding, not a second issue.

The prior external --help finding is repaired in source, and the public description’s internal coordination link has been removed. No additional actionable findings. Optional: preserve the underlying connection error when the ownership check cannot run, so sandbox failures remain easier to diagnose.

Source-only: no tests, builds, helper or app workflows executed. The native integration tests are the appropriate layer; this follow-up adds no browser cases. Runtime behavior, Windows safety and the reported macOS latency remain unverified here. The one CI snapshot still had Rust, JavaScript and browser work running; Windows validation was skipped. The referenced issue could not be read (403); intent was assessed from the PR and repository docs. No attached images were present. This COMMENT is not an approval or merge recommendation.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4115_agent-command-discovery branch from d7bd3dd to e8da62f Compare November 1, 2026 16:50

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account.

Reviewed head e8da62f68152949d310cbde3b5285576b50692f4 against base ca836a3109be30a7fd46db97bdcceaf32ebabf65, comparing with previously reviewed head d7bd3dda6f8869eb6174be3fda278b1f13719007.

This revision only clarifies the installed macOS app name in docs/agents.md and docs/buzz-help.md (3 added lines, 2 removed). Implementation and tests are unchanged. The documented helper location matches the configured resource layout; no new actionable findings in this documentation follow-up.

The existing P2 listener-authentication finding remains unresolved because its implementation is unchanged: previous follow-up and repair guidance. No duplicate inline finding added.

Solo follow-up under the limited-change exception; the preceding implementation review had Rocket, Mantis and Groot coverage. Source-only: no tests, builds, helper or app workflows executed. No browser cases added or removed and no focus behavior changed. Public description inspected; no attached images. The CI snapshot shows Chromium shard 8/9 and the required aggregate failing, with Windows skipped; failure cause and runtime behavior were not investigated in this documentation review. This COMMENT is not an approval or merge recommendation.

A plugin can spawn the bundled helper with `--advertise` inside its existing
app-held MCP connection; the helper saves that session to a same-user-private
file. A local agent Buzz did not start (Claude Code, Codex, Pi, Nexus) runs the
installed helper with no inherited connection and calls the newest advertised
session that still answers, dropping stale ones. Inherited connections keep
precedence.

Fixes NEXUS-4115

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4115_agent-command-discovery branch from e8da62f to 5d0c8a7 Compare November 1, 2026 17:16

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 5d0c8a7a3bce604286826800f663db1dfb3d23be against base ca836a3109be30a7fd46db97bdcceaf32ebabf65, comparing endpoint trees with previously reviewed e8da62f68152949d310cbde3b5285576b50692f4.

The fix now checks listener ownership before each advertised request, including the later tool call, and reports failures to run the check. That repairs the discovery-length stale snapshot and missing call-time check. No new actionable findings.

The existing P2 listener-authentication finding remains partially unresolved: original finding. In crates/agent-command/src/main.rs:81–96, the ownership observation still precedes a separate connection and send. If the original server closes and another OS user obtains the port between them, tool arguments can reach that replacement and its output can be trusted. The PR now accurately discloses this narrower race; disclosure does not establish that the peer is authenticated. The remaining repair is connection-bound server identity verification before sending credentials or arguments, potentially requiring a host/transport change as the author notes. This updates the existing finding rather than adding a duplicate. The final scope/risk decision belongs to the human maintainers.

Source-only: no tests, builds, helper or app workflows executed. Tests are unchanged; the wildcard-listener case checks ownership before discovery, not a transition between discovery and a call. Native integration is the appropriate regression layer; zero browser cases added or removed, and no UI focus surface changed. The one CI snapshot had Rust/tool integration successful, browser shards still running and Windows validation skipped. Platform behavior and the author-reported macOS latency remain unverified here. Public description and commit inspected; no attached images or new public-material findings. NEXUS-4115 was not independently consulted this cycle; intent was assessed from the PR and repository docs. This COMMENT is not an approval or merge recommendation.

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