--- name: nostr description: > How Nostr works in this repository: the three NIP lanes under `nips/`, the wire protocol (NIP-01), relay authentication (NIP-42), payload encryption (NIP-44), the Block agent NIPs the relay serves, and the OpenAgents NIPs (NIP-CJ jobs, NIP-CAP capabilities, NIP-PRG programs) that carry a Coder turn to a worker. Read it before touching `crates/nostr`, `crates/nostr-relay`, `crates/coder/src/relay.rs`, `coder-worker`, or any file under `nips/`, and before debugging a relay handoff. --- # Nostr in OpenAgents Use this skill when you change or debug anything that touches a relay: the protocol crates, the relay binary, the Coder relay door, the worker, or a specification under `nips/`. The specifications are the source of truth; this skill tells you where they are and how the pieces fit, so you don't rediscover the flow from code. ## Where the specifications live | Lane | Path | Source of truth | Synced | | --- | --- | --- | --- | | Official NIPs | `nips/official/` | [nostr-protocol/nips](https://github.com/nostr-protocol/nips) | yes, `./scripts/sync-nips.sh` | | Block (Buzz) extension NIPs | `nips/block/` | [block/buzz](https://github.com/block/buzz/tree/main/docs/nips) | yes | | OpenAgents NIPs | `nips/openagents/` | this repository | no — the files here are authoritative | `nips/manifest.json` pins the upstream commit of each synced lane. A sync never changes the implementation without review and a fixture update. `nips/README.md` states the mandate: every pinned spec that applies to a relay is an implementation target for `crates/nostr` and `crates/nostr-relay`, optional features stay fail-closed and out of NIP-11 until they run, and client-only NIPs are implemented as fixture-backed clients rather than pretended. Read the [September 26 upstream review](../../../docs/protocol/2026-09-26-upstream-nip-sync.md) and [implementation coverage](../../../docs/protocol/2026-09-26-nip-implementation-coverage.md) before assuming complete support. Source inventory checks now track the current pins separately from behavioral evidence. PMA 30179 is rejected, client-authored 39007 is refused, and app data and private artifacts have explicit visibility. RS has an opt-in atomic HTTP snapshot. The PL executor is off by default and needs a credential-holding push gateway; no real device delivery is recorded. Client helpers do not imply complete AP, FI, CW-thread, or RS merge integration. Read the spec before the code. The files you need most often: - `nips/official/01.md` — the protocol: events, kinds, filters, and the client-relay messages. - `nips/official/42.md` — client authentication to a relay. - `nips/official/44.md` — encrypted payloads. - `nips/official/11.md` — the relay information document. - `nips/official/40.md` — the `expiration` tag. - `nips/official/19.md` — `npub` and `nsec` encoding. - `nips/block/README.md` — a per-spec summary of all 17 Block NIPs. - `nips/openagents/NIP-CJ.md` — Coder jobs, the protocol between `coder` and `coder-worker`. - `nips/openagents/NIP-DEC.md` — decisions: typed questions with structured (EntryType) instructions and criteria, carried by the CJ decision family. - `nips/openagents/NIP-CAP.md` — capability manifests and presence. - `nips/openagents/NIP-PRG.md` — programs. - `nips/openagents/NIP-EXT.md` — extension releases, discovery, revocation, foreign-format import, and compatible host component-set assessments. - `nips/openagents/NIP-REG.md` — curated plugin registries, exact publisher release pins, per-registry trust, and signed Nostr and GitHub/HTTPS sources. - `nips/openagents/NIP-RUN.md` — encrypted durable journals and recovery. - `nips/openagents/NIP-CTX.md` — task frames, evidence views, and expansion. - `nips/openagents/NIP-POL.md` — instructions, approvals, disclosure, routing records, and governed activation of learned private preferences. - `nips/openagents/NIP-COORD.md` — task claims, fencing, and background findings. - `nips/openagents/NIP-EVAL.md` — attributable workload evaluation and promotion evidence. - `nips/openagents/NIP-OPT.md` — semantic AI contracts, immutable implementations, bounded studies, actual candidate execution, and optimization evidence. - `nips/openagents/NIP-XP.md` — quests, referee awards of accepted outcomes, revocations, and per-reader XP ledgers; `crates/nostr` (`xp`) checks them. - `nips/openagents/NIP-KB.md` — shared knowledge entries, heads, withdrawals, and evidence; `crates/nostr` (`kb`) checks them. - `nips/openagents/NIP-CTRL.md` — task-scoped client pairing, observation, steering, and cancellation; no approval or wallet authority from pairing. - `nips/openagents/NIP-MKT.md` — negotiated offerings, bilateral orders, private records, and separately admitted fixed-price Lightning settlement. - `nips/openagents/NIP-LAB.md` — bounded agent labor, exact execution linkage, deliverables, independent checks, rework, disputes, acceptance, and rights. - `nips/openagents/NIP-X402.md` — Designed Lightning-paid operations before execution, standard x402 HTTP/MCP bindings, and an opt-in native Nostr profile. - `nips/openagents/NIP-SESS.md` — engine sessions, actual adapter support, queues, turn control, pending interactions, and retained native history. - `nips/openagents/NIP-WS.md` — workspace/resource identity, conditional edits, checkpoints, and bounded projection snapshots, deltas, and cuts. - `nips/openagents/NIP-WORK.md` — tracked objectives, planning relationships, accountable ownership, delegation, exact revisions, and work disposition. - `nips/openagents/NIP-AUTO.md` — durable schedules, source watches, bounded goal continuations, occurrence identity, and restart-safe accounting. - `nips/openagents/NIP-ENV.md` — execution environment leases, actual materialization, attachment, cleanup, and uncertain resource accounting. - `nips/openagents/NIP-LIVE.md` — admitted media/capture, participants and recipients, input and speaking floors, evidence, and scoped device input. - `nips/openagents/NIP-REACH.md` — private host directory, bounded presence, reachability, and authenticated direct channels. Placement grants no rights. - `nips/openagents/NIP-HOST.md` — host-wide device enrollment and scoped grants; access rights do not supply POL approval or spending authority. - `nips/openagents/NIP-TERM.md` — host-owned interactive terminals, bounded output replay, explicit gaps, and current access checks. - `nips/openagents/NIP-SOV.md` — Designed successor to historical SA: durable identity, supported custody, bounded AUTO lifecycle, POL guardians, treasury policy, and retained recovery evidence. It allocates no new event kinds. - `nips/openagents/NIP-MV.md` — Verse world presence, entity state, and gestures; independent of the shared agent-artifact contracts. - `docs/optimization/README.md` — DSPy/GEPA concepts, Gym evaluation, host boundaries, and a consolidated unfiled integration backlog. - `nips/openagents/contracts.md` — pinned identities, schemas, locks, evidence, context, effects, outcomes, and private artifact envelopes for the v1 contracts. - `docs/coder/design/typesafe-agent-protocol-addendum.md` — what belongs in Nostr and what hosts/clients must implement; complete source-proposal coverage. - `docs/protocol/implementation-plan.md` — implementation across all lanes. - `docs/agents/README.md` and `docs/agents/roadmap.md` — general agent infrastructure, Coder's domain boundary, and remaining non-code contracts. - `docs/protocol/block-nips.md` — what the relay does with each Block NIP, including what it deliberately doesn't advertise. The OpenAgents lane contains 30 NIPs plus the shared contracts. X402, SESS, WS, WORK, AUTO, ENV, LIVE, and SOV are **Designed** drafts, as are POL's learned preference lifecycle and EXT's import/component-set assessment additions. REG is a **Designed** profile using inert EXT catalogs and existing release/head kinds; no registry client is implemented. These profiles allocate no new event kinds: private artifacts use `3188`, remote host operations use admitted CAP/CJ profiles, and effects leave RUN evidence. Keeping an encrypted envelope does not implement the contract inside it. SOV does not restore the legacy `392xx` allocations or imply working key custody, guardians, or payments. REACH has a narrower `coder-reach` implementation; consult its role limits before treating reachability as availability or controller-transfer authority. ATIF is a **Designed** draft that does allocate kinds: public trajectory declarations `3198` and chunks `3199`; private trajectories ride `3188`. It replaces historical `39230`/`39231`; no component publishes trajectories yet. MKT/LAB has a bounded free-order host in `crates/coder-labor`, with retained relay and process fixtures. Paid settlement, independently operated service delivery, and broader market roles remain unfinished. CTRL has a bounded local-owner bridge in `crates/coder-control`; read `docs/coder/runtime/nostr-task-control.md` for its rights, expiry, exact retry, and disclosure limits. It does not complete mobile control or a cross-device product. Check the [glossary](../../../docs/glossary.md#nostr-and-shared-protocols) for the narrower implemented and partial roles. Read the [x402 integration assessment](../../../docs/coder/design/x402-lightning-nostr-integration.md) before adding a payment adapter. NIP-X402 preserves upstream `http:1` and `mcp:1`; `nostr:openagents:1` is an opt-in OpenAgents extension that requires explicit facilitator support. It pays before execution; MKT/LAB pays after acceptance. NWC is wallet transport, not purchase authority. Zap invoices hash the zap request, while x402 invoices hash the bound operation; do not reuse one invoice as both. L402/LSAT is a distinct macaroon-based protocol. These paths have no operational OpenAgents wallet or settlement adapter. Pure validation components do not consume proofs or authorize spending. Read the [teardown integration plan](../../../docs/coder/design/teardown-nostr-integration.md) and [file-by-file archive coverage](../../../docs/protocol/2026-09-26-teardown-coverage.md) when carrying historical ideas forward. The archive supplies design evidence; its old TypeScript, hosting, and product decisions are not current instructions. A tracked work item, session, run, environment, view, and commercial order have separate identities. Assignment does not dispatch; completion does not accept labor; acceptance does not pay; a live view does not grant mutation. Hosts must enforce these boundaries before advertising the corresponding role. ## The protocol in one page (NIP-01) An **event** is a signed JSON object: `id` (SHA-256 of the serialized fields), `pubkey` (the author's x-only secp256k1 key, hex), `created_at` (unix seconds), `kind`, `tags` (arrays of strings; the first element is the tag name), `content`, and `sig` (Schnorr). The `id` and `sig` are what make an event evidence: a relay can relabel or drop an event, but it can't forge one. **Kind ranges** decide how a relay stores an event: | Range | Class | Relay behavior | | --- | --- | --- | | everything not listed below | regular | stored, all kept | | `0`, `3`, `10000`–`19999` | replaceable | latest per `pubkey`+`kind` kept | | `20000`–`29999` | ephemeral | fanned out to open subscriptions, never stored | | `30000`–`39999` | addressable | latest per `pubkey`+`kind`+`d` tag kept | `crates/nostr` implements this as `EventClass::from_kind` in `domain/replacement.rs`. Every NIP-CJ job kind is ephemeral; NIP-CAP and NIP-PRG discovery kinds are addressable. NIP-EXT and NIP-RUN add regular immutable records with separate addressable heads. A discovery head is never an execution version pin. **Messages**, client to relay: - `["EVENT", ]` — publish. - `["REQ", , , ...]` — subscribe; the relay sends stored matches, then `EOSE`, then live matches until `CLOSE`. - `["CLOSE", ]` — end a subscription. - `["AUTH", ]` — answer a NIP-42 challenge. Relay to client: - `["EVENT", , ]` — a match. - `["OK", , true|false, ]` — the verdict on an `EVENT` or `AUTH`. A `false` message starts with a machine-readable prefix: `duplicate:`, `pow:`, `blocked:`, `rate-limited:`, `invalid:`, `restricted:`, `mute:`, `auth-required:`, or `error:`. - `["EOSE", ]` — end of stored events; what follows is live. - `["CLOSED", , ]` — the relay ended the subscription, with the same prefixes. - `["NOTICE", ]` — human-readable. - `["AUTH", ]` — a NIP-42 challenge. **Filters** select on `ids`, `authors`, `kinds`, `#`, `since`, `until`, and `limit`. Only single-letter tags are indexed, which is why NIP-CJ routes on `e` and `p`. ## Authentication (NIP-42) A relay may send `["AUTH", ]` at any time; this relay sends one on connect whenever `NOSTR_RELAY_URL` is set. The client answers with a kind-`22242` event carrying `["relay", ]` and `["challenge", ]` tags, `created_at` within ten minutes of now, and the relay replies `["OK", , true, ""]`. Kind `22242` is never stored or broadcast. An unauthenticated client isn't refused by default. Only what the relay gates needs auth: - Everything, when `NOSTR_RELAY_AUTH_REQUIRED=true` — the relay answers `auth-required:` on `EVENT` and `CLOSED ... auth-required:` on `REQ`. - Reads of private Block kinds (gift wraps `1059`, engrams `30174`, turn metrics `44200`, and others listed in `event_visible_to_reader` in `crates/nostr-relay/src/gateway/subscription.rs`). A subscription's `read_pubkeys` is the set of keys the connection has authenticated as. - Protected events (NIP-70 `-` tag), closed-membership relays, and every relay command in `docs/protocol/block-nips.md`. The Coder client and worker always authenticate when challenged — the `npub` is the account — and both refuse a connection whose challenge gets no `OK`. NIP-AA lets an agent key satisfy AUTH with an owner attestation (`nips/block/NIP-OA.md`) instead of its own membership. ## Encryption (NIP-44) NIP-44 version 2 encrypts a payload under a **conversation key** derived from the sender's secret and the recipient's public key (ECDH, then HKDF), so either party can decrypt and nobody else can. It hides the content and pads its length; it does not hide the tags, so `kind`, `e`, and `p` stay visible to the relay as routing metadata. Gift wraps (NIP-59) and every NIP-CJ payload use it. The implementation is `crates/nostr`'s `nip44` module. ## Coder jobs (NIP-CJ) `nips/openagents/NIP-CJ.md` is the contract between `coder` (the customer) and `coder-worker` (the fulfiller). The relay is transport only: it holds no job state and sees only ciphertext. | Kind | Name | Direction | Payload `type` | | --- | --- | --- | --- | | `25900` | job request | terminal → worker | `task`, `transcript`, `instructions`, `client`, `v` | | `27000` | job feedback | worker → terminal | `judgment`, `partial` (with `seq` under `v: 1`), `status` | | `26900` | job result | worker → terminal | `result` with `text`, optional `usage` and `model` | The flow, in the order the sockets speak it: 1. The worker connects, answers AUTH, and subscribes `{"kinds": [25900], "#p": []}`. 2. The terminal connects, answers AUTH, signs a kind-`25900` request `p`-tagged to the worker with NIP-44 content encrypted to the worker's key, subscribes `{"kinds": [26900, 27000], "#e": []}`, and only then publishes the request. Subscribing first closes the race where fast feedback would be missed. 3. The relay fans the request out to the worker's subscription. The worker decrypts, generates through its door, and publishes feedback and exactly one result, each `e`-tagged to the request and `p`-tagged to the customer, encrypted to the customer's key. 4. The terminal accepts an event only if the signature covers what it expects: the kind is `26900` or `27000`, the signer is the configured worker, an `e` tag names this request, and a `p` tag names this terminal. The subscription label is a routing hint, never identity. 5. The job ends on the first result or `status: error`. The terminal then sends `CLOSE` for the job's subscription and keeps the socket for the next turn. A relay `CLOSED` on that subscription ends the job as a relay error with the relay's reason. A socket that broke, or a wait that ran out with a subscription that might still deliver, is dropped, and the next turn opens a fresh one. A turn holds the socket for its duration, so a caller that cancels the turn drops the socket with it; the relay frees the subscription when the connection ends. The conversation specification uses integer v1 with sequenced partials. Decision and execution bodies use their named v1 schemas. Validate each family against its own complete contract; do not infer support from a version alone. Deadlines and failure words, on the terminal side (`crates/coder/src/relay.rs`): if nothing bound to the request arrives within the contact deadline, the turn fails with cause `worker_absent` ("no worker answered"). If a worker was heard but no result arrives within the answer deadline, the cause names the worker as silent. A relay `OK … false` on the request fails the turn with the relay's reason. Opening a socket, handshake and AUTH together, is bounded by `CONNECT_TIMEOUT`. A worker that fails upstream and publishes nothing looks identical to an absent worker from the terminal — check the worker's stderr before blaming the relay. A worker whose door is a local executor (`CODER_EXECUTOR`) publishes one encrypted kind-`27000` feedback event, `{"type": "status", "status": "processing"}`, as soon as it admits a delegation and before the executor starts. The terminal counts any well-formed kind-`27000` status of `queued`, `processing`, or `error` as contact, so its 30-second contact deadline ends at admission rather than at the executor's answer. Environment, terminal side: `CODER_WORKER` (worker `npub` or hex pubkey) and `CODER_RELAY` (`ws://` or `wss://` URL). The identity is `CODER_SECRET_KEY` or `CODER_NSEC`, or, when neither is set, the key at `~/.openagents/nostr-secret`, created on first use with mode `0600`. Only a missing file is a first use: an unreadable one is an error, so a permissions accident cannot silently mint a new `npub`. Creation is exclusive and atomic, and concurrent first runs agree on one key. A fresh keypair is a valid anonymous account; never print, log, or commit the secret. Environment, worker side (`crates/coder/src/bin/coder-worker.rs`): `CODER_WORKER_SECRET` (64 hex or `nsec`), `CODER_RELAY`, and the door: `CODER_DOOR_KEY` with `CODER_DOOR_URL` and `CODER_WORKER_MODEL` for an Open Responses door, or `CODER_EXECUTOR=` for a local approved executor such as `devin-local` (see `docs/coder/guides/worker-executor.md`). Setting both is refused. The worker prints its pubkey on start; that value is what the terminal's `CODER_WORKER` names. `--once` answers one job and exits; `--decline ` refuses every job with a typed status error, for measuring the refusal path. `CODER_WORKER_ALLOW` (comma-separated `npub` or hex keys) limits which customers the worker answers; anyone else gets a typed `not_admitted` status, never silence. Unset admits everyone, which is right only on a local relay. `CODER_WORKER_JOBS` bounds how many jobs the worker runs at once; unset, an executor door runs as many as its manifest's `concurrent_max` and a model door runs four. A request past the bound is refused before anything runs, with a typed `busy` status, and the refusal releases the slot. The worker's key and the terminal's key must differ. `docs/coder/measurements/relay-transport.md` is the measured proof that both ends meet, with per-transport latency and refusal causes. ## Capabilities and programs (NIP-CAP, NIP-PRG) CAP describes portable interfaces, host bindings, presence, and operator preferences. Reading a manifest is inert; probes and invocations have separate admission. PRG defines typed workflows, seven step kinds, bounded composition, and the Wasm packet ABI. EXT distributes immutable components and RUN records durable execution. OPT separates semantic AI signatures from their concrete implementations. Programs can invoke a supported pinned implementation through the same host boundary. Compiler, optimizer, evaluator, and inference capabilities remain distinct. EVAL supplies workload evidence and scoped promotion; neither a score nor a package signature grants execution. Follow the normative NIPs for exact fields and validation. Implement only supported roles and advertise them after conformance. Artifact identity, actual materialization, protected evaluation, and operator adoption are separate checks. Local operation need not publish an event for every action. ## The relay (`crates/nostr-relay`) One binary, one Postgres database. Configuration is environment only; `deploy/nostr-relay.env.example` and `docs/deployment/configuration.md` list every variable. The ones you need for a local run: ```sh DATABASE_URL=postgres://:@127.0.0.1:5432/ NOSTR_RELAY_BIND_ADDR=127.0.0.1 NOSTR_RELAY_PORT=7447 NOSTR_RELAY_URL=ws://127.0.0.1:7447 # enables the NIP-42 challenge NOSTR_RELAY_MEDIA_ROOT=$HOME/relay-media # optional; absolute path NOSTR_RELAY_AUTH_REQUIRED=false # true to gate EVENT and REQ NOSTR_RELAY_LOG_LEVEL=info ``` Migrations run on first boot and the start fails closed if the ledger disagrees with the database. `GET /health` and a NIP-11 request (`Accept: application/nostr+json` on `/`) confirm it's up. `docs/deployment/runbook-local-dev.md` walks the same steps with a disposable Postgres cluster and the `nak` client. Live fan-out is in `gateway/subscription.rs`: a published event is indexed by id, author, kind, and each single-letter tag; a subscription is matched against its filters, then checked with `event_visible_to_reader` for the private kinds; a subscription still loading history buffers live matches until `EOSE`. Ephemeral events are deduplicated by id over a short window and never reach storage. The Block NIPs the relay serves, and how, are in `docs/protocol/block-nips.md`. The relay-signed kinds (`39005`, `39006`, `39007`, `30622`) are refused from clients with `restricted:`. ## Debugging a handoff When `coder -p` reports `worker_absent`: 1. Confirm the worker is connected and subscribed: its stderr shows `worker `, `relay `, `door …`, and `waiting for jobs`. `CODER_WORKER` must equal that printed pubkey. 2. Confirm the relay fans out: open a second subscription with the worker's filter (a `nak req` or a scripted WebSocket client) and run the turn again. Seeing the kind-`25900` event there proves the relay and the terminal; not seeing it means the request was refused (read the `OK`) or the terminal's `CODER_RELAY` points elsewhere. 3. Read the worker's stderr for `job failed: …`. A worker whose door fails upstream publishes nothing, and the terminal can't tell that from silence. 4. Run the relay with `NOSTR_RELAY_LOG_LEVEL=debug`. It then logs one line per admitted ephemeral event: kind, ID, author, `e` and `p` tags, and the content's byte length, never the content. A `25900` with no `26900` tagged `e` to it is a job the worker never answered. Ephemeral kinds are not stored, so the database holds nothing to query afterwards; the log is the only relay-side record. 5. Only then read relay code. ## Rules that don't bend - Product code is Rust. `crates/nostr` has no storage, no network, and no third-party Nostr crate. - Never print, log, commit, or paste a secret key, `nsec`, or door key. Test fixtures use throwaway keys generated in the test. - The relay is transport, not authority. Anything a relay could relabel or replay must be checked against the signature — kind, signer, `e`, and `p` — before the payload is read. - Unverifiable state is refused, not assumed: an unknown probe is not present, an unsigned answer is not an answer, and a feature that isn't configured isn't advertised in NIP-11. - A NIP change goes spec first (`nips/openagents/` or a synced upstream commit), then validators and fixtures, then the applicable host, client, or relay role. A client-only or encrypted host contract does not become relay behavior merely because its envelope passes validation.