Private node (your personal runtime)

The marketplace model, a node holds the API key and you pay per call, is right for public services (search, data feeds, DeFi, LLMs) and exactly wrong for personal ones: no stranger should hold your Gmail token. The private node is for those: a small runtime you deploy that holds your secrets (encrypted), exposes them as tools, and is called directly by your agent for private steps, unmetered because you own it. The marketplace never sees your data.

A workflow spans both worlds: public steps go to paid marketplace nodes, personal steps to your private node, local and free. launch-agent-workflow.md runs a whole one end to end.

What it is

An HTTP/MCP-style server (offchain/src/private-node/) with the same tools/list / tools/call shape as a marketplace node, but personal and free. Behind it, an encrypted-at-rest secret store (AES-256-GCM, ~/.aioum-private/secrets.enc): secrets never touch disk in plaintext and never leave the box. In front of it, the connectors: memory, the local brain, and workspace, a folder on the machine your agents work in, both yours with no credentials; gmail / gcal (real Google OAuth behind your own client creds); a generic apikey fallback for any key-only API; and the models, hardware, training, skills, wallet, exchange and account connectors of Your node. Every tool does the thing it names; nothing answers without doing it.

Deploy — local or VPS (one image, both work)

Local is the recommended choice for privacy:

cd offchain
PRIVATE_NODE_PASSPHRASE=your-strong-pass npm run private-node     # binds 127.0.0.1:8770

Loopback only. Every route except GET /health and the OAuth callback (GET /oauth/:provider/callback, where Google redirects your browser back to) needs the node's bearer token. On first start the node writes one to ~/.aioum-private/token (mode 0600) and prints the path, never the token. Callers on the same machine read that file; set PRIVATE_NODE_TOKEN to supply your own. Leave PRIVATE_NODE_PASSPHRASE unset and the node encrypts with an auto-generated 0600 keyfile instead.

For agents that run while you are offline, the same image runs on a VPS, always on:

cd offchain/src/private-node
PRIVATE_NODE_PASSPHRASE=your-strong-pass docker compose up -d      # published on the host at 127.0.0.1:8770 only; Caddy on the same box proxies to it

Beyond localhost, put TLS in front (Caddy/nginx) and give each remote caller the bearer token once. The selling node it starts for you is separate: where it answers is set on the Earn page ("Where your node answers"), behind a relay it dials out to, so this machine's address never goes on chain, or at a direct https address on a server with no domain needed; the node writes it on chain itself with its certificate's fingerprint (going to production). A browser app on another origin needs PRIVATE_NODE_ALLOWED_ORIGIN (default http://localhost:3000).

Connect accounts

Connect the accounts your agent will act through. For Gmail and Calendar, set GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET; POST /connect {provider:"gmail"} returns an authUrl, and the callback stores the tokens encrypted (OAuth, not an API key, as with local MCP servers). sendGmail sends through the Gmail API and createGcalEvent creates the event on your primary calendar, each with the stored token, refreshed by the node when it expires. For any key-only API, POST /connect {provider:"apikey", apiKey:"…", baseUrl:"https://…"} stores the key encrypted and fixes where it may go: the call tool resolves its path against that base URL and refuses any other origin. memory and workspace need no connect: the moment the node is up, remember and recall keep and find your context, and ws.write and ws.read put files on your machine and read them back.

An inbox of the agents' own: connect Agent inbox with an AgentMail API key (agentmail.to, free for three inboxes) and name the inbox, and your agents get an address to receive at and send from: agentmail.inbox, agentmail.send, agentmail.messages, agentmail.read, agentmail.reply, and agentmail.wait for the next message to arrive. The key stays in the node's encrypted store, and mail whose content came from the marketplace is not sent without your say, as with Gmail. It opens no accounts anywhere; it is one address.

GET /health lists connected providers (names only, never secrets); GET /connections returns the same with the token, and POST /disconnect removes one.

Your subscriptions and keys

Your Claude plan, your API keys and the keys your node's tools read are set on the node too, under Subscriptions and keys or with aioum subs add. Your own agents plan, judge and write with them; nobody else's can, and they are in no list of tools (Your subscriptions and keys).

Local brain (your context)

The node also remembers for you. The local brain (offchain/src/private-node/memory.ts) is a persistent, capped, user-owned context store of facts, preferences, results and notes, kept on your private node and recalled by keyword (recall). It is exposed two ways. As a memory connector it offers remember / recall tools, so a workflow step reads or writes it like any other tool. As REST endpoints, GET /memory and POST /memory recall and store, DELETE /memory/:id forgets, and POST /forget-all wipes the store in memory and on disk (secrets and schedules untouched); all bearer-token protected, like the rest of the node.

The invariant is data minimization: your context stays on the private node. The orchestrator recalls only the relevant slice for a step and sends only the minimal per-call inputs to marketplace nodes; a paid node gets the bytes its call needs, never your whole context. The store is capped, so it cannot grow unbounded, and encrypted at rest with the secret store's key. In a workflow, memory steps are personal: steps like every private-node step: local, free, never routed to the marketplace.

Backups

POST /export returns the node's encrypted state as one blob under a passphrase you choose (12 characters minimum). Secrets and memory inside stay encrypted under the node's own at-rest key, so restoring on another machine needs the same PRIVATE_NODE_PASSPHRASE or a copy of its .secretkey. POST /restore loads a blob; restart the node afterwards. The bearer token is never included. From the command line:

npm run private-node:backup -- export  ./aioum-backup.bin
npm run private-node:backup -- restore ./aioum-backup.bin

Security model

ConcernHow
Secrets at restAES-256-GCM, key from your passphrase (scrypt) or an auto-generated 0600 keyfile. No plaintext on disk; the local brain is encrypted the same way.
Who can call itBearer token on every route except /health and the OAuth callback (/oauth/:provider/callback, the redirect target Google calls after you approve a connection); a CORS allowlist plus Host/Origin checks against DNS rebinding; TLS when exposed. PRIVATE_NODE_TRUST_LOOPBACK=1 optionally skips the token for local non-browser callers (off by default; single-user machines only).
Does anyone else see your dataNo. Personal steps hit your node directly; the marketplace never receives them. Untrusted marketplace output can never fill a personal tool's recipient fields, and sensitive data leaves the node only through an explicit per-step consent gate.
PaymentsNone — you own the node, so personal calls are free (no voucher/settlement).

The scope, plainly: the private node keeps personal data on a box you control. A public marketplace node still sees the request it serves, because it must; what protects you there is trust, stake and reputation, not cryptography. See architecture.md §verification.

How a workflow uses it

The private node runs the workflow itself. Its executor (offchain/src/private-node/executor.ts) calls its own connectors in-process for personal steps and pays marketplace nodes for public steps from the node's budget-capped agent wallet (open session → sign receipt → call → settle), stopping at the budget. A byte cap on data sent to paid nodes blocks a run that ships more context than expected rather than silently truncating it.

Scheduled runs use the same executor: POST /schedule {name, steps, intervalSeconds, budgetUsd} creates a cron job, POST /schedule/:id/run fires it now, POST /schedule/:id/toggle pauses or resumes it, DELETE /schedule/:id removes it, GET /schedules lists them. Jobs persist to disk, each run holds a lease so two ticks never double-pay, and a job that comes due while the node is off for hours is skipped rather than fired in a burst on restart.

You now have a selling node with its catalogue of adapters, and a private node that keeps your own secrets and pays the marketplace from a capped wallet. Each reads its settings from the environment.

Next: Every setting — every environment variable each runtime reads, its default as the code reads it, and what it changes.