MINERVA™
Verify a packetThe policy keySecurity

A formatpublished 2026-10-26 · version 2

The policy key: a format

Published 26 October 2026 by Minerva Software Inc. as a description of a format. The cryptography is standard (Ed25519, SHA-256, RFC 6962 trees); the binding described here is the company's.

This page describes the policy-key format, version 2, as its reference implementation encodes it. It describes a format and nothing else: not a product's behaviour, not a roadmap, not a claim of invention. The parts are old and well understood — capability bit matrices, Ed25519 signatures (RFC 8032), SHA-256 hash chains and Merkle trees in the shape of RFC 6962, and the attenuation idea behind macaroons; what is described here is how they are bound together. The same text stands in the repository as docs/POLICY-KEY-SPEC.md.

01What a policy key is

A policy key is a signed byte string that says what one AI agent's runtime may do: which registered tools it may call on which scope classes, which tools are held until a person in a named role approves the call, which need the caller's consent, and a session's limits on calls and spend. It is compiled from a decided row of a governance record, bound to the agent's identity and to that row, signed by an issuer, carried by the runtime that dispatches the agent's tool calls, and read there without a network call. Every decision the runtime makes is one record in a hash chain, and every batch of records is sealed by a Merkle root the runtime signs, so one decision can be proven on its own and the whole stream recomputed by anyone who holds it.

02Conventions

  • Integers are unsigned and little-endian: u8, u16, u32, u64.
  • A string is a u16 length in bytes followed by that many bytes of UTF-8.
  • A hash is 32 raw bytes of SHA-256; a hex form of it is 64 characters.
  • An object's hash is SHA-256 over every byte before its signature.
  • An object's signature is Ed25519 (RFC 8032) by its issuer's private key over every byte before it; 64 bytes; the last field of the object.
  • A key id is the first 8 bytes of SHA-256 over the raw 32-byte Ed25519 public key.
  • The agent field is SHA-256 over the UTF-8 bytes of the agent's registered identity string.
  • Three magics name the three objects: MNVK a policy key, MNVR a roster, MNVM a manifest. The version byte is 2.

03The key's header (181 bytes)

OffsetBytesFieldMeaning
04magicMNVK
41version2
51flagsbit 0 the signature algorithm (0 = Ed25519); bit 1 (0x02) the rows are sparse; bit 2 (0x04) the key is a ceiling — a layer (section 7)
68issuerthe key id of the signing public key; a runtime picks the pinned key to verify with, so several issuers coexist
142Tthe number of tools
162Sthe number of scope classes
181rolesthe number of role names (at most 255)
192refusedthe number of refused approver keys
2132agentSHA-256 of the agent's registered identity; a key cannot be moved to another agent
538row seqthe governance-record row the key was decided on
6132row hashthat row's hash; the row carries the key's hash, so each names the other
938not_beforeunix seconds
1018expiresunix seconds; outside not_before … expires every decision is FORBID
10932replacesthe hash of the revision this one retires, or 32 zero bytes
14132rosterthe hash of the roster (section 6) that holds the roles' keys, or zeros; the runtime refuses any other roster
1734limit: tool callsper session; 0 in a ceiling means no cap of its own
1774limit: spendin cents, per session

04The key's body

After the header, in this order:

  1. The dictionary. For each of the T tools: its name (a string) then its class (a string, possibly empty). Then each of the S scope classes (a string). Then each of the role names (a string).
  2. P, the permission matrix. Either dense — ⌈T·S / 8⌉ bytes of bits — or, when the flag says sparse, one row per tool: a u16 count followed by that many u16 scope-class indices whose bit is set. The encoder writes whichever is smaller on the wire; in memory the matrix is always the bit array.
  3. H and C. Two bit vectors of ⌈T / 8⌉ bytes each, one bit per tool: H — the tool is held until a person in an allowed role approves the call; C — the tool needs consent present in the call's context.
  4. The role sets. For each tool: a u8 count followed by that many u8 role indices — the roles that may release the tool when it is held.
  5. The refused approvers. refused hashes of 32 bytes each: SHA-256 of each public key that may never approve a call under this key (the agent's developer's, first).
  6. The signature. 64 bytes, Ed25519 by the issuer over everything above.

The key's hash is SHA-256 over the bytes before the signature. Compiling the same source twice yields the same bytes before the signature, so “recompile and compare” is a check anyone can run; because the roster is referenced by hash, a person joining or leaving changes the roster's hash and no key's.

05The matrix and the bit

Let the tools be t ∈ {0 … T−1} and the scope classes s ∈ {0 … S−1}. The policy is a Boolean matrix P of T × S bits. A call (t, s) is permitted when its bit is set:

s      = class(resource)        // "mortgage-files/8812/application.pdf" → "mortgage-files/*"
i      = t * S + s              // the bit's index
byte   = P[i >> 3]              // which byte holds it
permit = (byte >> (i & 7)) & 1  // shift it down, mask it off

A scope is a class: mortgage-files/* is every resource under that prefix; an exact name is a class of one. A resource resolves to the longest class whose prefix it carries, looked up by its first path segment. A tool carries a class from a taxonomy (core.transfer is a payments.transfer), which is what a layer written in classes matches on.

Attenuation is bitwise. A key P′ grants nothing a key P does not when every byte satisfies (P′[b] & ~P[b]) == 0. A ceiling Q applied to a key is P & Q′, where Q′ is Q resolved onto P's own classes and every pair Q does not name is 1. A delegated sub-key is P_parent & M, within the parent by construction.

06Three magics: the roster and the manifest

The roster (MNVR) holds the approvers by role, in one signed object every key references by hash:

FieldEncoding
magic · version · flagsMNVR · u8 2 · u8 0
issuer8 bytes, the signing key's id
rolesu8 count; for each: the role name (a string), a u16 key count, then that many raw 32-byte Ed25519 public keys
signature64 bytes

The manifest (MNVM) names every agent's current key, in one signed object:

FieldEncoding
magic · version · flagsMNVM · u8 2 · u8 0
issuer8 bytes
issued_atu64
entriesu32 count; for each: the agent hash (32), the key's hash (32), the revision (u32), not_before (u64), expires (u64)
signature64 bytes

A runtime pins each issuer's public key, verifies the manifest, and then fetches each key, roster and layer by hash: content-addressed, so a changed policy is a changed hash, never a changed file.

07Layers, and how they compose

A layer is a key with the ceiling flag (0x04). It names tool classes — or exact tool names — and scope classes (* allowed), says permit or forbid for the pairs it names, may carry hold and consent obligations and limits of its own, and passes everything else through. Several layers may stand over one agent's key, each signed by its own issuer.

At load the runtime verifies each layer against its pinned issuer and composes the set once:

  • For each of the agent's tools, each layer resolves the tool in this order: exact name · exact class · class by prefix · name by prefix · its *. Each of the agent's scope classes is resolved by its representative prefix.
  • A layer can only narrow. A pair a layer forbids has its P bit cleared, and the effective policy remembers which layer cleared it. A layer's H or C obligation is added (bitwise OR). The roles that may release a held tool are intersected by name. A limit is the minimum across the set (a layer's 0 is no cap of its own).
  • Two refusals to compose. A layer that names a class narrower than one of the agent's (the agent says mortgage-files/*, the layer mortgage-files/archive/*) cannot be applied at class level, and a layer that leaves no role able to release a held tool cannot be applied either; a set that contains either does not compose and the runtime has no policy (section 9, check 1).
  • The result has the agent key's shape — P, H, C, role sets, limits — so a decision costs what it costs under one key.
  • The set's hash is the composite: SHA-256("MNVC" ‖ h₁ ‖ h₂ ‖ …) over the layer hashes sorted bytewise, so the same layers in any order give the same composite. Every decision record carries it as its bundle. A key with no layers carries its own hash there.

08Rotation, revocation and suspension

A key has a window (not_before … expires) and names the revision it replaces. Two revisions may overlap for the time a cut-over takes; a decision on the older key after the newer one is in force is drift. Suspension is a key: a forbid-all revision (P all zeros, the same header shape) decided, signed and pulled like any other. Rotating an issuer means publishing the new public key, pinning both for a window, and compiling later keys under the new issuer id.

09The decision, in order

Eight checks, fail-closed first, in a fixed order; each names the rule word the record carries.

#ConditionDecision · rule
1No valid policy set (the key, a layer or the roster failed to load; an issuer not pinned; a set that does not compose)FORBID · no-policy
2The host's clock is outside not_before … expiresFORBID · window
3A tool the dictionary does not name, or a resource no scope class holdsFORBID · unregistered
4The P bit is clearFORBID · not-permitted (naming the layer that cleared it, when one did)
5calls + 1 > limit or spend + cost > limit for this sessionFORBID · budget
6The C bit is set and the call's context carries no consentFORBID · consent
7The H bit is set and no approval is offeredHOLD · approval-required
7′The H bit is set and an approval is offered but fails section 10FORBID · approval-invalid
8OtherwisePERMIT · permitted

Only a PERMIT moves the session's counters: calls by exactly one, spend by the call's declared cost. The runtime keeps the session table itself and never trusts a caller's claim of what it has spent.

10The approval

An approval is a signature by a person's key over one message:

SHA-256("MNVA" ‖ trace ‖ tool ‖ scope ‖ args_hash)

where trace is the call's trace id as text, tool the tool's name as text, scope the resource or scope as text, and args_hash the raw bytes of the SHA-256 of the call's arguments. The runtime accepts it only when all of the following hold: the approving key is not the runtime's own; its hash is not in the key's refused list; the role it claims is in the held tool's role set; the roster the key names holds that public key under that role; and the signature verifies over the message. An approval therefore binds the exact arguments and the exact trace: an approval of one amount cannot release another, and an approval cannot be reused on another trace.

11The record (190 bytes)

Every decision is one record, packed in this order and hashed with SHA-256 over the packed bytes:

BytesFieldMeaning
8seqone sequence per runtime, from 1
8atthe host's clock, unix milliseconds
8runtimethe enforcer key's id — which runtime of a fleet
8sessionthe first 8 bytes of SHA-256 over the session id
32agentthe key's agent field
32bundlethe composite of the policy set in force (section 7), or the key's own hash
2toolthe tool's index in the dictionary (0xFFFF when unregistered)
2classthe scope class's index (0xFFFF when none holds the resource)
1decision0 PERMIT · 1 FORBID · 2 HOLD
1rulethe index of the rule word: permitted 0 · no-policy 1 · window 2 · unregistered 3 · not-permitted 4 · budget 5 · consent 6 · approval-required 7 · approval-invalid 8
16tracethe first 16 bytes of SHA-256 over the call's trace id
4callsthe session's call counter after the decision
4spendthe session's spend in cents after the decision
32approverSHA-256 of the key that released a held call, or zeros
32prevthe previous record's hash on this runtime's chain; zeros for the first

8+8+8+8+32+32+2+2+1+1+16+4+4+32+32 = 190. A reader recomputes each hash, checks that prev names the record before, that seq is continuous, that the clock does not go backwards, that the counters move only on a PERMIT and by one call, that a trace is released at most once, and that bundle is a set the record decided for this agent and in force at at — anything else is drift.

12The batch and the proof

Every N records (the reference implementation's default is 1,000) the runtime computes the Merkle root over their hashes and signs it. The tree is RFC 6962-shaped: a leaf is SHA-256(0x00 ‖ record_hash), a node is SHA-256(0x01 ‖ left ‖ right), and a level with an odd count pairs its last node with itself. The seal is the runtime's Ed25519 signature over SHA-256(runtime(8) ‖ batch u32 ‖ from u64 ‖ to u64 ‖ root(32) ‖ at u64); from and to are the sequence numbers the batch covers. One record is proven by its sibling path to the root — ⌈log₂ N⌉ hashes, each with a side — against the signed root alone. Records not yet under a sealed root are an open tail, named as such and never assumed.

13What this format does not do

  • A model cannot enforce a policy on itself. The key is read by the runtime that dispatches the model's tool calls, in that process; an enforcer inside a process can be removed by whoever controls the process. The chain and the batch seals make its absence visible — a stream that stops, or a bundle the record never decided — they do not make it impossible.
  • The window is checked against the host's clock; a wrong clock is a wrong window, and the record carries the clock it saw.
  • The format does not say who may decide a key, how a roster is filled, or where a key is stored. Those are a deployment's rules, outside the bytes.
  • Nothing here is new cryptography, and the format would be weaker if it were.

Published 26 October 2026 by Minerva Software Inc. How an evidence packet is verified is published beside this page at /verify. Questions about either go to minerva@minerva.studio.

MINERVA issues no certification, attestation or opinion. Management still writes its own assertion; your auditor still selects, tests and concludes. What is shown is evidence about this workspace only.

© 2026 Minerva Software Inc. · minerva@minerva.studio