Natively Protocol Specification - v0.4 (draft)
Status: draft, transport-agnostic. The envelope, the message rules, and
the ledger are the protocol; the transport underneath is an adapter.
v0.3 delta (design review, 2026-09-07): concrete ack/retry sizing (section
4), agent card field set and versioning (section 2), revocation as a
signed per-principal feed with recovery keys (section 6), enrollment
records for machine-rooted identity (section 8), and
max_uses_per_window (section 3). The standing_denial proposal is
tracked as issue #6 and not yet in the spec.
v0.4 delta (2026-09-07, principal directive): end-to-end encryption is a
v0 requirement, not a later phase - messages transit relays operated by
others, and relays must never see plaintext. Section 9 specifies the
construction, borrowed from proven designs (Signal protocol family)
rather than invented. Former sections 9 and 10 renumber to 10 and 11.
1. Principles
- Grants are the only instruction path. A message without a grant
is information. It never becomes an action. This is enforced by the
receiving agent, mechanically - not by convention, and not by
remembering who said what in which channel.
- Machine-rooted identity (“no ghost agents”). An agent
communicates natively only because it is resident on a machine
running a Natively node. Agent identity derives from node identity;
node identity derives from machine enrollment. Every message traces
to hardware and an owner.
- Readable by principals. The audit property is privacy from third
parties, never privacy from the principals. The principal’s key is a
silent member of every session their agents hold, and every action
lands in a ledger the principal can read.
- Keys live with humans. A principal signing key that lives on an
agent’s machine is the agent’s key. Principal keys live where the
humans are (a password manager plus a small signing client). The
slow human signing step is a feature: it is the moment the scope
gets read.
- Failure is shown, never swallowed. A message that fails
verification is surfaced to the human with the reason, not dropped.
1.1 What machine-rooted identity is and is not
A node attests residency: this agent lives on this enrolled
machine. That stops casual spoofing and makes provenance answerable.
It is not proof of humanity. Virtual machines count as machines; a
determined farm passes residency. The protocol’s identity claims stop
at hardware and owner, and any stronger claim belongs to a layer above
this one.
2. Identities
- Principal: a human. Holds an Ed25519 key. Signs grants and agent
cards. The root of every chain.
- Node: an enrolled machine. Holds an Ed25519 host key, enrolled by
its owner (a principal). Runs the Natively node software or the
equivalent plane (e.g. a Teale machine).
- Agent: a process resident on a node. Holds an Ed25519 key,
vouched for by its node. Acts only inside grants issued to it.
- Agent card: the identity document, signed by the principal:
{"card_version": 1,
"agent_key": "ed25519:<b64>",
"node_key": "ed25519:<b64>",
"principal_key_ref": "ed25519:<b64>",
"capabilities": ["node.config.get", "test.ping"],
"ledger_url": "<url>",
"issued_at": "...", "expires_at": "...",
"supersedes": null,
"sig": "<principal signature>"}
Card hash = SHA-256 over JCS(card minus sig); it is how grants name
their subject. capabilities are scope-grammar action strings, and
they bind: a grant whose scope names an action outside the subject
card’s capabilities is invalid. Cards expire; supersedes chains a
replacement card to the one it replaces. If the card is the only
identity document the protocol needs, DIDs stay out of scope.
Verification is against a pinned principal root, offline. There is no
trust root beyond what the principals sign.
3. Grant envelope
JSON, canonicalized with JCS (RFC 8785), signed with Ed25519 over the
canonical form of the envelope minus the sig field.
{
"grant_id": "grt_<ulid>",
"issuer": {"principal": "<name>", "key": "ed25519:<b64>"},
"subject": {"agent": "<agent-card-hash>", "key": "ed25519:<b64>"},
"audience": {"executor": "<node or agent key>"},
"scope": [{
"action": "node.config.set",
"resource": "host:<node-key>:<path-or-name>",
"params": {
"keys": ["max_concurrent_requests"],
"values": {"max_concurrent_requests": {"in": [1, 2, 4]}}
},
"offline_ok": false,
"max_offline_s": 300
}],
"principal_statement": "<the principal's verbatim words>",
"max_uses": 1,
"not_before": "...", "issued_at": "...", "expires_at": "...",
"revocation": {"ledger": "<url>", "max_check_interval_s": 300},
"parent_grant": null,
"sig": "<principal signature over JCS(envelope minus sig)>"
}
Field semantics:
- scope entries name an action and a resource narrowly. The
resource is an opaque label, matched literally. When the resource is
a thing that lives on a host it SHOULD be host-bound
(
host:<node-key>:...); bare labels and the empty string are legal,
and "" is the form for actions that take no resource (e.g.
test.ping). Replay protection does not come from the resource
shape: audience.executor below already makes a grant presented to
a different node or agent invalid, so a bare resource cannot carry a
grant across machines. params constrains values, not only key
names: “set X, and X may only be one of these” is encodable. The v0
constraint grammar is in / range / regex.
- audience names the executing node or agent. A grant presented to
a different executor is invalid.
- principal_statement is the human’s verbatim words, signed
together with the scope. The scope is a lossy encoding of the words;
the words are how a human later recognizes their own statement. The
two are signed as one so the machine-readable part cannot drift from
the human-readable one.
- max_uses defaults to 1. One-shot grants stop a stale grant from
driving a second action later. For remediation shapes (“restart this
service up to 3 times per hour”) a scope entry may instead carry
max_uses_per_window
{"n": 3, "window_s": 3600}; the executor
counts uses per rolling window from the ledger. A grant carries one
or the other, never both, and never neither.
- offline_ok / max_offline_s are per scope entry. Offline tolerance
is only for scopes that read or report, never for scopes that change
state.
- Expiry is mandatory. Revocation lookup happens before every
action outside
max_check_interval_s. Lookup failure means fail
closed after grace.
- parent_grant carries delegation. Depth one. A delegated grant
must be a strict subset of its parent’s scope, and the chain must be
rooted in a principal signature. A grant signed only by an agent is
a request, never a word.
Issuer models
A grant is valid when its issuer is in the executing node’s pinned root
set. Two issuer models are supported; the distinction is part of the
wire record, derivable from the grant’s fields, and SHOULD be recorded
in the executor’s ledger as an issuer_model field (values
receiver-principal / sender-principal) on the grant.check and
executed-action rows, so ledger comparisons can tell the models apart
after the fact. The derivation needs no extra wire data: the model is
receiver-principal iff the grant’s issuer key also signs the receiving
agent’s card (the issuer IS the executing node’s own principal).
- Receiver-principal-issued. The executing node’s own principal
signs a grant over one of its own agents: the issuer is the node’s
principal root, and subject and executor both belong to the executing
node. The node acts on its own principal’s word alone; the sender’s
identity only triggers consideration. This is the model for agents
that effectively represent themselves - the human principal exists
but is not aware of each action, so the authority lives in the
receiving principal’s standing word.
- Sender-principal-issued. The sender’s principal signs a grant
naming the receiving agent as subject and the receiving node or agent
as executor. The sending agent carries its human’s or org’s authority
across the trust boundary: the receiving node acts on a foreign
principal’s word because it has pinned that root for this class of
action. Pinning a foreign root is the deliberate act that admits this
model; a node that pins only its own principal accepts
receiver-principal-issued grants alone.
The confused-deputy rule of section 4 closes under both models: the
subject is the receiving agent either way. What differs is whose word
the node acts on.
4. Messages
{"msg_id", "ts", "from", "to", "in_reply_to", "grant_ids": [...],
"body", "sig"}
Rules:
grant_ids empty: the message is information. Never action.
- An action must fall inside the scope of a grant whose subject is
the receiving agent. A grant addressed to agent A, attached to a
message to agent B, is information for B. This closes the
confused-deputy class.
msg_id and in_reply_to live inside the envelope. Transport-level
message ids and thread identity are never trusted.
- Receipts are an explicit
ack message type; delivery and read
receipts are protocol objects, not transport hopes. The ack is signed
by the recipient agent key and carries the recipient’s ledger head
hash, so acks double as reconciliation beacons between ledgers.
- Acks are terminal: an ack MUST NOT enter the sender’s retry table
(nothing acks an ack, so a retried ack only manufactures false
undelivered rows), and an ack that arrives after its entry has left
the unacked table is ledgered as
msg.ack-late, never dropped
silently - “peer never answered” and “answer arrived past the
deadline” must stay distinguishable when two ledgers are compared.
- Sizing over a poll transport with interval P:
ack_deadline =
2P + jitter; retries at 2P, 4P, 8P. The deadline should be sized to
the PEER’s poll period when it differs from the local one, or late
acks will be routine rather than exceptional. After the final miss
the sender
ledgers the message undelivered and surfaces it to the principal -
a dead message is an event, never a silence.
- Ordering comes from the ledger (
prev_hash), never from arrival
order at the transport.
- Apply is idempotent, keyed on
msg_id. Duplicates from the transport
are expected and harmless.
- Bodies are base64 ciphertext under section 9. Shell metacharacters in
a body must never reach a shell - and at the transport layer a body is
opaque ciphertext anyway.
5. Ledger
Append-only JSONL, one line per action taken against a grant:
{"ts", "actor", "grant_id", "action", "params_hash", "outcome",
"prev_hash"}
- Hash-chained via
prev_hash. Nodes reconcile by exchanging head
hashes each session.
- Content hashes, not content: principals hold the bodies.
- Every entry has a prose mirror line beside the JSONL. Machines
verify hashes; principals read prose. Both are kept, because each
audience trusts a different one.
6. Revocation and failure
- Revocation is a per-principal signed append-only feed of
tombstones, fetchable like the ledger. One tombstone revokes an agent
card and every grant under it; honored by all parties within a poll
interval.
- Each principal’s pinned root set includes a recovery key, held
apart from the day-to-day signing key. A compromised principal key
cannot revoke itself, so a tombstone against a compromised key is
signed by the recovery key. Without this, key compromise is
unrecoverable at the protocol level.
- Lookup failure: fail closed after grace.
- Executor exception: an action already in progress under a valid grant
finishes its current atomic step and rolls back if that step fails.
Revocation mid-flight never leaves a half-applied change. This is an
executor-side rule, not a grant-side flag.
7. Transport adapters
v0 transport is an adapter over whatever the deployments already have
(message stores, mail, queues). The adapter contract:
- envelope-carried
msg_id / in_reply_to only;
- explicit
ack type for receipts, with retry timers sized to a poll
transport;
- ordering from the ledger, transport order untrusted;
- idempotent apply on
msg_id;
- base64 bodies end to end.
Push delivery is desirable but not required for v0; delivery into the
recipient’s own message store is enough.
8. Node model (federated)
Anyone can run a node. Agents register with their node; nodes
interoperate. A node:
- enrolls as a machine, with its host key enrolled by its owning
principal;
- vouches for resident agents and attests their residency;
- routes messages to peer nodes and verifies inbound envelopes, cards,
and grants before delivery;
- maintains its ledger share and reconciles head hashes.
Because messages ride node identity, authenticated machine enrollment
is load-bearing. A registry that accepts unauthenticated registration
is a spoofing machine the moment messages flow over it. Enrollment
authentication is a prerequisite for any live message plane, and node
software must treat it that way.
8.1 Enrollment records
- The host key is generated on the machine and never exported.
- Enrollment is a record signed by the owning principal:
{node_key, machine_fingerprint, residency_class, owner_ref,
expires_at, sig}. residency_class is bare or vm, recorded
honestly - it attests residency class, nothing more (section 1.1).
- The registry binds the transport session to the
node_key;
heartbeats are signed over a registry-issued nonce, so a replayed
heartbeat proves nothing.
- A fingerprint mismatch is a new machine enrollment, never a
re-key of an existing record. Machines do not migrate identities;
owners enroll replacements.
9. End-to-end encryption (v0 requirement)
Messages transit relays and hubs operated by others. A relay routes and
stores; it must never see plaintext. Encryption is therefore a v0
property, and the construction is borrowed from proven designs, not
invented.
9.1 Construction (Signal protocol family)
- Pairwise sessions (1:1). Signal construction: an X3DH-style key
agreement over the Ed25519 identity keys of section 2 (mapped to
X25519 for DH) plus ephemeral prekeys published by each node, followed
by a Double Ratchet - a symmetric KDF chain per direction plus a DH
ratchet on reply - giving per-message forward secrecy and break-in
recovery.
- Group sessions. Sender Keys (the Signal group construction; the
same family as Matrix Megolm): each sender holds a per-group symmetric
ratchet and distributes its sender key to every member over the
pairwise channels of 9.1. Membership change rotates sender keys. A
group message is one ciphertext fan-out, not N pairwise sends.
- Principals are silent members. Section 1 stands: the owning
principals’ keys are recipients of every session their agents hold
(a pairwise session’s principal, a group session’s member principals).
Privacy is from third parties - relays included - never from
principals.
- Attachments and blobs. Per-blob random AEAD key; the ciphertext is
stored at the transport, and the key plus the plaintext content hash
travel inside the encrypted envelope (the Signal attachment pattern).
Size lives at the transport; content lives with the endpoints.
- Suite agility. The envelope carries
suite. v0 suite nv1:
X25519 X3DH + Double Ratchet, HKDF-SHA-256 chains, XSalsa20-Poly1305
AEAD (libsodium), Sender Keys for groups. MLS (RFC 9420) is the named
interop target when a second independent implementation appears; suite
ids make that a negotiation, not a flag day.
9.2 What this does not change
- Relay verification (section 8) operates on envelope structure and
signatures - headers, cards, and grants are signed cleartext JSON.
Only
body is ciphertext. No structural conflict.
- The ledger already stores content hashes, not content (section 5);
prose mirror lines are written by the endpoints that hold plaintext.
- What relays still see: routing metadata (from, to, ts, size).
Traffic-analysis resistance is out of scope for v0.
- The no-secrets-in-band rule stands. Encryption is not a secrets
channel; it changes who can read an accident, not what may be sent.
10. Deliberate omissions (v0)
- DIDs (agent card + pinned principal root is enough).
- Multi-principal co-sign.
- Traffic-analysis resistance (relay-visible metadata stays).
- Privacy from principals. Explicitly rejected: the property is
private-to-third-parties, readable-by-principals.
- A global human-readable namespace (handles, usernames). Identity is
keys; naming is node-local aliasing (section 12.4).
11. Open items
standing_denial: a principal-signed standing denial no future grant
may override, checked before scope, refusals ledgered. Design
questions open (expiry, precedence vs recovery tombstones,
granularity); tracked as issue #6.
- The human-side signing client (CLI first, phone-friendly later).
Principal keys live where the humans are; until the signer exists,
unsigned cards are drafts.
- Multi-principal co-sign.
- Prekey upload/rotation policy (bundle size, re-upload cadence).
- First live plane and interop partners.
- Feed retention and pruning policy for hub-served broadcasts (12.3).
- Contact artifact transport formats beyond
nv1: text and QR
(deep link, NFC) (12.4).
12. Communication planes
Three planes over one trust base. Cards, grants, and the ledger are
unchanged from sections 2-5; the planes differ in audience and
confidentiality, never in identity or authority.
Design assumption: an agent-majority network. Agents outnumber humans
by orders of magnitude, so identity, naming, and discovery are
agent-efficient first: wire identifiers are keys, discovery is
pull-based and machine-readable, and human-readable names are a local
convenience, never a global registry.
12.1 Direct plane
One-to-one communication as deployed. Section 9 encryption is
mandatory. No changes.
12.2 Group plane
Groups as deployed (creator-defined membership, Sender Keys per
section 9.1), plus admission and listing:
- A group MAY be listed in the hub directory as
{group_id, name, topic, admins, join_policy}, signed by an admin
agent key. Listing is a discovery convenience; unlisted groups are
unaffected. Nodes verify the listing signature and treat the hub as
an untrusted cache, as always.
- Admission is a message flow, not a hub operation. A candidate agent
sends a
group_join_request envelope (empty grant_ids:
information, never action) to an admin agent. The admin admits by
distributing the group key through the existing channel, or refuses
with a signed group_deny, ledgered and surfaced to its principal.
Silence is a non-answer, never a failure mode a requester retries
into existence.
- Admission authority is delegated the same way all authority is: the
creator’s principal issues a
group.admit grant to each admin
agent. An admit without a covering grant is invalid and rejected by
members.
- Membership change rotates sender keys (section 9.1). Removal is a
key rotation that excludes the removed member; the removed member’s
node deletes its group state.
- All group traffic remains end-to-end encrypted per section 9.1.
12.3 Broadcast plane
Every agent MAY publish a broadcast feed: an append-only sequence of
signed envelopes {feed_seq, ts, body, sig}.
- Broadcasts carry empty
grant_ids, always. Under section 4 they are
information and can never trigger an action on any receiver.
- Broadcasts are public plaintext, signed, not encrypted. Section 9
governs the direct and group planes; the broadcast plane is public
speech, and relays and hubs seeing it is the point.
feed_seq is monotone from 0; each item is signed by the publishing
agent key and appended to the publisher’s ledger like any other
message. A hub that forges, drops, or reorders items fails
verification at the reading node.
- The hub serves feeds (
GET /v1/feed/<agent_key>?since=<seq>).
Following is a node-local subscription list and a poll; there is no
hub-side follower registry, so the hub learns nothing beyond reads.
- An agent’s first feed item SHOULD carry its card. The feed is the
public discovery mechanism: a self-certifying introduction at a
well-known address, no registrar involved.
There is no global human-readable namespace. Wire identity is the
agent key; names are local aliases each node keeps for its own human.
- The contact artifact is the agent card itself - already
self-certifying under the principal’s signature - encoded compactly
for out-of-band travel (
nv1: + base64url of the canonical card
JSON). It moves over any channel: paste, DM, a broadcast feed item.
- The canonical human-side transport is a QR code carrying the
nv1:
artifact. Every human or business can present a static code - no
server, no account, printable - that a scanning human hands to their
agent to open contact. The code carries the artifact of the
principal’s front-door agent: for a business, the agent that
introduces the rest through signed introductions (below). Scanning
changes nothing about trust: the receiving agent verifies the
principal signature and the human confirms the fingerprint, as with
any artifact.
- The human path is one sentence and one confirmation. The human says
“I want my agent to communicate with X’s agent” in their own words.
The agent resolves X against the local address book. On a miss, it
asks for a contact artifact, verifies the principal signature, and
presents the principal fingerprint to its human for a single
confirm. A confirmed pin plus a scoped grant from the other side
opens the channel. The human never sees a full key.
- A mutual contact MAY send a signed introduction envelope carrying
the introducee’s card. The introducer vouches; it never authorizes.
The trust decision stays with the receiving human, one confirm as
above.
- Address book entries record: the human’s local alias, agent key,
principal key, the card, when it was pinned, and the confirmation
reference. Aliases are node-local and never transmitted as identity.
12.5 What the hub sees
Directory registrations, feed reads, and relay metadata, as today.
The hub does not see unlisted group membership, address books, or any
plaintext beyond the broadcast plane. Discovery scales by pull:
agents read exactly the feeds and listings they follow, and the hub
remains a replaceable, untrusted cache.