Design
What PAC is meant to be, as a system: its concepts, the fixed core every node runs, and the kinds of plugin that extend it. Nothing on this page names a package, a path, a port or a number. What of this exists today, and where, is the architecture page's job; why PAC exists at all is why.
Terms
- Node -- one running instance of PAC, acting for a single principal: a person, a group, a company, or another agent. The network is the set of such nodes.
- Peer -- another node, as seen from this one.
- Identity -- a keypair a node presents to peers, its id derived from the public key. A node holds one or more, and a peer cannot tell that two of them are the same node.
- Flow -- an installable program that runs multi-step interactions with peers: it defines the messages, the state each side keeps, and the points where a model is invoked.
- Session -- one run of a flow between nodes.
- Message -- one signed envelope from node to node.
- Artifact -- a versioned object two nodes negotiate over. Each change is a revision, signed by its author, and agreement is a mark on one revision.
- Authorization -- permission from the principal for an action. Policy is one mechanism for expressing it in advance; what policy does not cover is asked of the principal and answered one action at a time.
- UX -- a way for the principal to drive the node: a cli, a page, an MCP server, a chat surface. A client of the control API, not a plugin the node loads.
- Trace -- the attributed record of what happened.
- Plugin -- anything that plugs into the node. The core plugins specified below are what a node is: every node runs all of them. Everything a principal installs is a non-core plugin: the adapters, the flow services, and the flows.
The shape of a node
Everything a node runs is a plugin, and every kind has the same shape: a manifest declaring what it is and what it will request, reach into the node only through the core plugins' operations, one folder of its own, and a trace of what it did. What separates the kinds is what each one plugs into.
Most non-core plugins are adapters: one outside system bound to one of the node's ports. What separates one group of adapters from another is the port -- which core plugin calls it, and what it is handed -- not whether it reaches outside the node, since all of them do. That is what lets implementations be swapped or run side by side, and it is why the groups are named for the core plugin whose port they plug into: a new group is a new port on a core plugin, and one that grows a port leaves the others as they were.
| group | port on | for example |
|---|---|---|
| data adapters | data | a source that ingests something the principal exported; an operation that applies a source somewhere sensitive -- a payment rail, an encrypted submission, a compiler |
| intelligence adapters | intelligence | an executor a run runs on -- a cloud model, a local one, a personal agent's command line; a tool the model may call |
| network adapters | network | a transport that moves envelopes -- the public internet, a private network, a local mesh |
| discovery adapters | discovery | a whole mechanism for being found -- a directory, published claims, a distributed registry |
| package sources | host | where a plugin's bytes come from -- a registry, a git remote, the checkouts beside a development node |
A tool and an operation differ in who holds the material: a tool is invoked by the model with arguments the model chose; an operation is performed on the flow's behalf with material the flow named and never held.
Three kinds are not adapters, because they back no core plugin:
- Flows -- peer-facing behaviour, loaded by the host. These are the applications.
- Flow services -- shared machinery the core does not provide, called by flows rather than spoken on the wire: a versioned artifact store; the suggest-and-agree rounds every negotiation runs; durable records a plugin keeps, with the caller derived rather than named so no plugin can open another's; an intent router that decides what a principal's sentence is asking for and starts it as a call on the chosen flow's own route, so the work is attributed to that flow and not to the router.
- UX -- new ways for the principal to drive the node; clients of the control API.
the principal
│
clients of the control API (not plugins)
│
────────────────────────────── control API ─────────────────────────────
flows *
│
─────────── the core plugins' caller-aware operations ───────────
flow services *
┌───────────┐ ┌───────────┐ ┌──────────────┐ ┌────────────────┐ ┌──────────┐
│ network │ │ discovery │ │ intelligence │ │ data │ │ identity │
└─────┬─────┘ └─────┬─────┘ └──────┬───────┘ └───────┬────────┘ └──────────┘
│ │ │ │
transports * discovery executors * sources *
│ adapters * tools * operations *
│ │ │ │
──────┼─────────────┼─────────────┼───────────────┼── trace ─────────
│ │ │ │
peers directories, models, outside services
(other nodes) published claims tools
* = non-core plugins
The two core plugins not drawn as boxes are the lines: the host loads everything above the seam, and the api is the control API.
Each plugin, core or not, owns one folder under the node's data directory and keeps everything of its own in it: its databases, its keys, its files, whatever a run of it produced. There is no shared store and no separate vault, and no plugin reaches into another's folder. What one holds is reached by asking it, which is what every operation below already is.
Architectural style
Two things are described here and they have different shapes. A node is a microkernel: a minimal core the node always runs, and all other behaviour in plugins that plug in through declared contracts. Flows are the applications of this kernel; the adapter kinds each back one core plugin, and the rest back none. Each seam also has the shape of ports and adapters: the core plugin defines the contract and owns everything above it, and a non-core plugin adapts one implementation to it, which is what lets implementations be swapped or run side by side without the core changing.
The network has no kernel and no core, because it is not a program: it is the set of nodes, and every one of them is somebody else's. Its shape is peer-to-peer -- discovery introduces peers, and from then on nodes message each other directly, with nothing in the middle of the data path -- and what holds it together is the envelope rather than any shared installation. Two nodes interoperate because they speak it and run a flow of the same id and version, not because they were built alike, so nothing here says what a peer must be made of.
Inside a node, flow execution is event-driven: a flow reacts to a delivered event -- a message arriving, a turn of one of its runs settling -- calls operations, and returns. Between nodes, delivery is asynchronous messaging with an outbox, retries, and deduplication on the receiving side.
The same boundaries carry three principles. Separation of concerns: each core plugin owns one responsibility and states what it is not responsible for, so interpreting data, choosing peers, and moving bytes are never the same plugin's job. Least privilege: a plugin is authorized against the manifest it declares, reaches the node only through the core plugins' operations, and writes only its own folder. Complete mediation is the principle the seams are shaped for: every use of a data source is meant to pass through one check, and sensitive operations are performed on the flow's behalf, so raw contents, credentials, and keys never enter flow code and a check cannot be avoided by going around the plugin that enforces it.
The core plugins
Each is specified by what it owns, the operations it offers, and what it is not responsible for.
Identity
A node holds one node key and one or more network identities. The node key is exactly one: it signs the trace's checkpoints and names the node to its principal, and it need never reach a peer. A network identity is a keypair a peer sees, its id derived from the public key, and a node may hold several, so one node can present faces to different peers that they cannot link to each other. This plugin holds the keys and offers signing and verification to the others; the material never leaves it. A session binds to one identity when it opens and everything on it is signed by that one, which is what makes reputation accrue to an identity rather than to a node -- the cost of being unlinkable -- while the trace, being local, sees across all of them.
- Provides: the node's id and the identities it can present; sign, as the node or as a named identity; verify a peer's signature against its id.
- Not responsible for: deciding trust, or choosing which identity a session presents -- that is policy. Reputation is built above identity, on behaviour over time, per identity.
Network
Everything required to talk to other nodes, and nothing that moves bytes itself: the wire is a transport underneath, and this plugin is the layer above it. Every message is a signed envelope: sender and recipient, the sender's public key, the plugin id and version both ends run, the session, a unique message id, and the payload. The sender's id is derived from the key it carries, so any node can verify any envelope with no prior introduction, and first contact needs no handshake. Flows hand their outbound messages to this plugin and never touch the wire; it sees all inbound traffic, verifies it against the sender's identity, and routes each message to the plugin and session it belongs to. An envelope that fails verification, or names a plugin or version this node does not run, is rejected and traced. Delivery is at-least-once; ordering across retries is not guaranteed.
- Provides: deliver an envelope; verify and route inbound; report delivery failure.
- Not responsible for: choosing recipients (discovery) or interpreting payloads (the flow).
Discovery
How flows find work and the peers offering it. A flow states a need and gets back candidates, and a candidate is neither a node nor an endpoint on one: it is an advertisement bound to a peer -- the offer, and who is making it. What a node advertises is what it is willing to act on, so a flow finds an offer it can answer and receives the peer as part of the answer, rather than looking a node up and asking what it has. Nothing makes a node discoverable, and a node has no surface a peer can enumerate: what a peer can see is what the node chose to advertise.
A peer is named by an identity and located by addresses that belong to its transports, which change and are refreshed on every query. There is no one way to be findable, so there is no one provider: an adapter implements a whole mechanism -- presence, advertising, withdrawal, query -- and a node runs as many as its principal installed. They are not ranked, and a need names the providers it is for, because mechanisms differ in exactly the ways a flow cares about: how narrowly a need can be asked, how fresh presence is, what a candidate carries, and who can see that the asking happened.
One convention sits above every adapter: a flow that wants to be found advertises itself under one well-known kind, and a peer queries that kind to learn which live nodes speak it. The node advertises nothing on a flow's behalf, so a flow that says nothing is not findable, and that is the flow's choice.
- Provides: publish an advertisement, and withdraw it; query for candidates matching a need, over the providers the need names. Presence is upkeep the plugin does, so a provider that has not heard from a node lately stops returning its advertisements.
- Not responsible for: verifying or choosing a peer -- it returns candidates and the flow judges them. Nor for addressing. Nor for reconciling providers with each other: a candidate stays attributed to the mechanism that produced it.
Intelligence
The node's intelligence. A flow is mostly a state machine, and when a step calls for judgment -- what to propose, whether to accept, how to reply -- the flow starts a run and appends turns to it. A run is a conversation: its turns run in order, a later turn continues the session the first one opened, and a run stays open until the flow closes it. A run names the executor that will run its turns and carries two things the kernel stores and never reads: a payload for the executor, in whatever shape the executor defines, and metadata for the flow, echoed on every event about the run so a handler finds its own record. What the executor produces comes back to the flow exactly as produced, or as a structured error saying why it produced nothing. The kernel composes no prompt, parses no output, enforces no budget, and records no cost: what a turn means is the flow's to decide, and what a payload means is the executor's. A failed turn is not a failed run; the flow decides whether to append another.
Turns execute in a process of their own that never opens the node's store, because the model's process is the least trusted thing in the node: it runs over material a flow chose to show it, under instructions nobody here reviewed. Which executor backs a run -- a cloud model, a local one, a personal agent -- is named per run, so a node is never tied to one model, or to models that are language models at all. Tools come from tool adapters, and a tool call with side effects is a gated action like any other.
- Provides: start a run; append a turn; close a run; each settled turn comes back to the flow that started the run as one event, verbatim.
- Not responsible for: deciding when to reason, what a turn is asked, or what to do with what came back. It runs only when a flow appends a turn, and it interprets nothing.
Data
Designed, not built. This is the specification the stand-ins are measured against; the architecture page says what stands in today.
A collection of the principal's data sources -- exported data, credentials, payment instruments, skills. Sources are created and kept current by source adapters, and each source is owned by the adapter that writes it. Data policies are how authorization over sources is expressed and enforced: which flows and peers may learn what about a source, which uses are allowed on what terms, and what must be asked of the principal instead. When a flow needs a source applied somewhere -- staked into a joint computation, presented to an endpoint -- this plugin performs the operation on the flow's behalf, so contents never cross into flow code. That is the one verb: the flow names what to use and where it goes, and never holds what it named.
- Provides: the source registry; source writes by owning adapters; the policy check every access passes through; execution of the operations that apply sources on a flow's behalf; the authorization queue, whose decisions come back as flow events the way settled turns do.
- Not responsible for: interpreting the data. That is flow work.
API
One control API, and many faces on it: a cli, a page, an MCP server, a chat surface, or anything else that speaks to the node. This is where the principal watches what their representative is doing, manages what it holds, and grants or refuses authorization. The faces are UX plugins -- clients of this API rather than parts of it. Core routes are reserved by construction and everything a non-core plugin serves lives under a path of its own, so no plugin has a path with which to claim a core route; a plugin's route belongs to the plugin that declared it, so disabling the plugin withdraws the route too.
- Provides: the control API itself -- watch, manage, decide -- and the seam a plugin's own routes are dispatched through.
- Not responsible for: policy decisions themselves -- it renders them. Nor for what any one UX looks like.
Host
The core plugin that manages installed non-core plugins. The node boots it along with every other core plugin; the host then loads what the principal has installed and manages each one the way an OS manages processes: add, remove, enable, disable, kill. It does not sit between a flow and the rest of the node. A flow reaches the other core plugins directly through the caller-aware operations they themselves offer, and every one of those operations knows which plugin called it, checks policy, and leaves a trace. Because everything a flow registers goes through those operations, disabling it withdraws all of it, and a flow whose dependencies are missing waits rather than failing.
It keeps one record of the installed set and nothing beside it: what is installed, what each plugin declares about itself, and what vouched for it live together, so nothing has to be kept in step. A package is either fetched through a package source, which says what vouches for its bytes, or already on the machine, which nothing vouches for; whether vouching is required is the host's one policy. Nor does the host run anything on a plugin's behalf: there are no declared schedules, and a plugin's background work is its own, registered the way everything else it does is, which is what lets disabling the plugin stop it along with everything else it registered.
- Provides: install, remove, enable, disable, kill; the package source seam, and whether vouching is required over it; each plugin's folder.
- Not responsible for: what flows decide to do, or their background work.
Trace
Traceability first, with observability derived from it rather than collected beside it. Observability asks whether an operator can infer what a system is doing, and normally pays for the answer with sampling, aggregation, and continuous export; traceability asks what this node did, on whose behalf, and whether the record of it can be shown to somebody else and believed. Traceability is the stricter of the two -- nothing sampled, attribution derived rather than supplied, the record tamper-evident -- which is what makes a trace a sound source to read metrics out of instead of standing up a second pipeline. The containment stops at what this plugin is willing to hold: it is deliberately small, low-volume, and synchronously written, and keeps only a bounded tail, so anything high-frequency or long-horizon is materialized outside it.
Every message sent, model run executed, revision authored, authorization granted, and flow step taken leaves a record, attributed to the identities involved, so you can always reconstruct what your representative did and why. Each record carries the hash of the one before it and the head is signed with the node key, so an omission breaks the chain and an excerpt shared with a peer verifies against a signed checkpoint. Traces are local-first: sharing any part of one is a policy decision, and attribution is strong enough that reputation can later be built on it, per identity rather than per node.
- Provides: append, from every core plugin; reconstruct a session, a day, a peer's history; a signed checkpoint over the chain.
- Not responsible for: evaluating what it records.
Flows
A flow declares up front what it is and what it will need -- the data it wants to learn about, the actions it wants gated -- so the principal can make an informed install decision.
Once installed, a flow reaches the rest of the node only through the core plugins' caller-aware operations: through them it messages peers, finds them, invokes the intelligence loop, keeps its own state, negotiates over a shared versioned artifact, learns what policy allows about the principal's sources, asks the principal for authorization, and leaves traces. Those operations never hand over raw contents, credentials, or keys: a flow names what to use and where it should go, and the operation is performed on its behalf.
There is no core flow beneath the others. The fixed layer is the envelope: every node speaks it, and the plugin id and version it carries are what route a message to the flow and session that own it, so a flow is an application on that layer rather than a continuation of some base conversation. What flows share is factored downward instead, into flow services -- called rather than spoken -- so common machinery costs nothing on the wire and no session ever carries two names.
Worked example: a joint query
Two principals ask "which songs would we both like?" without either seeing the other's listening data. The flow, with the core plugin doing the work in parentheses:
- Each principal ingests listening data as a named source (data).
- Node A publishes the query so peers can find it (discovery).
- Node B sees it and triages: one model run over what B holds decides whether this principal can contribute (intelligence).
- A probes B; a session opens (network, identity). B answers from its stored verdict, with no new model run.
- Both are willing, so the initiator is chosen by the byte order of the two identities on the session: both sides compute the same answer from ids they already hold, and exactly one initiation exists.
- The nodes negotiate objectives: each side's agent proposes and reviews revisions of a shared artifact until one side marks the other's revision agreed and both then hold that same agreed revision, within a bounded number of rounds (intelligence, an artifact store, network). Neither agent sees the other node's data.
- For each agreed objective, the same machinery negotiates a program the same way: propose, review, agree.
- The flow verifies a joint-computation box both sides can check and neither owns, confirms its own copy of the agreed program by hash, stakes exactly the sources the objective names into it through an operation -- so the flow never holds them -- and fetches its own output (data, under authorization).
- One more model run summarizes the result for the principal (intelligence), who has been able to watch every step (api, trace).
Note what the node did not contain: the joint-computation box, the negotiation strategy, the triage question. Those belong to the flow. The node supplied messages, model runs, authorization, and traces. This is what a written-down flow looks like; the meta flow negotiates one like it from a principal's intent instead of shipping it.
Built today: architecture. What to clone: repositories.