Repository navigation
Open
Description
What
jspod already supports giving an autonomous agent scoped, key-authenticated read/write to a pod — no OAuth, no JWT, just a Nostr key. We dogfooded the full loop and it works cleanly, but the recipe isn't documented and we hit a few avoidable papercuts. Filing so it can become docs (and maybe a couple of small DX fixes).
The pattern
An agent holds its own Nostr key. It reads a pod resource publicly and writes with a NIP-98 (kind 27235) Schnorr signature. The server resolves the signing pubkey to an identity two ways:
- As a WebID — if the pubkey is published in the pod owner's profile as a CID v1
verificationMethodreferenced fromauthentication. jspod's profile already ships thecid/lwscontext with theverificationMethod/publicKeyMultibase/publicKeyJwkslots — they're just empty by default. - As
did:nostr:<pubkey>— the fallback when the key isn't in that pod's owner profile (the cross-pod / "guest" case). Grant this identity in a WAC ACL.
Recipe
- Add the agent's Nostr key to a WebID profile as a Multikey
verificationMethod, listed inauthentication. Encoding forpublicKeyMultibase:f+e701+02+ the x-only pubkey hex. - To let the agent write a resource it doesn't own, add a resource
.aclgrantingacl:agent=did:nostr:<pubkey>theacl:Writemode. - Sign each write: a NIP-98 event with
u+methodtags, sent asAuthorization: Nostr <base64(event)>. - General edit: the agent does
GETresource, modifies the JSON-LD, thenPUTs it back signed. Two primitives (get/put) plus the agent's own reasoning give full CRUD — no per-verb endpoints needed.
Papercuts worth documenting / fixing
- The
.aclmust live in the live--root. Writing it into a same-namedpod-data/directory elsewhere silently no-ops, because the server reads the runtime data root. Easy to get wrong when multiple checkouts exist. - NIP-98
payloadtag vs body re-serialization. The verifier hashes the parsed-and-re-serialized request body, so apayloadSHA-256 computed over the client's exact bytes mismatches. Omitting the optionalpayloadtag works (the signature still binds URL + method + freshness), but this should be documented — or the verifier should hash the raw body. --no-bootstrappods render blank in a browser. The data-browser assets (data-browser-folder.js/.css) aren't placed in/public, so an HTMLGET404s them and shows nothing. Either document "it's headless, copy these in," or serve them from the package regardless of bootstrap.- A resource
.acloverrides inherited container defaults. You must restate the owner'sControland publicReadalongside the new grant, or they're lost for that one resource. acl:agentdid:nostr matching is exact-string — worth a note so people get the casing/format right.
Suggestion
A small CLI helper would turn this from "hand-edit JSON-LD" into one command, e.g. jspod profile add-nostr-key <pubkey> and/or jspod grant <webid-or-did> <mode> <resource>.
Metadata
Metadata
Assignees
Labels
No labels
Activity
melvincarvalho commentedon Jun 12, 2026
Companion from the agent side: jsclaw/podwire packages this pattern as a reusable skill (
get/put, NIP-98 signed) plus a "wiring an agent to a pod" guide — the agent-builder view of this issue's server-side writeup. The papercuts here are linked from its README.