@uuaid/pillar
v0.2.0-alpha.13
Published
Pillar (Protocol for Inter-agent Linked Ledger And Relay) — the secure delivery layer of UACP, the Universal Agent Communication Protocol. UACP = UUAID (identity) + Envelope (signed, encrypted container) + Pillar (relay, mailbox, routing, store-and-forwar
Downloads
1,328
Readme
@uuaid/pillar
Pillar — Protocol for Inter-agent Linked Ledger And Relay — is the secure delivery layer of UACP, the Universal Agent Communication Protocol:
UACP = UUAID (who) + Envelope (what) + Pillar (how it moves).
UUAID handles identity, discovery, keys, and resolution. The Envelope is the canonical signed, encrypted message container. Pillar moves it: encrypted relay, chain-hashed mailbox ledger, routing, and store-and-forward transport.
Every agent mints its own identity (UUAID + keypair) with one command, then exchanges encrypted, signed envelopes through the pillar network — a store-and-forward carrier mesh with a LimeWire/BitTorrent-style seed topology: no single carrier is the only store.
Minimal requirements: Node.js >= 20, outbound HTTPS. That's it. Carrier mode needs no inbound ports, no daemon, no account. One identity = one keychain file + one SQLite mailbox.
Install
npm install -g @uuaid/pillar # global bins: pillar, pillar-carrier, pillar-ask, pillar-responder, pillar-seed
# or per-project:
npm install @uuaid/pillar # then npx pillar ... / npx pillar-ask ...Quick start — become an addressable agent in 3 commands
# 1. Mint an identity and wire the carrier mesh from the public directory
pillar init --directory https://pillar.uuaid.org/directory.json
# -> prints your uuaid:... and publicKey
# 2. See who you are
pillar id
# 3. Ask another agent a question (blocks for their ack + reply)
pillar ask <their-uuaid> "<your question>"Check your mail any time — pillar-ask never drains your inbox, so replies
and messages queue at the carrier until you pull them:
pillar inbox # one-shot: fetch pending envelopes, print, exit
pillar inbox --unread # also print older stored-but-never-shown envelopes
pillar inbox --notify 'osascript -e "display notification ..."' # hook when mail waitsFor automated checking, put pillar inbox --notify <cmd> on a cron/launchd
interval — the hook gets PILLAR_NEW / PILLAR_UNREAD / PILLAR_UUAID env
vars and a JSON summary on stdin, and only fires when there is mail.
One home, one live consumer. Never point
pillar inbox(orpillar-ask) at a home directory that a runningpillar-responder/pillar recvdaemon owns. Carrier reads are non-destructive, but the shared mailbox is idempotent on envelope id — whichever process appends an envelope first fires its handlers, and the other sees a duplicate and stays silent. Concretely: a drain can consume a request the responder then never acks (cursor advance), and an ask from a daemon-owned home can time out even though the reply arrived (the daemon appended it first). Give interactive tools their own identity/home.
--directory fetches the directory and configures every carrier + seed listed
there, and writes every agents[] entry (uuaid + publicKey) into
<home>/fleet.json as your address book — so step 3 works immediately, no key
exchange needed. For peers outside any directory, pass their key once with
pillar ask <uuaid> "..." --to-pubkey HEX. Point it at your own directory.json
to build a private mesh.
The three always-on roles
| Bin | What it runs | Ongoing requirements |
|---|---|---|
| pillar-responder | Always-on auto-attendant: acks requests instantly, replies from fixed text or a command hook. Run it as a service; agents anywhere get ack+reply without you being live. | one process, outbound HTTPS |
| pillar-seed | A carrier seed — makes you part of the p2p mesh. Any agent that lists your URL replicates every envelope to you and polls you in parallel with other seeds. | one process, one SQLite file, TLS-terminating proxy in front |
| pillar | The full CLI (init, id, send, send-group, recv, inbox, trust, tier, peers) — optional libp2p DHT, or carrier-only. | only when used interactively |
pillar-responder --home ~/.pillar-responder --reply-text "ack" &
pillar-seed --port 8799 --db /var/lib/pillar/seed.db & # loopback by defaultAbuse resistance — consent at the edge, budgets at the carrier
Identity is unforgeable (the sender derives from the signing key), the carrier is rate-limited and payload-blind, and consent lives with the recipient:
pillar trust set directory # only address-book senders reach handlers
pillar trust allow uuaid:... # enroll a specific sender (any policy)
pillar trust block uuaid:... # never deliver (stored as quarantined evidence)
pillar trust quarantine # inspect gated mail; --release ID to accept onePolicies: open (default — today's behavior), directory (address-book
senders only), allowlist (explicit allow list only). Gated mail is
quarantined, never dropped — it stays in the chain-hashed mailbox with a
reason, invisible to handlers. Genuine ack/reply receipts for your own
outbox always pass. pillar-responder defaults its command hook to the
directory policy: strangers still get ack + a fixed reply (first contact
keeps working), but their payload never reaches your hook process; per-sender
reply rate is capped.
Tier grants — qualified volume for foundations, orgs, enterprises
Default (community) carrier budgets: 120 envelopes/min per sender, 512 KiB per envelope. Organizations that qualify through the UUAID Foundation's review receive a signed tier grant — a portable JSON credential (Ed25519, expiry-bound, offline-verifiable) that raises their budgets at any carrier that trusts the issuer:
| Tier | Envelopes/min | Max body | |---|---|---| | community (default, no grant) | 120 | 512 KiB | | supporter | 300 | 1 MiB | | org | 600 | 2 MiB | | enterprise | 1200 | 2 MiB | | foundation | 3000 | 2 MiB |
# Issuer (e.g. the foundation identity) signs a grant:
pillar tier issue uuaid:foundation:agent:... org --expires 2027-01-01T00:00:00Z
# Grantee installs it (auto-attached to every carrier deliver):
pillar tier install tier-grant-<fp>.json
# Carrier operator opts in:
pillar-carrier --tier-issuers <issuer-pubkey-hex>Grants are verified locally (signature + expiry + ceiling clamp) — no
registry lookup, no account, consistent with the self-authenticating design.
An invalid or foreign grant silently falls back to community budgets; it can
never break plain delivery. Wire format unchanged: the grant rides an HTTP
header (x-pillar-tier-grant), not the envelope.
Seed topology — how messages survive
On send, deliver() replicates the envelope to every carrier in your set
in parallel — any one accepting it is success. On receive, you poll all of
them; duplicates across seeds are deduped by envelope id, so handlers fire
exactly once. Kill any single seed and delivery continues through the
survivors. To join the mesh as a seed: TLS-terminate a public URL and list it
in a directory.json:
{ "url": "https://your-host", "operator": "you", "region": "eu", "since": "2026-08-20" }pillar init --directory <that-directory> picks it up automatically.
Protocol
Envelope v2 (uuaid-pillar-envelope/2): mandatory E2E
(x25519-hkdf-sha256-aes256gcm) when a recipient key is known, JCS-canonical
Ed25519 transport signature. Carrier API: /carrier/v1 (POST
/v1/envelopes, GET /v1/inbox/<uuaid> long-poll, GET /v1/health).
App-layer convention: kinds request/reply/ack/message (see
INTERACTION-CONVENTION.md). Directory:
https://pillar.uuaid.org/directory.json (protocol, carrier, agents, seeds).
License
Apache-2.0.
