aere-quantum/agents
2026-09-30 09:43:10 +03:00
..
dovezi-ancora-28001 agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00
x402 agents/x402: a 2-of-2 contract wallet (ERC-1271) that neither the agent nor its owner can spend from alone 2026-09-30 00:48:10 +03:00
agent-ancora.mjs agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00
agent-aprobare.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
agent-cli.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
agent-ledger.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
agent-policy.mjs agents/x402: an agent wallet that pays over x402 only what the agent's ledger records and its policy allows, run on the public testnet 2026-09-30 00:01:34 +03:00
control-negativ-ancora.mjs agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00
control-negativ-aprobare.mjs agents/x402: an agent wallet that pays over x402 only what the agent's ledger records and its policy allows, run on the public testnet 2026-09-30 00:01:34 +03:00
proba-agent-ancora-testnet.mjs agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00
proba-agent-ancora.mjs agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00
proba-agent-aprobare.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
proba-agent-cli.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
proba-agent-ledger.mjs Add agents: a post-quantum identity, a policy and a signed action ledger for AI agents, with human approval and revocation 2026-09-29 21:59:41 +03:00
proba-agent-policy.mjs agents/x402: an agent wallet that pays over x402 only what the agent's ledger records and its policy allows, run on the public testnet 2026-09-30 00:01:34 +03:00
README.md agents: the chain as the witness of an agent's ledger - a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below; run on testnet 28001 with its evidence 2026-09-30 09:43:10 +03:00

Agents: policy, post-quantum identity, a signed action ledger, human approval and revocation

An AI agent that can pay or call tools needs limits that someone other than the agent can check. This component gives an agent a post-quantum identity (an ML-DSA-65 key), a policy (a spending limit per time window, allowed tools, allowed recipients, which actions need human approval, who may revoke the agent), and a ledger in which every action the agent proposes is judged against the policy, signed by the agent and chained. A verifier that does not trust the agent re-runs the policy over the whole ledger and says whether any allowed action broke it. Humans approve and revoke with their own ML-DSA-65 keys.

Node.js 24 only (node:crypto with ML-DSA), no dependencies, no network. It moves no money: amount is the unit the policy names; moving value is the job of a payment layer, which can ask for this ledger as evidence that an action was allowed.

Quick start (command line)

node agent-cli.mjs id --out agent/                  # prints aere-agent:<40 hex>; writes agent.key.pem and agent.pub.pem
node agent-cli.mjs human --out alice/               # an approver or an owner: aere-human:<40 hex>
node agent-cli.mjs policy --spec spec.json --out policy.json
node agent-cli.mjs record --key agent/agent.key.pem --policy policy.json --ledger ledger.json --action pay.json
node agent-cli.mjs approve --key alice/human.key.pem --policy policy.json --action pay.json --out approval.json
node agent-cli.mjs record ... --approvals approval.json,approval2.json
node agent-cli.mjs verify --ledger ledger.json --policy policy.json [--revocations r.json] [--anchors a.json] [--not-before <unix>]
node agent-cli.mjs revoke --key owner/human.key.pem --policy policy.json --out revocation.json
node agent-cli.mjs equivocation --a ledger-a.json --b ledger-b.json --out proof.json
node agent-cli.mjs verify-equivocation --proof proof.json

A spec is { agentId, spend: { amount, windowSeconds, asset? }, tools: [...], recipients: [...], approval: { approvers: [humanId...], threshold, above?, tools? }, owner: humanId, wallet: 0x<EVM address> }; every field but agentId is optional. When wallet is named, a payment must say from and be from it. An action is { kind: "payment", to, amount, asset? } or { kind: "tool", tool, args? }; the ledger sets its time. Exit codes: 0 yes (allowed, valid, found), 1 no (invalid, not found, refused by a check), 3 an action refused by the policy, 2 wrong usage. Private keys stay in their files and are never printed; the tool creates them with mode 0600, which has no effect on Windows (protect the folder there).

What each part does

  • Policy (agent-policy.mjs): definePolicy returns the policy in a normal form and its hash. hashPolicy recomputes the hash of a policy received from anyone and refuses unknown fields, so a relying party that pinned a hash cannot be handed a looser policy under it. checkAction is pure: a payment must be a strictly positive decimal integer, to an allowed recipient, in the policy's asset, and within the limit over the window; a tool must be on the list. Approval is a requirement in addition to the limits, never an exemption. decisionEnvelope writes a decision as an AIP-23 envelope (aere-proof-of-agent-decision) whose actionHash is the SHA-256 of the action's canonical JSON (keys sorted), so anyone can recompute it.
  • Identity and ledger (agent-ledger.mjs): agentId is derived from the public key (nobody can claim another agent's id). Each entry carries the action, the decision, the approvals it used and optionally the hash of its provenance (for example a Proof of AI envelope); it is signed by the agent and chained by hash. The spending in the window is derived from the ledger's own allowed entries, so the limit holds over the whole ledger, across restarts (resumeLedger verifies the saved ledger, then continues it).
  • Verifier (verifyLedger): recomputes the policy hash, the identity, every signature and the chain, and re-runs the policy from the first entry: an "allowed" written over a refusal is caught even when the agent re-signs its whole ledger. Revocations are given to it separately, by the owner, because an agent can leave its own revocation out of its ledger; the result says which set of revocations it judged against (revocations.setHash).
  • Approval and revocation (agent-aprobare.mjs): an approval signs exactly the action (kind, recipient, amount, asset, tool, and the hash of the tool arguments, so an approval to deploy to staging does not approve production), the agent and the policy; it has a validity window of at most seven days and a nonce that counts once per ledger. Only approvers named in the policy count, each once. A revocation is signed by the owner named in the policy; from its time on every action is refused.

Paying over x402

x402/ holds the agent wallet: a service that keeps the payment key for the owner and signs an x402 payment only when the agent's ledger records it and the policy allows it, with the wallet as the witness of the ledger's heads and time. With a 2-of-2 contract wallet (AereAgentWallet2of2, ERC-1271) the money leaves only with the agent's signature and the wallet service's together, so neither the agent nor the owner can spend alone. Both were run end to end on the public testnet 28001 on 2026-09-29, and the evidence is there with a verifier anyone can run. See x402/README.md.

The chain as the witness of the ledger (2026-09-30)

Without a witness, an entry's time is only the statement of whoever holds the agent key. agent-ancora.mjs makes the chain that witness: the ledger's head (sequence number and hash) becomes an AIP-23 envelope, its hash is notarized on AereNotary, and the first-seen time the contract records, read through the AIP-23 reference verifier under a post-quantum certified anchor (a Merkle proof in the certified state), becomes an anchor {seq, hash, at} for verify --anchors.

node agent-ancora.mjs head --ledger ledger.json --out head.json                                  # no network
node agent-ancora.mjs notarize --head head.json --rpc <url> --key-file <key> --out notarized.json # needs ethers (x402/)
node agent-ancora.mjs anchors --ledger ledger.json --head notarized.json --rpc <url> \
     --chain 2800 --verify-proof <verify-proof.mjs from aere-node> --out anchors.json   # --chain: the chain whose time you accept
node agent-cli.mjs verify --ledger ledger.json --policy policy.json --anchors anchors.json

With the anchor, an entry after the notarized head cannot claim a time before its first-seen time (minus the 300 s tolerance), an entry up to the head cannot claim a time after it, and a branch that does not contain the head does not verify. The time is the chain's, not the agent's and not the notarizer's: whoever notarizes can only notarize late, which weakens the bound, not move it earlier. Only the hash of the head's statement goes on chain, nothing of the ledger. The anchors file is the output of the verifier's own run, with the RPC endpoint and the AIP-23 verifier it chose; an anchors file handed over by the party being verified proves nothing. anchors answers not measured (exit 2) while the finality is pending or when the verifier cannot be run, never an anchor it did not certify. notarize reads the chain id from the endpoint and refuses chain 2800 (a real transaction paid by the key's account) unless AERE_CONFIRM_MAINNET=yes.

What the verifier can and cannot see

  • Time. An entry's time is the statement of whoever holds the agent key. The verifier bounds it from above with its clock. It bounds it from below only with a witness: anchors (ledger heads seen by a witness at a known time, for example notarized) or notBefore. Without one, the key holder can backdate entries into past windows and spend the limit several times; the result says so (time.lowerBound: "none..."). With anchors, backdating is limited to the interval between two anchors; the chain itself can be that witness (above). The library itself refuses to write an entry more than 300 seconds away from its clock.
  • One ledger per agent and policy. The ledger's session is derived from the agent and the policy hash, so a "second ledger" is a branch of the same one. Two branches signed by the agent are a proof of equivocation (findEquivocation, verifyEquivocation) that anyone can check with the public key alone: this is how a double spend or an approval used twice across branches is shown. Someone who sees only one branch cannot know that another exists; anchors or a second copy reveal it. An agent that loses its ledger cannot continue it without looking like a branch: keep the ledger durable, or start under a new policy.
  • Revocation time is the owner's statement, as the agent's entry times are the agent's.
  • Nothing here proves that the actions happened in the world, only what the agent recorded and whether the policy allowed it.

Tests

Measured on 2026-09-29 (Node.js 24.14.1, Windows):

test result
node proba-agent-policy.mjs 27/27 with the AIP-23 reference verifier (AERE_VERIFY_PROOF=<verify-proof.mjs from aere-node>); without it 25 run, 2 are reported as skipped and the exit code is 2
node proba-agent-ledger.mjs 51/51: limits over the ledger, identity, tampering, a re-signed false decision, a looser policy under the real hash, backdating (refused when written; caught with anchors or notBefore), a branch, equivocation proofs, resuming
node proba-agent-aprobare.mjs 39/39: threshold, unnamed or repeated approvers, classical keys, another action or other tool arguments, reused nonces (also after a restart), expiry, approvals across branches, revocation, an omitted revocation
node proba-agent-cli.mjs 23/23 on Windows, through files and processes only; on Linux and macOS one more test checks the 0600 mode of the key (not measured here)
node proba-agent-ancora.mjs 26/26 without a network (the AIP-23 verifier's answer in its own form): the head, the time read from the finality level (pending, failed, a mismatched or unknown form: no anchor), an envelope that claims another time, backdating caught with the anchor and not without it, another branch, the refusals before any transaction (a modified head, chain 2800 without confirmation, no contract at the notary)
AERE_TESTNET_KEY_FILE=<funded testnet key> node proba-agent-ancora-testnet.mjs 7/7 on the public testnet 28001, measured 2026-09-30: a real ledger's head notarized, post-quantum finality from the published AIP-23 verifier within 20 s, the certified time equal to the contract's first-seen time, the honest continuation valid with the anchor, a backdated continuation valid without it and caught with it, another branch refused; evidence in dovezi-ancora-28001/
node control-negativ-ancora.mjs 11/11: each anchoring guard removed in a copy turns its named test red
node control-negativ-aprobare.mjs 26/26: each guard is removed in a copy, one at a time, and the named test must fail for that reason; a test that stops before its summary counts as a failure of the control

The data format is version 2 (2026-09-29). Code comments, test names and most internal names are in Romanian; messages, data and the exported interface are in English. No third party has reviewed this component.