AI Agent Communication
NIP-XX
AI Agent Messages
draft optional
This NIP defines a protocol for bidirectional communication between Nostr clients and AI agent runtimes over Nostr relays.
Until a NIP number is assigned, this document uses placeholder number XX in the title and filename.
Kinds
This NIP reserves the following kinds for AI Agent communication:
eryoung.com. | ai.info | No |
Prompt events (25802), terminal events (25803, 25805), and AI info events (31340) are non-ephemeral, allowing durable replay and capability discovery for state restoration and audit. Streaming/tooling telemetry (25800, 25801, 25804, 25806) remains ephemeral.
Rationale
Nostr has emerged as a universal transport for decentralized applications. AI agents are increasingly being deployed as networked services, and this protocol adds messaging shape for interactive, sessioned, streaming agent workflows.
- Decentralized identity: Agents and clients identify via Nostr pubkeys.
- Interactive sessions: Support multi-turn workflows with optional session grouping.
- Streaming: Delta events let clients render partial output.
- Tool telemetry: Agents can expose tool activity.
- Structured discovery:
ai.infoadvertises capabilities, limits, and supported formats.
Definitions
Run
A run is a single prompt/response interaction.
- One prompt event (
ai.prompt, kind25802). - Optional cancellation request (
ai.cancel, kind25806) from client while non-terminal. - Optional status (
ai.status, kind25800) and delta (ai.delta, kind25801) events. - Optional tool-call events (
ai.tool_call, kind25804). - Exactly one terminal event:
ai.response(25803) orai.error(25805).
The run identifier is the prompt event id (hex string). Non-prompt events MUST reference it in an e tag with marker root.
Session
A session groups related runs.
- The
stag identifies a session. sis optional on protocol events.- If
sis omitted, recipients SHOULD usesender:<lowercase-hex-pubkey>. - The default is deterministic and can be reproduced by all parties.
Clients MAY also use ["s","session:<opaque-hash>"] for higher-level session IDs.
Actors
- Client: User-facing app that sends prompts and renders agent output.
- Agent Runtime: Nostr-native service that processes prompts.
- Relays: Transport/storage layer.
Encryption
All protocol payloads in encrypted event kinds (25800, 25801, 25802, 25803, 25804, 25805, 25806) MUST use [NIP-44](44.md).
Required tag
All encrypted events in this NIP MUST include:
["encryption", "nip44"]If an agent and client both publish additional supported encryption schemes, senders MUST choose a scheme supported by both sides. If no overlap exists, implementations MUST fail the request with UNSUPPORTED_ENCRYPTION.
Key agreement
Encryption uses the sender’s private key and recipient’s public key. In nostr-tools this is:
const conversationKey = nip44.utils.getConversationKey({
privateKey: senderPrivateKey,
publicKey: recipientPublicKey
})
const encrypted = nip44.encrypt(plaintext, conversationKey)The plaintext must be JSON with a ver field.
Each message MUST use a fresh nonce as defined by NIP-44.
NIP-59 metadata privacy (optional)
NIP-44 encrypts content, but event metadata remains visible: authors, pubkeys, tags, timestamps, and kind.
Clients and runtimes MAY wrap these events using [NIP-59](59.md) for metadata privacy. When gift-wrapped:
- routing SHOULD use the outer wrapper’s
ptag andkindsfilters; - inner tags are not intended for relay indexing;
- clients still need to unwrap before applying payload validation.
This NIP defines two privacy profiles:
- default: NIP-44 encryption only.
- privacy-first: NIP-44 + NIP-59 wrapping + minimal disclosure in tool output.
If wrapped, tool metadata in ai.tool_call should avoid sensitive argument/output fields.
Event Formats
All fields marked required in tables and schemas below MUST be present.
For any event type where a field is repeated in both encrypted content and a tag (tool, phase, etc.), the encrypted content is the canonical source of truth. If a mirror tag is present and differs from the encrypted payload, implementations MAY reject the event as INVALID_SCHEMA.
Structured text envelope (compatibility profile)
For compatibility with runtimes that currently emit telemetry as a compact text payload, implementations MAY encode structured metadata inside text using this format:
- delimiter between fields:
| - field shape:
key=value - value encoding: percent-encoding (
encodeURIComponent/ URL-encoding)
Example:
event=tool|phase=start|name=web_fetch|call_id=call_123|target=https%3A%2F%2Fexample.com|ts=1771402416When this envelope is used:
event,phase, andtsMUST be present.- consumers SHOULD ignore unknown keys.
- producers SHOULD keep key names stable and lowercase snake_case.
- if canonical JSON fields are present (for example
name,phase,arguments), they
remain authoritative over mirrored envelope keys.
Per-event required envelope keys:
ai.response(25803):event=final,phase=end,textai.delta(25801):event=delta,phase, and at least one oftextorblockai.tool_call(25804):event=tool,phase, andtextornameai.error(25805):event=error,phase=end,code,text
Recommended common keys for all envelope payloads:
run_id(orrunId)session_id(orsessionId)timestamp(orts)
Canonical keys SHOULD use snake_case (run_id, session_id, ts). Implementations SHOULD also accept camelCase aliases for interoperability.
This format is intended as a wire-compatibility strategy and does not replace canonical JSON fields when those fields are available.
ai.info (kind 31340)
Agent capability discovery event. This is a replaceable event (kind 31340). Agents SHOULD publish this in their pubkey namespace.
Tags
| Tag | Required | Description |
|---|---|---|
| d | Yes | Fixed identifier, e.g. "agent-info" |
Agents SHOULD keep exactly one active d value and SHOULD not change it after first publication, so clients can reliably cache capabilities.
Clients SHOULD cache the newest valid ai.info publication by (created_at, id) and refresh cached capabilities when capability entries change.
Content (JSON, unencrypted)
{
"ver": 1,
"supports_streaming": true,
"supports_nip59": true,
"dvm_compatible": false,
"encryption": ["nip44"],
"supported_models": ["gpt-4.1-mini", "llama-3.1-70b"],
"default_model": "gpt-4.1-mini",
"tool_names": ["web_fetch", "calculator"],
"tool_schema_version": 1,
"max_prompt_bytes": 32000,
"max_context_tokens": 128000,
"tool_schemas": {
"calculator": {
"schema_version": 1,
"description": "Evaluate arithmetic expressions",
"requires_approval": false,
"input_schema": {
"type": "object",
"properties": {
"expr": { "type": "string" },
"precision": { "type": "number" }
},
"required": ["expr"]
}
}
},
"pricing_hints": {
"currency": "USD",
"per_1k_prompt_tokens": 0.002,
"per_1k_output_tokens": 0.004
}
}Model and schema negotiation
Clients SHOULD use ai.info before sending prompts. For each prompt:
- If
modelis omitted, agents SHOULD usedefault_model. - If
modelis set, the agent MUST have it listed insupported_models. - If the sender supplies
tool_schema_version, the agent MUST use that exact version. - If the sender omits
tool_schema_version, agents MUST usetool_schema_versionfrom
their latest ai.info.
- If no compatible model/schema is advertised/supported, the agent MUST return an
ai.error with:
UNSUPPORTED_MODELwhen the requested model is unknown.UNSUPPORTED_SCHEMA_VERSIONwhen the requestedtool_schema_versionis
incompatible. and MUST NOT continue execution.
The tool_schema_version requested in a prompt binds accepted ai.tool_call JSON shapes for that run.
Prompt (kind 25802)
Client → agent invocation.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Agent recipient pubkey |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"message": "user's message text",
"thinking": "low|medium|high|max",
"provider": "optional provider identifier",
"model": "optional model name",
"tool_schema_version": 1,
"fallback_models": ["list", "of", "fallbacks"]
}Cancel (kind 25806)
Client → agent cancellation request.
Clients MAY emit this if they lose UI interest in a run. Agents SHOULD treat ai.cancel as idempotent for the same run (p + e). Agents MUST ignore ai.cancel for completed runs and MUST NOT emit additional terminal events in that case.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Agent recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"reason": "user_cancel|timeout|policy"
}Response (kind 25803)
Agent → client terminal response.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Client recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"text": "complete agent response",
"timestamp": 1710000000,
"usage": {
"input_tokens": 100,
"output_tokens": 250
}
}text MAY be plain response text, or MAY carry a compatibility envelope string such as:
event=final|phase=end|text=Here%20is%20the%20answer...|finish_reason=stop|run_id=abc123|session_id=sender%3Aabc|ts=1771402425Delta (kind 25801)
Agent → client streaming fragment.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Client recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"text": "partial response text",
"seq": 0
}text MAY alternatively carry a compatibility envelope string such as:
event=delta|mode=thinking|status=update|phase=update|block=Looking%20up%20latest%20pricing|text=Looking%20up%20latest%20pricing|run_id=abc123|session_id=sender%3Aabc|ts=1771402418If seq is present, it MUST be strictly increasing by 1 within a run.
Status (kind 25800)
Agent → client state updates.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Client recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"state": "thinking|tool_use|done",
"progress": 50,
"info": "additional status info"
}Tool Call (kind 25804)
Agent tool-call telemetry. Agents own tool execution.
For this section, payload fields are canonical (name and phase), and tool and phase tags are optional index hints only.
If any optional hint tag is present, it SHOULD match the encrypted payload field. A mismatch MAY be treated as INVALID_SCHEMA.
To reduce telemetry leakage, agents SHOULD avoid including sensitive data in output unless strictly required for user intent; clients SHOULD treat tool output as potentially untrusted and avoid surfacing secrets.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Client recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
| tool | No | Optional index hint |
| phase | No | Optional index hint: start/result |
Content (JSON, encrypted)
{
"ver": 1,
"name": "calculator",
"phase": "start|result",
"arguments": {
"expr": "12 * 7"
},
"output": {
"stdout": "84",
"stderr": "",
"exit_code": 0
},
"success": true,
"duration_ms": 120
}Compatibility envelope form inside text is also allowed, for example:
{
"ver": 1,
"text": "event=tool|phase=start|name=web_fetch|call_id=call_123|target=https%3A%2F%2Fexample.com|ts=1771402416",
"timestamp": 1771402416
}Error (kind 25805)
Terminal failure event.
Tags
| Tag | Required | Description |
|---|---|---|
| p | Yes | Client recipient pubkey |
| e | Yes | Prompt id (#e root) |
| encryption | Yes | Must be "nip44" |
| s | No | Session identifier |
Content (JSON, encrypted)
{
"ver": 1,
"code": "RATE_LIMIT",
"message": "provider unavailable",
"retry_after": 30,
"details": {
"provider": "provider-id"
}
}Error codes:
| Code | Meaning |
|---|---|
| UNSUPPORTED_ENCRYPTION | Requested encryption scheme unsupported |
| UNSUPPORTED_MODEL | Requested model unavailable |
| UNSUPPORTEDSCHEMAVERSION | Requested tool schema version unsupported |
| CANCELLED | Run cancelled |
| RATE_LIMIT | Request throttled |
| UNAUTHORIZED | Sender or agent unauthorized |
| BLOCKED_SENDER | Sender blocked by policy |
| MODEL_UNAVAILABLE | Requested model/provider unavailable |
| SESSION_LIMIT | Session turns exceeded |
| PARSE_ERROR | Prompt payload invalid |
| EMPTY_RESPONSE | No response produced |
| TOOL_ERROR | Tool execution failed |
| INVALID_SCHEMA | Payload failed schema validation |
| UNSUPPORTED_FEATURE | Requested feature/tool/scheme unsupported |
| INVALID_SEQUENCE | Delta sequence invalid for this run |
| INTERNAL_ERROR | Unexpected runtime failure |
JSON Schema
Common fields
{
"$id": "https://example.com/nip-xx-agent-message.json",
"type": "object",
"required": ["ver"],
"properties": {
"ver": { "const": 1 }
},
"additionalProperties": true
}Prompt schema (25802)
{
"$id": "https://example.com/nip-xx-prompt.json",
"type": "object",
"required": ["ver", "message"],
"properties": {
"ver": { "const": 1 },
"message": { "type": "string", "minLength": 1 },
"thinking": { "type": "string", "enum": ["low", "medium", "high", "max"] },
"provider": { "type": "string", "minLength": 1 },
"model": { "type": "string", "minLength": 1 },
"tool_schema_version": { "type": "integer", "minimum": 1 },
"fallback_models": {
"type": "array",
"items": { "type": "string" }
}
}
}Cancel schema (25806)
{
"$id": "https://example.com/nip-xx-cancel.json",
"type": "object",
"required": ["ver", "reason"],
"properties": {
"ver": { "const": 1 },
"reason": { "type": "string", "enum": ["user_cancel", "timeout", "policy"] }
}
}Response schema (25803)
{
"$id": "https://example.com/nip-xx-response.json",
"type": "object",
"required": ["ver", "text"],
"properties": {
"ver": { "const": 1 },
"text": { "type": "string" },
"timestamp": { "type": "integer", "minimum": 0 },
"usage": {
"type": "object",
"properties": {
"input_tokens": { "type": "integer", "minimum": 0 },
"output_tokens": { "type": "integer", "minimum": 0 }
},
"required": ["input_tokens", "output_tokens"]
}
}
}Delta schema (25801)
{
"$id": "https://example.com/nip-xx-delta.json",
"type": "object",
"required": ["ver", "text"],
"properties": {
"ver": { "const": 1 },
"text": { "type": "string" },
"seq": { "type": "integer", "minimum": 0 },
"timestamp": { "type": "integer", "minimum": 0 }
}
}Status schema (25800)
{
"$id": "https://example.com/nip-xx-status.json",
"type": "object",
"required": ["ver", "state"],
"properties": {
"ver": { "const": 1 },
"state": { "type": "string", "enum": ["thinking", "tool_use", "done"] },
"progress": { "type": "integer", "minimum": 0, "maximum": 100 },
"info": { "type": "string" }
}
}Tool-call schema (25804)
{
"$id": "https://example.com/nip-xx-tool-call.json",
"type": "object",
"required": ["ver"],
"oneOf": [
{
"required": ["text"]
},
{
"required": ["name", "phase"]
}
],
"properties": {
"ver": { "const": 1 },
"text": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1 },
"phase": { "type": "string", "enum": ["start", "result"] },
"arguments": { "type": "object" },
"output": { "type": "object" },
"success": { "type": "boolean" },
"duration_ms": { "type": "integer", "minimum": 0 },
"timestamp": { "type": "integer", "minimum": 0 }
}
}Error schema (25805)
{
"$id": "https://example.com/nip-xx-error.json",
"type": "object",
"required": ["ver", "code", "message"],
"properties": {
"ver": { "const": 1 },
"code": {
"type": "string",
"enum": [
"UNSUPPORTED_ENCRYPTION",
"UNSUPPORTED_MODEL",
"UNSUPPORTED_SCHEMA_VERSION",
"CANCELLED",
"RATE_LIMIT",
"UNAUTHORIZED",
"BLOCKED_SENDER",
"MODEL_UNAVAILABLE",
"SESSION_LIMIT",
"PARSE_ERROR",
"EMPTY_RESPONSE",
"TOOL_ERROR",
"INVALID_SCHEMA",
"UNSUPPORTED_FEATURE",
"INVALID_SEQUENCE",
"INTERNAL_ERROR"
]
},
"message": { "type": "string", "minLength": 1 },
"retry_after": { "type": "integer", "minimum": 1 },
"details": { "type": "object" }
}
}Info schema (31340)
{
"$id": "https://example.com/nip-xx-info.json",
"type": "object",
"required": ["ver", "encryption", "tool_names"],
"properties": {
"ver": { "const": 1 },
"supports_streaming": { "type": "boolean" },
"supports_nip59": { "type": "boolean" },
"dvm_compatible": { "type": "boolean" },
"encryption": {
"type": "array",
"items": { "type": "string" },
"contains": { "const": "nip44" }
},
"supported_models": { "type": "array", "items": { "type": "string" } },
"default_model": { "type": "string" },
"tool_names": { "type": "array", "items": { "type": "string" } },
"tool_schema_version": { "type": "integer", "minimum": 1 },
"tool_schemas": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": ["schema_version", "description", "input_schema"],
"properties": {
"schema_version": { "type": "integer", "minimum": 1 },
"description": { "type": "string" },
"requires_approval": { "type": "boolean" },
"input_schema": { "type": "object" },
"output_schema": { "type": "object" }
}
}
},
"max_prompt_bytes": { "type": "integer", "minimum": 1 },
"max_context_tokens": { "type": "integer", "minimum": 1 },
"pricing_hints": { "type": "object" }
}
}Validation and failure rules
Implementations MUST follow these validation and failure rules:
- JSON parse failures in encrypted payloads MUST be reported with
code = PARSE_ERROR. - Missing required tags (
p,ewhere required,encryption) or malformed tag values MUST be treated asINVALID_SCHEMA. Thedtag is required only forai.info(31340). - Invalid protocol content (for example unknown
state/thinking/code) MUST be treated as
INVALID_SCHEMA.
etags on non-prompt events MUST reference an existing or referenced prompt event id.- Non-
ai.promptevents MUST requireemarkerroot. ai.deltawith non-monotonicseqfor the same run MUST be treated as
INVALID_SEQUENCE when seq is present.
ai.cancelwithout matching active run MUST be ignored.- Duplicate
ai.cancelfor unfinished runs SHOULD be treated as idempotent and MUST NOT
change accepted terminal selection.
- Clients SHOULD ignore
ai.status,ai.delta, andai.tool_callfor runs already in a
terminal state, except when retained for audit/debug tooling.
- Agents SHOULD emit at most one terminal
ai.errorwithcode=CANCELLEDfor a given run. - If a run reaches a terminal state and emits additional terminal events, clients MUST keep
only the terminal event with the highest created_at and highest id as tie-breaker.
- If a run is missing a terminal response after reasonable timeout, clients MAY treat it as
an incomplete run and surface an implementation-specific state.
Streaming and reconciliation
For ai.delta events (kind 25801):
- Clients MUST ignore deltas where
(e, p, encryption)do not match the subscribed run and
recipient.
- If
seqis present, it MUST be contiguous starting at0. - Clients SHOULD collect deltas and order by
(seq, created_at, id)whenseqis present, otherwise by(created_at, id). - Clients MUST dedupe duplicates by
(event.id)and SHOULD dedupe identical(seq, text)tuples whenseqis present. - If a gap is detected (missing
seq), clients SHOULD continue best-effort rendering and
can display a soft placeholder (“streaming degraded”) until ai.response arrives.
- Final render MUST be taken from
ai.responsetext, not from the delta stream. - Clients MUST NOT apply deltas after a terminal event has been accepted for a run.
Envelope parsing helper (non-normative)
function parseTextEnvelope(text: string): Record<string, string> {
const out: Record<string, string> = {};
for (const part of text.split("|")) {
const idx = part.indexOf("=");
if (idx <= 0) continue;
const key = part.slice(0, idx).trim();
const value = part.slice(idx + 1);
out[key] = decodeURIComponent(value);
}
return out;
}Protocol Flow
- Client SHOULD read
ai.infofor capabilities:
``text {"kinds":[31340],"authors":["<agent-pubkey>"]} ` If no ai.info` exists, clients SHOULD:
- proceed with
supports_streaming = true - assume
encryption = ["nip44"] - assume
tool_names = [] - disable tool-related UI
- If
supports_nip59is true and stronger privacy is desired, client and agent SHOULD
use NIP-59 wrappers.
- Client sends
25802prompt: p= agent pubkey- optional
s - optional
modelandtool_schema_versionmatchingai.info encryption = nip44- encrypted JSON payload
- Agent subscribes to prompts:
``text {"kinds":[25802],"#p":["<agent-pubkey>"]} ``
- Agent emits
25800,25801, and optional25804. - Client MAY cancel a non-terminal run by emitting
25806(same prompt id ine). - If cancellation is effective before terminal output, agent SHOULD emit
25805with
code = CANCELLED.
- Agent emits terminal event:
25803on success25805on failure
If a cancellation arrives after a terminal event has already been emitted, the cancellation MUST be treated as no-op.
- Client subscribes to terminal and streaming events:
``text { "kinds": [25800,25801,25803,25804,25805], "#p": ["<client-pubkey>"], "#e": ["<prompt-event-id>"], "authors": ["<agent-pubkey>"] } ` Clients SHOULD additionally enforce run-local ordering by matching the selected #e value and authors` set.
Session recovery recipe
- Find active session:
``text {"kinds":[25802],"#s":["sender:<client-pubkey>"],"#p":["<agent-pubkey>"]} ``
- Resume a run:
``text {"#e":["<prompt-id>"], "authors":["<agent-pubkey>"], "kinds":[25800,25801,25803,25804,25805]} ``
- Latest terminal event should be taken as the current run state; deltas may be stale on relay replay.
Tool model and security
This NIP defines a runtime-exec model:
- Agents own tool execution and emit
ai.tool_calltelemetry only. - Clients MUST NOT execute tools based on
ai.tool_call. - Tool schemas advertised in
ai.infolet clients present expected UI affordances. - Agents MUST negotiate
modelandtool_schema_version; if unsupported, MUST return
UNSUPPORTED_MODEL or UNSUPPORTED_SCHEMA_VERSION.
Security and abuse controls
- Agents SHOULD reject unknown senders not in allowlists and return
UNAUTHORIZED. - Agents SHOULD reject senders blocked by policy and return
BLOCKED_SENDER. - Agents SHOULD reject unsupported tools and return
UNSUPPORTED_FEATURE. - Agents SHOULD validate schemas before execution and return
INVALID_SCHEMAon mismatch. - Agents SHOULD map execution or transport throttling to
RATE_LIMIT. - Agents SHOULD return
UNSUPPORTED_ENCRYPTIONwhen the requested scheme is unsupported. retry_afterSHOULD be present for transient failures where retry is useful.- Clients SHOULD prefer the newest terminal event by
(created_at,id)when multiple terminal
events are observed.
Full example (encrypted payloads shown as placeholders)
Prompt (25802, from client A to agent B)
{
"kind": 25802,
"pubkey": "A",
"tags": [
["p","B"],
["s","sender:A"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Status (25800)
{
"kind": 25800,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["s","sender:A"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Delta (25801, seq 0)
{
"kind": 25801,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Delta (25801, seq 1)
{
"kind": 25801,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["s","sender:A"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Tool call telemetry (25804)
{
"kind": 25804,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["tool","calculator"],
["phase","start"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Cancel request (25806)
{
"kind": 25806,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["s","sender:A"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}
Response (25803)
{
"kind": 25803,
"tags": [
["p","A"],
["e","d5f...3a","", "root"],
["encryption","nip44"]
],
"content": "<nip44-ciphertext>"
}Conformance examples
- Invalid schema:
ai.promptwithmessagemissing MUST be rejected and MAY
return ai.error with INVALID_SCHEMA.
- Unknown tool:
tool_call.namenot inai.info.tool_namesSHOULD be treated as
unsupported and MAY return UNSUPPORTED_FEATURE.
- Out-of-order deltas: clients reorder by
seqas described in reconciliation. - Encryption mismatch: noncompliant
encryptiontag content MUST be rejected as
UNSUPPORTED_ENCRYPTION or INVALID_SCHEMA.
- Duplicate terminal events: clients MUST dedupe by
(created_at,id)and render with
only the newest terminal event.
- Cancel path: client can cancel a run by
25806and should treat subsequent
25805 with code = CANCELLED as expected terminal completion.
- Response/cancel race: if both
25803and25805for the same run exist, clients
MUST keep the terminal event with highest (created_at,id) and apply normal ordering.
Backward Compatibility
Implementations MUST:
- Ignore unknown encrypted JSON fields.
- Use canonical fields.
- Avoid field duplication for historical aliases in future versions.
- Keep
soptional and default tosender:<lowercase-hex-pubkey>when absent.
This document defines ver: 1; field names and meanings MUST NOT be redefined in the same major version.
Implementations
- openclaw: Open PR adding support to OpenClaw:
- PR: https://github.com/joelklabo/openclaw/pull/2
- clawlet: Web client (Next.js) with (ZeroClaw) Rust runtime
- Web: https://github.com/joelklabo/clawlet
Copyright
This document is placed in the public domain.