feat(nip-ad): agent disposition ledger (kind:44300) - #6148
Draft
MajorTal wants to merge 1 commit into
Draft
Conversation
MajorTal
force-pushed
the
claude/agent-disposition-ledger
branch
from
August 17, 2026 18:41
782e5af to
4560f59
Compare
MajorTal
marked this pull request as draft
August 19, 2026 07:11
A signed, durable, plaintext record of how an agent resolved each request directed at it, so any authorized reader of a channel can verify that a request was answered and read why it was refused — without parsing chat prose or trusting unverifiable claims. Implements block#3091. An **obligation** is one marked request naming exactly one target agent that is also `p`-mentioned. That is the unit of accounting, because a disposition is only meaningful if "which obligation does this discharge, and who was obliged?" has exactly one answer. Requests naming several agents are reported as unsupported rather than given a state either agent could discharge. A disposition **binds** only when its `e`/`h`/`p` tags match the obligation and its signer is the target agent. The relay validates structure; binding is consumer-side, so every consumer shares one verifier. Without it, any channel member — including a merely `p`-mentioned human — could close an agent's obligation with a signed event. Four states. `completed` and `refused` are **terminal and absorbing**: a later weaker observation is recorded as a warning, never a reopening, so "terminal" means terminal. `responded` and `errored` leave the obligation open. **The harness cannot emit `completed`.** It observes that a turn ended, not that the work was done — a clean `EndTurn` covers asking a clarifying question or answering one message of a batch just as readily as finishing the job. `TurnOutcome` has three variants, so the type system enforces this rather than discipline. `completed` comes only from the target agent's own signed assertion via `buzz dispositions emit`, which refuses a signer that is not the obligation's target. Malformed and unsupported requests are reported separately from unanswered ones: filing a client bug as an agent's gap manufactures a gap no agent can ever clear. Coverage is two-sided — a clean channel claim needs both the request set and the disposition histories paginated to exhaustion, because missing one disposition page can hide a `refused` that turns "settled" into "disputed". `validate_disposition_event` in `buzz-core` enforces the complete NIP-AD envelope and content contract — kind, exact tag cardinality, canonical hex, content/tag agreement, required `reason` — and **relay ingest calls it** rather than keeping a parallel implementation. Two implementations of one contract drift; one implementation with two callers cannot. The lifecycle is stated declaratively in `scripts/gen-nip-ad-corpus.mjs`, which generates both the 341-case exhaustive corpus and the NIP's normative transition table; CI fails on drift. Both the Rust verifier and the TypeScript mirror run both corpora, so a cross-language divergence is a test failure. An adapter-conformance test compares the desktop's per-request status field by field against a shared-verifier oracle. A live suite drives signed requests through a real relay over HTTP and WebSocket/NIP-42, including a joined path that runs the actual CLI binary end to end and a cross-principal spoof that must change nothing. Squashed from seven iterations across five external design reviews; the per-round narrative and the surviving known limitations (terminal claims are irrevocable in v1; multi-agent requests unsupported; no dual-write migration from kind:9) are recorded in the NIP and the design record. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Tal Weiss <major.tal@gmail.com>
MajorTal
force-pushed
the
claude/agent-disposition-ledger
branch
from
August 20, 2026 06:40
4560f59 to
1d0c4f1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #3091.
A signed, durable, plaintext record of how an agent resolved each request directed at it, so any authorized reader of a channel can verify that a request was answered — and read why it was refused — without parsing chat prose or trusting unverifiable claims.
The model
An obligation is one marked request naming exactly one target agent that is also
p-mentioned. That is the unit of accounting, because a disposition is only meaningful if "which obligation does this discharge, and who was obliged?" has exactly one answer. Requests naming several agents are reported as unsupported rather than given a state either agent could discharge.A disposition binds only when its
e/h/ptags match the obligation and its signer is the target agent. The relay validates structure; binding is consumer-side, so every consumer shares one verifier. Without it, any channel member — including a merelyp-mentioned human — could close an agent's obligation with a signed event.Four states.
completedandrefusedare terminal and absorbing: a later weaker observation is recorded as a warning, never a reopening, so "terminal" means terminal.respondedanderroredleave the obligation open.The harness cannot emit
completed. It observes that a turn ended, not that the work was done — a cleanEndTurncovers asking a clarifying question, or answering one message of a batch, just as readily as finishing the job.TurnOutcomehas three variants, so the type system enforces this rather than discipline.completedcomes only from the target agent's own signed assertion viabuzz dispositions emit, which refuses a signer that is not the obligation's target.Malformed and unsupported requests are reported separately from unanswered ones: filing a client bug as an agent's gap manufactures a gap no agent can ever clear. Coverage is two-sided — a clean channel claim needs both the request set and the disposition histories paginated to exhaustion, because missing one disposition page can hide a
refusedthat turns "settled" into "disputed".One validity boundary
validate_disposition_eventinbuzz-coreenforces the complete NIP-AD envelope and content contract — kind, exact tag cardinality, canonical hex, content/tag agreement, requiredreason— and relay ingest calls it rather than keeping a parallel implementation. Two implementations of one contract drift; one implementation with two callers cannot.How it is verified
scripts/gen-nip-ad-corpus.mjs, which generates both the 341-case exhaustive corpus and the NIP's normative transition table. CI fails on drift, so the spec table cannot contradict working code.Deployment dependency
v1 is relay-first. Operators must upgrade the relay to accept and enumerate kind
44300before enabling any emitter. Emitters do not fall back to kind9— they fail to publish against an un-upgraded relay, so the gap is visible rather than silently masked. Kinds are additive, so disabling emission reverts to the status quo without data loss.Known limitations (v1, deliberate)
completed/refusedirrevocable. A correction relation is a real protocol extension with its own authority and ordering questions; v1 states the limitation, tells agents terminal claims are final, and defers the design rather than inventing one.9dispositions; that history is simply not shown.Testing
just cigreen (3,545 Rust, 5,003 desktop, 1,465 mobile)Reviewed across five external design rounds; the per-round narrative is in the NIP and the design record. Naming is mechanical to change if maintainers prefer a different kind number or tag names.
Screenshots of the desktop surface will follow in a comment.
🤖 Generated with Claude Code