feat(relay): channel-gated agent work status kind (NIP-AW, 30181) - #6582
Open
LucasMoskun wants to merge 1 commit into
Open
feat(relay): channel-gated agent work status kind (NIP-AW, 30181)#6582LucasMoskun wants to merge 1 commit into
LucasMoskun wants to merge 1 commit into
Conversation
Buzz Desktop shows a live "Working…" badge from ephemeral typing events, but there is no queryable answer to "what is this agent doing right now?" The buzz-acp harness already sees every turn it drives; this adds the opt-in lane that publishes a redacted projection of that activity to exactly the people who may read the channel it happens in. New parameterized-replaceable kind 30181 (docs/nips/NIP-AW.md): h = d = channel UUID, so each agent stores at most one live snapshot per channel and both writes and reads inherit the existing NIP-29 channel-membership gate — the relay needs no new access-control machinery, only registration: - buzz-core: KIND_AGENT_WORK_STATUS constant + registry entry. - buzz-relay: MessagesWrite scope, requires_h_channel_scope membership gating, and an envelope check rejecting d != h (a divergent d would allow several snapshots per channel or cross-channel coordinate collisions). Metric label registered for the new kind. - buzz-acp: --public-status / BUZZ_ACP_PUBLIC_STATUS projects the observer bus into signed status events — turn status, model id, and tool-call titles only; tool arguments, prompts, streamed text, and error messages never enter the payload by construction. Publication is paced (urgent on start/complete/error, 5s floor otherwise) and never fatal to the turn. Deliberately NOT kind 30315 (NIP-38): user status is a global-only kind on Buzz relays — any authenticated relay member can read it — which cannot honor the bar that work status be exactly as private as the channel it describes. Tested: unit tests in buzz-core/buzz-relay/buzz-acp, plus a live e2e (test_agent_work_status_channel_gating) proving member write + read, d != h rejection, missing-h rejection, non-member write rejection, and CLOSED "restricted: not a channel member" for non-member reads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: LucasMoskun <contact@lucasmoskun.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Buzz Desktop shows a live "Working…" badge from ephemeral typing indicators, but there is no persistent, queryable answer to "what is this agent doing right now?" Operators today either SSH into agent hosts or run side-channel collectors to get a live fleet view. The buzz-acp harness already sees everything it drives — turn lifecycle, tool calls, streaming — it just never tells anyone.
The privacy bar this PR holds: an agent's work status should be exactly as private as the channel it describes. Anyone who can read the channel's messages may see its status; nobody else can, enforced server-side. Kind 30315 (NIP-38) cannot meet that bar — it is a global-only kind on Buzz relays, readable by any authenticated relay member. This PR deliberately stays relay-gated plaintext (like channel messages themselves); encrypted lanes remain NIP-AO's job and out of scope here.
What this adds
A new parameterized-replaceable kind 30181,
KIND_AGENT_WORK_STATUS(spec:docs/nips/NIP-AW.md), withh=d= channel UUID:buzz-core: kind constant + registry entry.buzz-relay(registration only — no new access-control machinery):MessagesWritescope;requires_h_channel_scopeso writes and reads inherit the existing NIP-29 channel-membership gate; an envelope check rejectingd≠h(a divergentdwould allow multiple snapshots per channel or cross-channel coordinate collisions); metrics label. NIP-33 replacement keeps at most one live snapshot per (agent, channel) — a status lane, not a history log.buzz-acp: opt-in--public-status/BUZZ_ACP_PUBLIC_STATUSflag. A second consumer of the observer bus projects frames into signed status events: turn status, model id, timestamps, and tool-call titles only. Tool arguments, file paths, prompts, streamed text, and error messages never enter the payload — the projection is whitelist-shaped by construction and unit tests assert secrets stay out. Publication is paced (urgent on start/complete/error, 5 s floor otherwise, 3 s budget) and publish failure is never fatal to the turn.Any channel-authorized client can then render live agent lanes with one REQ — no collectors, no new HTTP surface, matching the "prefer Nostr events" guidance in CONTRIBUTING.
Testing
cargo test -p buzz-core -p buzz-acpgreen (262 / 811);-p buzz-relaygreen except 8 pre-existingapi::media/api::adminfailures that fail identically on untouchedmainin this environment.requires_h/global-only registration,d/hmismatch + non-UUID + missing-drejection.handd= channel).test_agent_work_status_channel_gating, run against a local relay + Postgres + Redis): member write accepted →d≠hrejected → missinghrejected → non-member write rejected (restricted: not a channel member) → member REQ returns exactly the one snapshot → non-member REQ is answered withCLOSED "restricted: not a channel member".cargo fmt --checkandcargo clippy --all-targetsclean on the four touched crates.Related PRs
Closest existing work found: #446 (original NIP-38 kind:30315 support — this PR exists because 30315 is global-only), #5098 / #4769 (desktop/diagnostic observability — complementary, different lane). None add a channel-gated status kind.
🤖 Generated with Claude Code