Skip to content

NIP-AD: durable agent drafts + external agent adoption - #20

Merged
BrianInAz merged 20 commits into
mainfrom
feat/nip-ad-durable-agent-drafts
Aug 6, 2026
Merged

NIP-AD: durable agent drafts + external agent adoption#20
BrianInAz merged 20 commits into
mainfrom
feat/nip-ad-durable-agent-drafts

Conversation

@BrianInAz

Copy link
Copy Markdown
Owner

Summary

Fixes #18 — the Desktop never ingested CLI agent draft-create broadcasts because the old agent_management_request path used ephemeral kind 24200, which the relay never stores. A draft published while the owner's Desktop was closed was lost forever.

This replaces that path with two durable, p-gated, FTS-excluded kinds:

  • 44300 — agent → owner draft request (two p tags: owner + agent, agent tag, no h tag, NIP-44 v2)
  • 44301 — owner → agent resolution (one e tag referencing the request)

A draft now replays on the owner's next launch, and the Desktop review dialog is driven by a durable store instead of a live observer frame.

What changed

  • NIP-AD spec (docs/nips/NIP-AD.md) + NIP-AO pointer
  • buzz-core: kinds 44300/44301 in P_GATED_KINDS/RESULT_GATED_KINDS, agent_draft payload module (fail-closed version validation)
  • relay: ingest envelope validation + is_agent_owner, global-only storage, FTS exclusion (migration 0027)
  • buzz-sdk: build_agent_draft_request / build_agent_draft_resolution
  • buzz-cli: draft-create/draft-update publish durable 44300; new agents drafts list|status
  • Desktop: durable draft store + review dialog; adopt_external_agent (attest-first, no new keypair) + import_external_agent_key; BackendKind::External fails closed on spawn/restart/deploy
  • E2E: relay integration (e2e_agent_draft.rs, 14 tests) + Desktop Playwright (agent-draft-review.spec.ts)

Live acceptance

Deployed the NIP-AD relay to production (buzz.bjzy.me) and verified: a real draft-create published as a Hermes specialist key (with BUZZ_AUTH_TAG) while the Desktop was closed is accepted and readable by the owner on the next launch — proving the durable backfill fixes B1.

Screenshots

{{01-draft-review}}

{{02-auth-tag}}

Notes

  • The owner cannot decrypt 44301 (it is encrypted to the agent), so resolved-ness is derived from the cleartext e tag.
  • nostr's EventBuilder discards self-p-tags by default; the two-p-tag envelope requires allow_self_tagging() at build time.

Defines durable p-gated kinds 44300 (agent draft request) and 44301
(owner draft resolution), replacing the ephemeral agent_management_request
telemetry payload on NIP-AO kind 24200. NIP-AO now points at NIP-AD.

Closes #18 (part 1/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds KIND_AGENT_DRAFT_REQUEST (44300) and KIND_AGENT_DRAFT_RESOLUTION
(44301) to P_GATED_KINDS and RESULT_GATED_KINDS, a new agent_draft payload
module (create/update request + resolution, NIP-44 encrypt/decrypt, fail-closed
version validation), and widens reader_authorized_for_event to derive the gate
from RESULT_GATED_KINDS so future kinds inherit it.

Note: nostr's EventBuilder discards self-p-tags by default; the two-p-tag
NIP-AD envelope requires allow_self_tagging() at build time.

Closes #18 (part 2/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Wires kinds 44300/44301 into relay ingest: MessagesWrite scope, global-only
storage (no h tag), envelope validation (two p tags, agent tag, no h, NIP-44,
author direction, single e tag for 44301), and is_agent_owner ownership check.
Extends the ids-exemption in p_gated_filters_authorized to derive from
RESULT_GATED_KINDS. Adds migration 0027 + schema.sql to exclude both kinds
from FTS. count.rs and bridge.rs inherit the gate via RESULT_GATED_KINDS /
reader_authorized_for_event.

Closes #18 (part 3/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds build_agent_draft_request (44300) and build_agent_draft_resolution
(44301) with the two-p-tag + agent-tag (+ e-tag for resolution) envelope,
NIP-44 content check, owner!=agent and hex validation, and allow_self_tagging
so the author's own pubkey survives as a p tag.

Closes #18 (part 4/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
draft-create/draft-update now publish a durable kind 44300 via submit_event
(was ephemeral kind 24200), building AgentDraftRequestPayload and returning
event_id. Adds 'buzz agents drafts list|status' reading both 44300/44301,
decrypting with the running key, and joining on requestId for pending-ness.

Closes #18 (part 5/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
The owner cannot decrypt kind 44301 (it is encrypted to the agent), so
drafts list/status must determine resolved-ness from the cleartext e tag
(which references the request event id) rather than decrypting resolutions.

Closes #18 (part 5 fix)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds list_pending_agent_drafts (queries 44300/44301, decrypts 44300 with the
owner key, drops undecryptable and resolved requests via the cleartext e tag)
and resolve_agent_draft (builds + signs + publishes a kind 44301 resolution).
Registers both in the invoke_handler. lib.rs kept at the 1000-line ratchet.

Closes #18 (part 6/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds adopt_external_agent: mints the NIP-OA BUZZ_AUTH_TAG from the agent's
public key and the owner's secret (no new keypair) and stores the agent with
BackendKind::External. Adds the External backend variant and fail-closed
guards on the spawn/restart/deploy paths. agents.rs and types.rs kept within
their file-size ratchets.

Closes #18 (part 7/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Replaces the ephemeral kind-24200 agent_management_request path with a durable
agentDraftStore (backfill via list_pending_agent_drafts + live 44300
subscription, dedupe by requestEventId). useAgentManagement now reads from the
store, classifies origin via declared NIP-OA ownership (agentDraftTrust), and
publishes 44301 resolutions on accept/decline/dismiss. Removes the
agent_management_request branch from observerRelayStore (24200 is telemetry
only again). Adds kinds 44300/44301 and typed Tauri wrappers.

Closes #18 (part 8/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Create drafts now review through AgentDraftAdoptDialog whose primary action is
'Adopt this identity' (adopt_external_agent, no new keypair) with a
separately-confirmed 'Import key to run from this Desktop'
(import_external_agent_key). After adoption the minted BUZZ_AUTH_TAG is shown
with a copy affordance. Adds the import_external_agent_key Tauri command and
an AGENTS.md rule for BackendKind::External.

Closes #18 (part 9/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
classifyAgentManagementOrigin is replaced by classifyAgentDraftOrigin
(agentDraftTrust.ts) which keys on declared NIP-OA ownership instead of local
managed-agent membership.

Closes #18 (part 8 cleanup)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
B2 cold-start fix: useManagedAgentObserverBridge now opens the owner-global
24200 REQ whenever an identity is known, regardless of agents.length, so a
newly adopted external agent's live telemetry is subscribed even when no
agent existed before. knownAgentPubkeys remains the telemetry decrypt gate.
Removes the now-obsolete shouldObserveManagedAgents gate and its test.

Closes #18 (part 10/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Covers every read chokepoint (REQ, kindless ids, COUNT, live fan-out) for
kinds 44300/44301: owner/agent read, third party closed out, ingest envelope
rejections (no owner binding, wrong p cardinality, h tag, non-NIP-44, wrong
author), resolution e-tag join, and FTS exclusion.

Closes #18 (part 11/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds agent-draft-review.spec.ts (smoke project) covering: draft arrives →
review dialog opens; adopt → BUZZ_AUTH_TAG shown with copy; decline →
resolution published and the draft does not resurface after reload. Adds mock
bridge handlers for list_pending_agent_drafts / resolve_agent_draft /
adopt_external_agent, a pendingAgentDrafts seed, and the
__BUZZ_E2E_EMIT_MOCK_AGENT_DRAFT__ helper. Keeps the adopt dialog mounted after
adoption so the auth tag stays visible.

Closes #18 (part 11/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Adds CHANGELOG + relay CHANGELOG entries for NIP-AD durable agent drafts and
captures review-dialog screenshots from the Playwright spec.

Closes #18 (part 12/12)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Closes #18 (part 12 fmt)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

BrianInAz added a commit that referenced this pull request Aug 6, 2026
@BrianInAz

Copy link
Copy Markdown
Owner Author

Draft review dialog

A durable NIP-AD draft arrives → the review dialog opens with "Adopt this identity".

01-draft-review

Auth-tag handoff

After adoption, the minted BUZZ_AUTH_TAG is shown with a copy affordance.

02-auth-tag

The committed lock was stale; cargo chef prepare updates it, so the
--locked relay build in the Docker image workflow failed with 'cannot
update the lock file'. Regenerate the lock so the CI build is reproducible.

Closes #18 (part 12 lock)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
The B2 cold-start fix opened the owner-global 24200 REQ on identity-known,
but that raced the observer-archive policy: the live filter opened before
kind 24200 was guaranteed present in the subscription, breaking
observer-archive-policy.spec.ts. Gate the subscription on identity-known AND
observerReconciled.

Closes #18 (part 10 fix)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
cargo chef cook resolves workspace crates to recipe versions that diverge
from the committed lock, so the --locked relay build failed with 'cannot
update the lock file'. Build without --locked so the lock is refreshed to
the chef resolution.

Closes #18 (part 12 docker)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
Same cargo chef cook lock divergence as the relay Dockerfile; build without
--locked so the lock is refreshed to the chef resolution.

Closes #18 (part 12 docker)

Signed-off-by: Brian Charbonneau <github@briancharbonneau.com>
@BrianInAz
BrianInAz merged commit f2c5f94 into main Aug 6, 2026
30 checks passed
@BrianInAz
BrianInAz deleted the feat/nip-ad-durable-agent-drafts branch August 6, 2026 05:12
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.

Desktop never ingests CLI agent draft-create broadcasts (kind 24200 observer frames)

1 participant