docs(nips): add NIP-AM draft for durable agent turn metrics

Defines kind:44200 — a regular stored event, one per completed agent turn,
NIP-44 encrypted agent-to-owner and #p-gated like NIP-AO telemetry, carrying
per-turn and cumulative token/cost usage so owners can account for agent
token consumption across harnesses without relaying transcript content.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw
2026-07-01 16:21:02 -04:00
co-authored by Will Pfleger
parent b1d9d955de
commit a44dde01e0
+190
View File
@@ -0,0 +1,190 @@
NIP-AM
======
Agent Turn Metrics
------------------
`draft` `optional` `relay`
This NIP defines a durable, encrypted event kind for recording per-turn token
usage and estimated cost of AI agent sessions. An agent publishes one
`kind:44200` event per completed turn, NIP-44 encrypted to its owner, so the
owner can account for token usage across agents and harnesses without the
relay — or any third party — learning what the agent did or what it cost.
## Motivation
AI agent harnesses consume model tokens on every turn. Owners running fleets
of agents need durable, harness-independent usage accounting — the equivalent
of a metered bill — for cost attribution, budgeting, and capacity planning.
[NIP-AO](NIP-AO.md) (kind 24200) already streams encrypted session telemetry
between agent and owner, but it is deliberately ephemeral: relays MUST NOT
persist it, so it cannot answer "how many tokens did my agents use last
week?". Transcript-grade durable telemetry is explicitly out of scope — the
persistence-averse reasoning behind NIP-AO's ephemerality contract applies to
conversation content, not to a small usage record. Kind 44200 stores only the
metric: token counts, an estimated cost, and correlation identifiers, all
encrypted to the owner.
## Definitions
- **Agent**: an AI process with its own Nostr keypair, executing sessions on
behalf of an owner.
- **Owner**: the human (or system) whose pubkey the agent was provisioned under.
- **Turn**: one prompt→response cycle of an agent session, as bounded by the
harness (e.g. one ACP `session/prompt` round trip).
- **Turn metric**: a single kind 44200 event recording the usage of one turn.
## Event
`kind:44200` is a regular event as defined in [NIP-01](01.md): stored,
append-only, never replaced. Each completed turn produces exactly one event.
```json
{
"kind": 44200,
"pubkey": "<agent_pubkey>",
"created_at": <unix_timestamp>,
"content": "<NIP-44 v2 ciphertext>",
"tags": [
["p", "<owner_pubkey>"],
["agent", "<agent_pubkey>"]
],
"sig": "..."
}
```
Events MUST have exactly one `p` tag (the owner) and exactly one `agent` tag
(equal to `pubkey`). The tag layout deliberately mirrors NIP-AO telemetry
frames so existing owner-scoped tooling applies unchanged.
No channel (`h`) tag is used. The channel a turn served is private usage
metadata and lives inside the encrypted payload; keeping it out of the tags
avoids leaking per-channel activity rates to the relay operator and keeps the
event community-global (owner-scoped) rather than channel-scoped.
## Encryption
`content` MUST be encrypted with NIP-44 v2 using `(agent_privkey,
owner_pubkey)` — identical to NIP-AO telemetry. Plaintext SHOULD be zeroized
after encrypt/decrypt. Decrypted payload MUST NOT exceed 65,535 bytes
(payloads are typically well under 1 KB).
## Decrypted Payload
The `content` field decrypts to a UTF-8 JSON object:
```jsonc
{
"harness": "goose", // REQUIRED: harness identifier
"model": "claude-sonnet-4-5", // model id, or null if unknown
"channelId": "<channel_uuid>" | null,
"sessionId": "<session_id>" | null,
"turnId": "<turn_id>" | null,
"timestamp": "2026-07-01T20:11:03.213Z", // REQUIRED: RFC 3339, end of turn
// Usage for THIS turn (computed delta). Fields are null when the harness
// does not report them — a null MUST NOT be recorded or summed as zero.
"turn": {
"inputTokens": 1234 | null,
"outputTokens": 567 | null,
"totalTokens": 1801 | null,
"costUsd": 0.0123 | null // estimated
},
// Session-cumulative usage as reported at the end of this turn.
"cumulative": {
"inputTokens": 45210 | null,
"outputTokens": 9876 | null,
"totalTokens": 55086 | null,
"costUsd": 0.41 | null // estimated
},
// false when the publisher could not observe the previous turn's
// cumulative baseline (e.g. harness restart mid-session), making the
// "turn" object unreliable for this event.
"deltaReliable": true,
"stopReason": "end_turn" // optional
}
```
`harness` and `timestamp` are REQUIRED. All other fields are OPTIONAL or
nullable. Consumers MUST ignore unknown fields (forward compatibility).
Where the harness reports only cumulative counters, the publisher computes
`turn` as the difference between consecutive cumulative snapshots within one
session. Publishers MUST set `deltaReliable: false` when the baseline is
unknown; consumers doing exact accounting SHOULD prefer recomputing deltas
from consecutive `cumulative` values and treat `turn` as a convenience.
`costUsd` values are estimates (provider list prices at publish time, or a
harness-reported estimate). They are advisory, not billing records.
`stopReason`, when present, MUST be one of `end_turn`, `max_tokens`,
`cancelled`, `error`, `unknown`. Consumers MUST treat unrecognized
`stopReason` values as `unknown`; the token counts remain valid.
## Publisher Behavior
- Publish exactly one event per completed turn, at turn completion, including
turns that end in cancellation or error when usage was observed.
- Do NOT publish an event for a turn with no observed usage (all counters
unknown); an all-null metric carries no information.
- `created_at` SHOULD equal the payload `timestamp` truncated to seconds.
## Relay Behavior
On receiving a kind 44200 event, a relay MUST:
1. Validate the event signature per NIP-01.
2. Verify `event.pubkey` equals the `agent` tag and that
`is_agent_owner(agent, owner)` holds for the `p` tag via authenticated
ownership lookup. Tag matching alone is insufficient.
3. Store the event durably, scoped to the owner (community-global; no channel
scope).
4. NOT index the event in any full-text search (the ciphertext is not
searchable and must not enter search indexes).
Reads MUST be gated: only an authenticated ([NIP-42](42.md)) reader whose
pubkey equals the `#p` tag value may receive the event. Unauthorized publish
or subscribe attempts MUST be rejected with `AUTH required`.
Relays SHOULD rate-limit kind 44200 to a rate consistent with real turn
frequency (RECOMMENDED: 60 events/minute per agent pubkey).
## Client Behavior
Owners recover usage history with:
```json
{"kinds": [44200], "#p": ["<own_pubkey>"], "since": <window_start>}
```
On receiving an event, a client MUST verify the signature, decrypt with its
own secret key and `event.pubkey`, and ignore events that fail to decrypt or
parse. Clients SHOULD deduplicate by event id and sort by `created_at`.
## Relationship to Other NIPs
- [NIP-AO](NIP-AO.md): same agent↔owner encryption and tag scoping, but
ephemeral and transcript-grade. NIP-AM events MUST NOT carry conversation
content, tool calls, or protocol frames — usage numbers and identifiers only.
- [NIP-09](09.md): the authoring agent (or its owner via relay policy) may
request deletion; relays apply standard deletion semantics.
- [NIP-40](40.md): publishers MAY set `expiration` to bound retention.
## Security Considerations
**Metadata leakage.** `p`, `agent`, and `created_at` are cleartext: a relay
operator learns that agent X completed turns for owner Y at some rate. Turn
rate is already observable from the agent's channel messages; the token
counts, cost, model, and channel remain encrypted.
**No forward secrecy.** NIP-44 does not provide forward secrecy; compromise
of the agent's private key allows decryption of captured ciphertexts.
**Integrity of accounting.** Metrics are self-reported by the agent process.
A compromised agent can under- or over-report. Owners requiring stronger
guarantees must reconcile against provider-side billing.