NativePort
← How-to

How to Connect Buzz's Built-In AI Agent to an OpenAI-Compatible API

Point Buzz's buzz-agent runtime at NativePort's OpenAI-compatible endpoint for model calls and confirm it can drive tools through the connection.

Buzz is Block’s open-source (Apache 2.0) agent runtime, announced by Jack Dorsey. Buzz’s own agent, buzz-agent, speaks three separate protocols: ACP (Agent Client Protocol) over stdio to whatever is driving it, model calls over HTTPS, and tool calls over stdio MCP. This tutorial wires the HTTPS leg to NativePort’s OpenAI-compatible endpoint, so buzz-agent runs on a model you don’t have to host yourself. It covers that model connection in depth; Buzz’s own Nostr relay and agent-identity setup are covered separately below, per Buzz’s official documentation, since they’re a different piece of the system from the model connection this tutorial focuses on.

What you’ll need

  • A NativePort API key (sign up, $5 of credit is seeded automatically), exported as an environment variable, never hardcoded:
export NATIVEPORT_API_KEY="np_..."
  • Buzz built from source, following the official quick start. Docker and Hermit are the two build/runtime prerequisites Buzz’s own quick start documents. This tutorial doesn’t reproduce that guide, since the toolchain pins and local-relay bootstrap are Buzz’s own to maintain:
git clone https://github.com/block/buzz.git
cd buzz

Follow the quick start in the repo from here to build buzz-agent and buzz-admin for your platform. This tutorial targets buzz-agent v0.1.0.

Point buzz-agent’s model calls at NativePort

buzz-agent reads its model provider from environment variables. To route through NativePort’s unified OpenAI-compatible endpoint, set the exact contract Buzz’s own OpenAI-compatible provider expects:

export BUZZ_AGENT_PROVIDER=openai
export OPENAI_COMPAT_API_KEY="$NATIVEPORT_API_KEY"
export OPENAI_COMPAT_MODEL="openai/gpt-5.4-mini"
export OPENAI_COMPAT_BASE_URL="https://api.nativeport.ai/inference/v1"
export OPENAI_COMPAT_API=chat

Every one of these five variables is load-bearing:

  • BUZZ_AGENT_PROVIDER=openai tells buzz-agent to use its generic OpenAI-compatible client rather than a provider-specific one; this is what makes pointing it at a gateway instead of OpenAI itself possible at all.
  • OPENAI_COMPAT_API_KEY carries your NativePort key, not an OpenAI key. NativePort is the only thing buzz-agent ever authenticates to here.
  • OPENAI_COMPAT_MODEL="openai/gpt-5.4-mini" is NativePort’s canonical model id: the openai/ prefix selects the route, everything after it is OpenAI’s own model name.
  • OPENAI_COMPAT_BASE_URL must point at /inference/v1, NativePort’s unified inference path, not the account or docs host.
  • OPENAI_COMPAT_API=chat pins buzz-agent to the chat-completions wire format. Leave it unset and the client may default to a different API shape the gateway doesn’t serve; see Errors you’ll actually hit.

buzz-agent also reads its output and context-window caps from the environment. Set them explicitly rather than relying on whatever Buzz ships as a default:

export BUZZ_AGENT_MAX_OUTPUT_TOKENS=32768
export BUZZ_AGENT_MAX_CONTEXT_TOKENS=1047576

BUZZ_AGENT_MAX_OUTPUT_TOKENS=32768 is buzz-agent’s default output cap; setting it explicitly here just pins the value instead of trusting the default to stay put across a future build. BUZZ_AGENT_MAX_CONTEXT_TOKENS=1047576 isn’t a Buzz default; it matches NativePort’s current context-window setting for gpt-5.4-mini in the model catalog. Neither number is universal: if you point OPENAI_COMPAT_MODEL at a different model, update both to that model’s real limits, not gpt-5.4-mini’s. A cap sized for the wrong model is a common source of truncated replies, covered below.

Confirm the model can actually drive tools

buzz-agent calls tools over stdio MCP mid-conversation: a tool-call request comes back from the model, buzz-agent dispatches it to an MCP server, and the result feeds back into the same turn. None of that works if the model behind OPENAI_COMPAT_MODEL can’t emit structured tool calls in the first place; it’ll either ignore the tools it’s offered or hallucinate a text description of calling one instead of actually calling it. Check before you rely on it, rather than assume:

curl -s https://api.nativeport.ai/inference/v1/models \
  -H "Authorization: Bearer $NATIVEPORT_API_KEY" | \
  python3 -c "
import json, sys
data = json.load(sys.stdin)['data']
for m in data:
    if m.get('capabilities', {}).get('tools'):
        print(m['id'])
"

openai/gpt-5.4-mini (the model used throughout this tutorial and the only one verified here) reports capabilities.tools: true. Stick to a model that reports the same before wiring it into buzz-agent; a route can list a model without it being ready for production tool-driving traffic, so don’t reach for whatever’s newest in your own /models listing without checking this field first.

What this connection supports

buzz-agent v0.1.0 (ACP protocol v2) supports the full ACP-over-stdio, HTTPS-model, MCP-tool architecture against NativePort: initialize, session/new, a complete tool-call lifecycle against an MCP tool server (request, dispatch, result), a final assistant reply, and stopReason: "end_turn". Usage is billed at NativePort’s real, metered rate for whichever model you point OPENAI_COMPAT_MODEL at; openai/gpt-5.4-mini is cataloged at $0.75/1M input tokens and $4.50/1M output tokens.

This tutorial covers the model connection and the MCP tool-call loop. It doesn’t cover Buzz’s own relay bridge (buzz-acp) or its Nostr identity plumbing; those are documented separately below, per Buzz’s own docs, and aren’t part of the connection wired up above.

Wiring buzz-agent into the relay bridge (buzz-acp)

buzz-acp is Buzz’s bridge between its Nostr-based relay (where messages to and from an agent actually travel) and an ACP-speaking agent process. In Buzz v0.1.0, buzz-acp defaults to spawning goose as its ACP backend, not buzz-agent. To have it drive buzz-agent instead:

export BUZZ_ACP_AGENT_COMMAND="/path/to/buzz-agent"
export BUZZ_ACP_AGENT_ARGS=""

BUZZ_ACP_AGENT_ARGS is set explicitly empty here, not left unset. Leaving it unset can let a default argument list meant for goose leak through, which buzz-agent won’t understand. The model environment variables from the previous section still need to be set wherever buzz-acp spawns buzz-agent from; buzz-acp doesn’t forward or synthesize them for you.

This is the configuration Buzz’s own documentation describes for swapping the ACP backend. If you’re standing up the relay for the first time, Buzz’s own quick start (linked above) covers the Docker-based local relay it expects.

Everything from here on is governed by your own Buzz deployment, not the NativePort model connection configured above. Confirm channel routing and key pickup behave as you expect in your setup before enabling tools on this identity.

Giving the agent its own relay identity

Each agent identity on Buzz’s relay is a Nostr keypair. Per the official docs:

  1. Generate a keypair with buzz-admin.
  2. Register the resulting public key as a member of the channel(s) the agent should participate in, using a stable BUZZ_RELAY_PRIVATE_KEY for the registering identity (typically the channel owner).
  3. Give the agent process its own identity via BUZZ_PRIVATE_KEY and point it at the relay with BUZZ_RELAY_URL.
export BUZZ_PRIVATE_KEY="nsec1..."
export BUZZ_RELAY_URL="wss://your-relay-host"

Generate a separate keypair per agent. Reusing one identity across multiple agents means the relay (and anyone reading it) can’t tell them apart, and revoking access for one agent revokes it for all of them.

Security

  • Never hardcode NATIVEPORT_API_KEY, BUZZ_PRIVATE_KEY, or BUZZ_RELAY_PRIVATE_KEY. Export them from your shell environment or a secrets manager, not a config file that gets committed.
  • Scope channel membership deliberately: only add an agent’s public key to the channels it actually needs to operate in. A relay identity with broad membership has a correspondingly broad blast radius if the agent misbehaves or its key leaks.
  • Leave Buzz’s default owner-only inbound gate enabled unless you specifically intend the agent to respond to senders other than its owner. It’s the default for a reason, and disabling it widens who can trigger tool calls (including dev__shell) on the agent’s behalf.
  • Keep Nostr identity secrets (BUZZ_PRIVATE_KEY, BUZZ_RELAY_PRIVATE_KEY) and the NativePort API key in separate stores. They protect different things, relay identity versus model spend, and a leak of one shouldn’t automatically compromise the other.
  • Never put any of the above in an agent’s persona file or other public-facing metadata that gets published to the relay or a channel.

Errors you’ll actually hit

  • 401: NATIVEPORT_API_KEY (passed through as OPENAI_COMPAT_API_KEY) is missing, malformed, or revoked.
  • 402: either the NativePort balance is at $0, or, if you’ve pinned openai/gpt-5.4-mini to a specific upstream account elsewhere in your setup, the upstream provider funding behind that pin is exhausted. Both fail closed rather than degrading output.
  • Connection or 404 errors on every request: OPENAI_COMPAT_BASE_URL isn’t set to https://api.nativeport.ai/inference/v1 exactly, or has a stray trailing path segment.
  • Requests going out in the wrong shape, or failing before they reach the model: OPENAI_COMPAT_API=chat is missing. buzz-agent’s OpenAI-compatible client supports more than one wire format; this variable is what pins it to the one the gateway actually serves.
  • buzz-acp still launches goose, or buzz-agent exits immediately with an argument error: BUZZ_ACP_AGENT_COMMAND wasn’t picked up (check it’s exported in the same environment buzz-acp runs in), or BUZZ_ACP_AGENT_ARGS was left unset instead of explicitly emptied, so it inherited arguments meant for goose.
  • Replies come back empty or visibly cut off mid-thought: BUZZ_AGENT_MAX_OUTPUT_TOKENS is sized too low for the model behind OPENAI_COMPAT_MODEL, especially on turns that include a tool call, since the call itself consumes part of that budget. Raise it.
  • The agent never receives messages on a channel it should be part of: its public key wasn’t actually registered as a member of that channel; the relay doesn’t route to identities it doesn’t recognize.
  • The agent ignores messages from everyone but its owner: that’s the default owner-only inbound gate working as intended, not a bug. Only change it if that’s a behavior you deliberately want.

Where to go next