0.0 / 30
What changed in the harness
Selection accuracy 93→91, token cost up 14%, unconfirmed writes 0%→0%.
Category breakdown
Where the score comes from.
Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.
01Safety
02Legibility
16.8 / 30
03Economics
13.9 / 20
04Discoverability
12.7 / 20
Highest-impact fix
Estimated gain +30 pointsAdd explicit identity and permission preflight tools
Expose machine-readable principal/tenant confirmation and a non-mutating permission check so agents can verify both before destructive actions.
Description evidence
Defects and rewrites.
40 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.
| Tool | Defect types | Suggested rewrite |
|---|---|---|
getProfile |
name_restates_behavior no_return_description |
Fetch the Nostr profile metadata (kind 0 event) for a public key from relays, returning the user's relayed metadata fields such as name, about, picture, and NIP-05. Use a pubkey in hex or npub format; optionally restrict which relays are queried to speed up retrieval or reach relays where the user is active. |
getKind1Notes |
no_return_description |
Fetch text notes (kind 1 events) authored by a public key, returning the note events with their content, ids, and timestamps, newest first. Optionally cap the number of notes returned (default 10), filter by a since/until time range in unix seconds, and restrict which relays are queried. |
getReceivedZaps |
name_restates_behavior no_return_description |
Fetch zaps received by a public key from relays, returning the matching zap events with amounts, senders, and comments so you can total or display incoming lightning payments. Optionally limit results, filter by a since/until time range, validate zap receipts according to NIP-57, and enable verbose debug logging. |
getSentZaps |
name_restates_behavior no_return_description |
Fetch zaps sent by a public key from relays, returning the matching zap events with amounts, recipients, and comments so you can total or display outgoing lightning payments. Optionally limit results, filter by a since/until time range, validate zap receipts according to NIP-57, and enable verbose debug logging. |
getAllZaps |
no_return_description |
Fetch both zaps sent and zaps received by a public key, returning the combined zap events so you can summarize an account's lightning activity. Optionally cap the total number of zaps returned (default 20), filter by a since/until time range, validate receipts per NIP-57, and enable verbose debug logging. |
getLongFormNotes |
name_restates_behavior no_return_description |
Fetch long-form articles (kind 30023 events) authored by a public key, returning each article's content, title/topic tags, and timestamps. Optionally limit the number of notes, filter by a since/until time range in unix seconds, and restrict which relays are queried. |
queryEvents |
name_restates_behavior no_return_description |
Run a generic Nostr event search across relays using a filter of event kinds, authors, event ids, tag values (e.g., { p: [pubkey] }), and a since/until time range, returning the matching signed events with their full content and metadata. Useful as a fallback when no dedicated get* tool fits. Optionally cap results (default 25), provide a NIP-50 search string (relay support varies), and authenticate to relays that require NIP-42. |
getContactList |
name_restates_behavior no_return_description |
Fetch a user's contact list (kind 3 event) from relays, returning the pubkeys they follow together with any stored relay hints and petnames for each contact. Optionally restrict which relays are queried. |
getFollowing |
no_return_description |
Return the list of followed pubkeys for a user (the contact entry pubkeys from their kind 3 contact list; alias of getContactList). Optionally restrict which relays are queried. |
getRelayList |
name_restates_behavior no_return_description |
Fetch a user's relay preferences published as a NIP-65 kind 10002 event, returning the relay URLs along with their read and write flags. Optionally restrict which relays are queried, and authenticate to relays that require NIP-42 using a private key. |
setRelayList |
no_return_description |
Publish a NIP-65 kind 10002 relay list for an account, declaring which relays the user reads from and writes to, and return a confirmation of the published event. Provide the signing private key and the relay entries (URL with optional read/write flags); entries with neither flag default to both. |
follow |
no_return_description |
Add a pubkey to an account's follow list by publishing an updated kind 3 contact list, optionally storing a relay hint and petname for that contact, and return a confirmation of the published event. Takes the account's private key and the target pubkey. |
unfollow |
no_return_description |
Remove a pubkey from an account's follow list by publishing an updated kind 3 contact list, and return a confirmation of the published event. Takes the account's private key and the target pubkey. |
reactToEvent |
name_restates_behavior no_return_description |
Publish a kind 7 reaction to a target event under a specific account's identity, allowing '+', '-', or an emoji as the reaction content, and return the published reaction event (including its id). Useful to signal approval or acknowledgment of a note. |
repostEvent |
name_restates_behavior no_return_description |
Share a target event to your followers by publishing a kind 6 repost under a specific account's identity, referencing the original event id, and return the published repost event (including its id). Takes the account's private key and the target event id. |
deleteEvent |
name_restates_behavior no_return_description |
Publish a kind 5 deletion request for one or more events the account created, asking relays to treat them as deleted, optionally with a reason, and return a confirmation of the published request. Takes the account's private key and the target event ids. |
replyToEvent |
name_restates_behavior no_return_description |
Post a kind 1 reply to a target event under a specific account's identity, automatically adding the correct NIP-10 thread tags so the reply is threaded to the root and parent, and return the published reply event (including its id). Provide the private key, target event id (hex, note, or nevent), and the reply text. |
encryptNip04 |
no_return_description |
Encrypt a plaintext message for a recipient using NIP-04 (AES-CBC), returning the ciphertext in the '<cipher>?iv=<iv>' format that can be sent as a kind 4 DM. Takes the sender's private key and the recipient's public key (hex or npub). |
decryptNip04 |
no_return_description |
Decrypt a NIP-04 (AES-CBC) encrypted message using the recipient's private key, returning the original plaintext. Provide the sender's public key and the ciphertext in the '<cipher>?iv=<iv>' format. |
sendDmNip04 |
name_restates_behavior no_return_description |
Publish a kind 4 direct message to a recipient, encrypting the content with NIP-04 (AES-CBC) using the sender's private key, and return a confirmation of the published event. Optionally set the created_at timestamp, choose publish relays, and authenticate to relays that require NIP-42. |
getDmConversationNip04 |
no_return_description |
Fetch kind 4 direct messages exchanged between your account and a peer from relays, and by default decrypt them with your private key to return the plaintext conversation messages. Optionally limit the number of messages (default 50), filter by a since/until time range, disable decryption to return ciphertext, and authenticate to relays that require NIP-42. |
encryptNip44 |
no_return_description |
Encrypt a plaintext message for a recipient using NIP-44 (ChaCha20 + HMAC), returning the encrypted base64 ciphertext payload. Takes the sender's private key and the recipient's public key; optionally specify the NIP-44 version to use. |
decryptNip44 |
no_return_description |
Decrypt a NIP-44 (ChaCha20 + HMAC) encrypted payload using the recipient's private key, returning the original plaintext. Provide the sender's public key and the NIP-44 base64 ciphertext. |
sendDmNip44 |
name_restates_behavior no_return_description |
Publish a NIP-44 encrypted direct message wrapped as a NIP-17 gift wrap event (kind 1059) so only the recipient can unwrap it, and return a confirmation of the published event. Takes the sender's private key, the recipient's public key, and the plaintext content; optionally choose publish relays and authenticate to relays requiring NIP-42. |
decryptDmNip44 |
name_restates_behavior no_return_description |
Unwrap a NIP-17 gift wrapped direct message (kind 1059 event) using the receiver's private key, returning the inner kind 14 rumor including its plaintext content and metadata (author, timestamp, tags). Provide the receiver's private key and the gift wrap event object. |
getDmInboxNip44 |
no_return_description |
Fetch the kind 1059 NIP-17 gift wrap events addressed to your account from relays and decrypt them with your private key, returning your received DM messages in plaintext. Optionally limit how many wraps are fetched/decrypted (default 25), filter by a since/until time range, and authenticate to relays that require NIP-42. |
sendAnonymousZap |
no_return_description |
Prepare an anonymous zap (lightning payment with an optional comment) targeting a profile pubkey or a specific event, returning the zap request/bolt11 data needed to complete the payment. Accepts a target in hex, npub, nevent, note, or naddr form and an amount in satoshis (minimum 1). |
convertNip19 |
no_return_description |
Convert a NIP-19 entity or hex string between formats (npub, nsec, note, hex, nprofile, nevent, naddr), returning the converted value. Provide the input and target type; supply optional relays, author, kind, or identifier when converting complex entities such as nprofile, nevent, or naddr. |
analyzeNip19 |
no_return_description |
Decode a NIP-19 entity or hex string and return its type plus decoded contents (such as the pubkey, event id, relays, or kind), so you can inspect what an npub, nsec, note, nprofile, nevent, or naddr refers to. |
postAnonymousNote |
name_restates_behavior no_return_description |
Publish a kind 1 note to relays under a freshly generated temporary keypair so the post is not linked to an existing account, returning the published event (including its id) and the temporary keypair used. Optionally attach tags or choose which relays to publish to. |
createKeypair |
no_return_description |
Generate a new Nostr keypair, returning the private and public keys in the requested format (hex only, npub only, or both, default both). Useful for creating throwaway accounts or testing without exposing your main keys. |
createProfile |
name_restates_behavior no_return_description |
Create and publish a kind 0 profile event for an account, signing with the provided private key, and return the published event (including its id). Optionally set the display name, about/bio, picture URL, NIP-05 identifier, lightning address (lud16/lud06), and website; choose relays to publish to. |
updateProfile |
name_restates_behavior no_return_description |
Publish an updated kind 0 profile event for an existing account, signing with the provided private key, and return the published event (including its id). Replaces the profile with the fields provided (name, about, picture, nip05, lud16, lud06, website); choose relays to publish to. |
postNote |
name_restates_behavior no_return_description |
Publish a kind 1 text note signed with an existing account's private key (authenticated posting), and return the published note event including its id and the relays it was sent to. Optionally attach tags or choose which relays to publish to. |
createNote |
name_restates_behavior no_return_description |
Create a new kind 1 note event (unsigned) from the provided content and optional tags, returning the event object ready to be signed and then published. Provide the author's private key and the note content. |
signNote |
name_restates_behavior no_return_description |
Sign an unsigned kind 1 note event with a private key, returning the fully signed event with its id and sig populated, ready to publish. Provide the private key and the unsigned note event (kind, content, tags, created_at, pubkey). |
publishNote |
name_restates_behavior no_return_description |
Publish an already-signed kind 1 note event to the specified Nostr relays (or a default set), returning a confirmation of where it was published. Provide the signed note (id, pubkey, created_at, kind, tags, content, sig); optionally choose the relays. |
createNostrEvent |
name_restates_behavior no_return_description |
Create an unsigned Nostr event of any kind, returning the event object ready to sign and publish. Requires the author pubkey (or a private key to derive it) and the event kind; optionally set content, tags, and created_at. |
signNostrEvent |
name_restates_behavior no_return_description |
Sign an unsigned Nostr event with a private key, returning the signed event including its id and sig, ready to publish. Provide the private key and the unsigned event's pubkey, created_at, kind, tags, and content. |
publishNostrEvent |
name_restates_behavior no_return_description |
Publish a signed Nostr event to the specified relays (or a default set), returning confirmation that it was accepted. Provide the signed event (with id and sig); optionally choose publish relays and authenticate to relays that require NIP-42. |
Selection evidence
Confusable tool pairs.
50 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.
| Tool A | Tool B | Confidence | Why they collide |
|---|---|---|---|
postAnonymousNote |
postNote |
high | Both post notes; a task like 'post this anonymously without using my key' vs 'post this note' could pick the wrong one if the agent doesn't notice the anonymous/dedicated-key distinction. |
sendDmNip04 |
sendDmNip44 |
medium | Both send encrypted DMs; 'send an encrypted DM' without specifying NIP leaves the choice ambiguous between NIP-04 and NIP-44/NIP-17. |
sendDmNip44 |
decryptDmNip44 |
medium | Both handle NIP-44 gift-wrapped DMs; a task like 'handle this DM' or 'process my inbox' could pick send instead of decrypt. |
getReceivedZaps |
getAllZaps |
medium | Both fetch zaps for a pubkey; 'show me the zaps for this user' is ambiguous between sent-only, received-only, or all. |
getSentZaps |
getAllZaps |
medium | Same ambiguity: 'get all zaps for this key' could be matched to getAllZaps or, if the agent only recalls a per-direction function, to getSentZaps. |
getReceivedZaps |
getSentZaps |
medium | The descriptions differ only by 'received by' vs 'sent by'; tasks like 'get the zaps of this user' don't make direction clear, though 'received'/'sent' wording helps disambiguate. |
getRelayList |
setRelayList |
low | Get vs set is usually explicit in tasks ('what relays does X use' vs 'publish my relay list'), so confusion is unlikely despite shared kind/token overlap. |
sendDmNip44 |
getDmInboxNip44 |
medium | Both relate to NIP-44 DMs and share gift-wrap/kind tokens; 'deal with my NIP-44 DMs' could route to reading the inbox instead of sending a message. |
createNostrEvent |
publishNostrEvent |
medium | Generic 'Nostr event' tasks ('create this event', 'publish this event') may pick the wrong pipeline stage; create takes content and produces unsigned, publish requires a signed event. |
createNostrEvent |
signNostrEvent |
medium | Both operate on unsigned events with keys; 'sign this event' and 'create this event' are plausibly conflated since create also accepts privateKey. |
decryptDmNip44 |
getDmInboxNip44 |
medium | Both decrypt NIP-44 DMs; 'decrypt my DMs' could route to getDmInboxNip44 (fetch+decrypt) instead of decryptDmNip44 for a specific event. |
decryptNip44 |
decryptDmNip44 |
medium | Both decrypt NIP-44 content; a task 'decrypt this NIP-44 message' is ambiguous between raw ciphertext (decryptNip44) and a gift-wrapped DM event (decryptDmNip44). |
getContactList |
getRelayList |
low | Both are 'get list' tools but for different resources (contacts vs relays); tasks usually name the entity, so confusion is unlikely. |
sendDmNip04 |
getDmConversationNip04 |
medium | Both concern NIP-04 DMs; 'handle my NIP-04 DM conversation' could pick send vs fetch-the-conversation. |
signNostrEvent |
publishNostrEvent |
low | Sign vs publish are distinct pipeline steps explicit in typical tasks; shared 'event/nostr' tokens alone are not enough. |
getKind1Notes |
getLongFormNotes |
medium | Tasks like 'get this user's notes' don't specify kind; agent could pick kind 1 notes when long-form or both were intended. |
getDmConversationNip04 |
getDmInboxNip44 |
medium | Both fetch/decrypt DM conversations but under different NIPs; a task 'get my DMs' is ambiguous regarding NIP-04 vs NIP-44, though NIP is usually stated. |
encryptNip04 |
decryptNip04 |
low | Encrypt vs decrypt tasks are explicit; cipher or plaintext orientation in the input makes the choice clear. |
encryptNip44 |
decryptNip44 |
low | Same as NIP-04: encrypt/decrypt wording in the task resolves ambiguity. |
follow |
unfollow |
low | Follow and unfollow are directly requested in natural-language tasks; semantic opposites are reliably distinguished. |
createProfile |
createNote |
medium | 'Create a profile/note' are distinct nouns, but both are 'create a kind event with content'; a generic 'create a profile post' or 'create a new kind-0 note' could misfire. |
createProfile |
updateProfile |
medium | Both write kind 0 profiles with identical schemas; 'set up my profile' versus 'change my profile' can be ambiguous about create vs update. |
reactToEvent |
repostEvent |
low | Reaction (kind 7) vs repost (kind 6) are clearly different user intents; only fail on a vague phrase like 'interact with this event'. |
publishNote |
publishNostrEvent |
low | Both publish signed events but one is note-specific; tasks usually say 'note' vs generic 'event', so confusion is unlikely. |
signNote |
signNostrEvent |
low | Signing a note vs an arbitrary event is distinguished by 'note' in the task; low risk. |
getContactList |
getFollowing |
low | getFollowing is explicitly documented as an alias of getContactList, so either choice yields the same result. |
convertNip19 |
analyzeNip19 |
medium | Both handle any NIP-19 entity; a task like 'tell me about this npub' could route to analyze, while 'convert this' is mostly explicit. |
createKeypair |
createNote |
low | 'Create a keypair' vs 'create a note' are explicit in the noun; low ambiguity. |
createKeypair |
createProfile |
low | Different artifacts (keys vs profile event); task wording distinguishes them. |
createNote |
publishNote |
medium | Both create/publish a kind-1 note with same content params; a task 'create a note and post it' could stop at createNote (unsigned) instead of publishNote. |
createNote |
signNote |
medium | Both take an unsigned note and a key; 'create the note event' vs 'sign the note' stages may be conflated. |
decryptNip04 |
decryptNip44 |
low | NIP-04 vs NIP-44 is typically mentioned in the task or implied by the ciphertext format; decrypt of raw payload is unambiguous directionally. |
deleteEvent |
replyToEvent |
low | Delete vs reply are distinct actions with different requirements; low ambiguity. |
encryptNip04 |
encryptNip44 |
low | Encrypt direction is explicit; only the NIP version may be unstated, which is usually specified. |
getProfile |
createProfile |
low | Get vs create profile is explicit; task verbs disambiguate. |
getProfile |
getFollowing |
low | One tool returns profile metadata, the other following lists; task nouns distinguish them. |
getProfile |
updateProfile |
low | Read vs write profile is explicit in task language. |
postNote |
publishNote |
medium | Both get a note onto relays but differ in input (privateKey+content vs signedNote); 'publish my note' could pick postNote (which also publishes) rather than publishNote. |
postNote |
signNote |
low | Posting vs signing are distinct stages; 'sign and post' tasks usually list both. |
queryEvents |
deleteEvent |
low | Query vs delete are opposite actions; low ambiguity. |
queryEvents |
reactToEvent |
low | Read vs react; distinct intents. |
queryEvents |
replyToEvent |
low | Read vs reply; distinct intents. |
queryEvents |
repostEvent |
low | Read vs repost; distinct intents. |
reactToEvent |
deleteEvent |
low | Distinct actions; low ambiguity. |
reactToEvent |
replyToEvent |
low | React vs reply are distinct intents; only 'respond to this note' could be ambiguous, which is rare. |
repostEvent |
deleteEvent |
low | Distinct actions; low ambiguity. |
repostEvent |
replyToEvent |
low | Distinct actions; low ambiguity. |
sendDmNip04 |
decryptDmNip44 |
low | Send vs decrypt, and NIP-04 vs NIP-44, are both explicit distinctions in the task. |
signNote |
publishNote |
low | Sign vs publish stages are explicit; inputs differ (unsigned vs signed). |
getProfile |
getReceivedZaps |
low | Profile vs zaps are different entity types; task nouns disambiguate. |
Compare the field