feat(relay): channel-gated agent work status kind (NIP-AW, 30181) - #6582
feat(relay): channel-gated agent work status kind (NIP-AW, 30181)#6582LucasMoskun wants to merge 1 commit into
Conversation
bcda016 to
5f119d5
Compare
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. Signed-off-by: LucasMoskun <contact@lucasmoskun.com>
5f119d5 to
324373d
Compare
🔐 Codex Security Review
|
|
Rebased onto current main — the merge conflict is resolved and all checks are green (full unit suite passing locally as well). No functional changes since the original submission. @wpfleger96 you seem to be the most active in |
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.