Skip to content

docs: agent broker specification (#6467 interface half) - #6790

Open
baxen wants to merge 5 commits into
mainfrom
ss-core-02/agent-broker-spec
Open

docs: agent broker specification (#6467 interface half)#6790
baxen wants to merge 5 commits into
mainfrom
ss-core-02/agent-broker-spec

Conversation

@baxen

@baxen baxen commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

This is the design behind the broker stack, written up as a standalone specification so it can be approved before any of the implementation merges. It is the interface half of #6467 — how an agent that holds no secret key reads its channels, posts, reacts, keeps its profile, addresses its memory, and creates agents by asking a host to act for it.

It sits at docs/agent-broker.md, beside docs/remote-agents.md, because it is the same kind of document: a Buzz-defined protocol that is not a NIP because the relay never sees it. It is written for someone building a host in Go or a client in TypeScript who will never open the Rust crate — the vocabulary is wire names and JSON examples, and there are no code identifiers in it.

The shape: a short system model with two axioms (identity is public-key-only; the body never asserts authority), six invariants argued from the rules, the nine operations with their argument and outcome shapes and limits, the three-way succeeded / failed / indeterminate result with the full code-to-status table, the HTTP binding, and conformance checklists for the client side and the host side. It closes with what is deliberately out of scope and four open decisions.

Status is draft until a non-author approves it. #6742 (the contract crate) is on hold until then; later PRs in the stack that change the wire amend this document in the same change.

One thing to know when reading: every limit and rule in here was checked against the code at 7c853e0fb rather than against #6742's description, which got two details slightly wrong — string trimming is not uniform (content and cursors are left exactly as sent), and the storage slug bound is in bytes, not characters. Both are stated correctly here.

ss-core-02 and others added 3 commits August 25, 2026 10:05
A standalone specification of the keyless-agent broker protocol from
#6467, written for someone implementing a host or a client who will
never open the Rust crate: system model, six invariants, the nine
operations with their wire shapes, the result and error model, the HTTP
binding, and conformance checklists for both sides. Draft status until
approved; the implementation in #6742 is held until then.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz>
Six members in the request envelope, not five; requestId is never trimmed
and admits no whitespace; cursors admit no spaces; reaction and
updatedFields are described as intent plus what is actually checked; the
three response examples now use values that parse.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz>
Drop the cross-references to the originating issue and implementation
pull requests; keep the rationale each one carried inline.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
@baxen
baxen marked this pull request as ready for review August 25, 2026 21:28
@baxen
baxen requested a review from a team as a code owner August 25, 2026 21:28

@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: e28589733d

ℹ️ 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".

Comment thread docs/agent-broker.md Outdated
Comment on lines +550 to +552
- Records, per `requestId`, a digest of the received bytes and the verdict
reached; replays the recorded verdict with `replayed: true` on a matching
digest and answers `request_id_conflict` on a differing one (I3).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scope idempotency records to the authenticated principal

When two authenticated sessions choose the same caller-controlled requestId, this requires a shared lookup keyed only by that value. If their request bytes match, the second agent receives the first agent's stored verdict without its own operation running; if they differ, it gets a cross-agent request_id_conflict. This can suppress another principal's operation and expose its outcome, so the durable key must include the session-derived agent/conversation authority scope in addition to requestId.

Useful? React with 👍 / 👎.

Comment thread docs/agent-broker.md Outdated
The outcome is addressing material only:

```json
{ "authorPubkey": "…", "kind": 30078, "dTag": "…" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return the NIP-AE event kind from storage.address

This returns the NIP-78 application-data kind, but Buzz defines NIP-AE engrams as kind:30174 in both docs/nips/NIP-AE.md and KIND_AGENT_ENGRAM. A client using this addressing result will query or publish at the wrong coordinate and will not find the agent's actual encrypted memory; return 30174 here.

AGENTS.md reference: AGENTS.md:L176-L178

Useful? React with 👍 / 👎.

Comment thread docs/agent-broker.md
Comment on lines +35 to +37
This document specifies the **contract between an agent and a broker host**:
the request and response envelopes, the nine operations and their argument and
outcome shapes, the result and error model, and the HTTP binding. It

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Specify every successful operation outcome

The document claims to define all outcome shapes and later requires strict parsing, but it never defines successful outcomes for message.post, reaction.add, or profile.set; only request arguments are given before the next operation begins. Independent hosts and clients therefore cannot agree on required fields or reject unknown fields as the conformance rules demand, so each operation needs an explicit outcome schema.

Useful? React with 👍 / 👎.

Comment thread docs/agent-broker.md
Comment on lines +543 to +546
- Accepts only the request envelope in §Request with exactly its six
members; rejects an unknown member anywhere, a `null` anywhere, a repeated
key, an unknown `protocolVersion`, an unknown action, or an unsupported
`actionVersion` — each as `failed` with the matching code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Define an error form for requests without a valid requestId

For a malformed envelope whose requestId is missing, null, or repeated, this requires the host to return a failed verdict even though every result envelope requires one unambiguous requestId and clients reject responses that do not correlate to the request. The host cannot produce a conforming response for those inputs; define a pre-correlation error/transport response or permit an uncorrelated failure shape for envelope errors.

Useful? React with 👍 / 👎.

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

this reads well and the discipline of arguing six invariants from the rules — rather than listing them — is what makes it reviewable. three things i'd want settled before it leaves draft, all in the spec rather than in code.

I6 has a second spelling of absence and the document doesn't rule on it. "null is never a legal value anywhere in this protocol" is unambiguous, but arrays aren't. channel.read's outcome shows "tags": [], and message.post takes "mentions": ["<pubkey>", …]. is "mentions": [] legal, and if so does it mean the same as omitting mentions? if both are legal and equivalent, absence has exactly the two spellings I6 exists to forbid, and a host validating "which members a status admits" has to treat them as one — which is the slip the invariant predicts. the same question applies to empty strings for optional string members. one sentence ("an optional array member is omitted when empty; a present empty array is malformed", or the opposite) closes it.

requestId scoping isn't stated, and it's the one place a caller-chosen value becomes a lookup key. the request section says it's "the caller-chosen idempotency key, unique per logical operation" and that it "becomes part of a durable idempotency record". uniqueness is asserted of the caller, but nothing says the record is scoped to the authenticated session. if a host keys idempotency on requestId alone, agent B picking a string agent A already used gets A's cached verdict — a cross-agent read of someone else's outcome, arrived at without any authorization failure. that would also quietly violate I5, since the verdict B receives correlates on a requestId it really did send. §Scope and Non-Goals defers "how a host stores idempotency records" to the host, which is right for the mechanism, but the scope of the key is part of the contract, not the implementation. i'd state it as a rule: the idempotency record is keyed on (authenticated principal, requestId).

I4's completeness caveat lands hardest on the wake path, and the spec doesn't say so. the invariant is honest that A trusts H for completeness. but mentionsOnly is described as "the wake path for a keyless agent, which polls this instead of holding a subscription", and cursors are opaque, host-issued, and "never parsed, compared, or synthesized" by A. so an agent has no gap detection available to it at all: it cannot compare created_at across pages against a window it chose, cannot notice a skipped id, and cannot even tell a short page from a filtered one, because it "learns to stop from nextCursor's absence, never by comparing a page length against a limit". a host that drops every mention from one person is indistinguishable from a quiet channel, forever. that may be an accepted consequence of trusting the key holder — the host has the key, so it can also just not post — but silent selective censorship of reads is a different capability from refusing to act, and the document should say which side of the trust boundary it puts that on rather than leaving it to the reader.

smaller: §Operations says length limits "on names, prompts, and similar fields count characters", while the pr description says the storage slug bound is in bytes. both may be right, but "characters" needs a definition in a document this precise — unicode scalar values, grapheme clusters, and UTF-16 units give three different answers for the same emoji, and a client in TypeScript and a host in Go will pick different ones by default. naming the unit (and NFC or not) would prevent a class of interop bug that only shows up on non-ASCII names.

on the pr note itself: checking every limit against the code at 7c853e0fb rather than against #6742's description, and reporting the two places the description was wrong, is exactly how a spec-before-implementation should be written up. worth keeping that commit sha in the document's own header rather than only in the pr body, since the pr body isn't what a future reader has.

jmecom
jmecom previously approved these changes Aug 26, 2026
- Key the idempotency record on (authenticated principal, requestId),
  never requestId alone (I3, C2)
- Rule empty arrays a second spelling of absence: optional arrays are
  omitted when empty, a present [] is malformed (I6)
- State the shared publish outcome (eventId/kind/createdAt) once at
  message.post
- Define the no-answer treatment for a request whose requestId is
  itself unusable
- Name the unit behind 'characters' (Unicode scalar values)
- Correct the storage.address example kind to the agent-memory kind
- Say out loud that read completeness on the wake path is trusted, not
  verifiable: silent mention withholding is indistinguishable from quiet

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
jmecom
jmecom previously approved these changes Aug 26, 2026
@baxen
baxen enabled auto-merge (squash) August 26, 2026 16:44
Review on the contract asked the pubkey type to preserve the
public-key invariant rather than accept any 64 hex characters; the
contract is being tightened to match, so the spec states the rule.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
baxen added a commit that referenced this pull request Aug 26, 2026
A documentation-minimalism pass, no code changes. The English spec
(docs/agent-broker.md, #6790) is the canonical home for design rationale;
doc comments now state each invariant once, where an implementer will read
it, instead of retelling the same stories (no-null, second wire door,
identity spelling, validate-then-execute trap) across module docs, type
docs, and helper docs.

Also names memory read/write in the deferred-operations list, per review:
v1's storage.address only addresses a record, so intent-level memory
operations are a declared follow-up alongside presence/typing.

Net -404 lines, all comments. Validation: cargo test -p buzz-sdk 301/301,
clippy --all-targets clean, RUSTDOCFLAGS="-D warnings" cargo doc clean,
fmt clean. The diff contains no non-comment lines (verified by filtering
the diff for changes outside //-prefixed lines).

Signed-off-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz>
Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.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.

4 participants