The private node from the command line

A small runtime of your own next to the marketplace, wired to your agents from a shell. The node keeps your accounts, keys, memory and local tools on a machine you control, encrypted, and pays a marketplace node for the public steps of a workflow from a budget-capped wallet; nothing personal leaves it. aioum node … speaks to the node's API with the bearer token from its token file.

What you need

  • A machine: your laptop, or a VPS with Docker.
  • The repository, with npm install done in offchain/, and the marketplace node the private node pays through (MARKET_NODE_URL, http://127.0.0.1:8546 on the local development network).
  • A passphrase you keep. It is the key to everything the node stores; there is no recovery.
  • For Gmail or Google Calendar: a Google OAuth client, its GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in the node's environment, with http://127.0.0.1:8770/oauth/gmail/callback (and /oauth/gcal/callback) as its redirect URIs. For a key-based service: the key and the base URL of its API.

Steps

  1. Start the node, in a shell of its own. It binds to loopback only.

    cd offchain
    export PRIVATE_NODE_PASSPHRASE='a long passphrase you keep'
    export MARKET_NODE_URL=http://127.0.0.1:8546
    npm run private-node
    

    The first start writes a bearer token to ~/.aioum-private/token (mode 0600) and prints that path, never the token; the CLI reads the file by itself. On a server, docker compose up -d in offchain/src/private-node does the same in a container, with the data in the aioum-private volume and deploy/Caddyfile for TLS in front; read the token with docker compose exec private-node cat /data/token, set it as AIOUM_PRIVATE_NODE_TOKEN and the address as AIOUM_PRIVATE_NODE_URL.

  2. Confirm it is up, back in packages/cli.

    npx aioum node status
    

    The health line (ok, kind private-node), then one row per connector with its label, its auth type, whether it is connected and its tool names: workspace (a folder on your machine your agents work in), hf (Hugging Face models), hardware, train, skills and memory (the local brain), connected from the start; gmail, gcal, apikey, wallet, exchange, the OAuth accounts and ghost, connected below. A 401 here means the token the CLI found is not this node's.

  3. List the tools you can call now.

    npx aioum node tools
    

    Every tool of every connected connector, with what it does (--json adds each input schema): ws.list, ws.read, ws.write, ws.run, ws.fetch, ws.screenshot, ws.browse and ws.ffmpeg from the workspace, hf.install, hf.status, hf.list, hf.remove and hf.run, hardware.check and hardware.optimize, train.lora, train.distill, train.status, train.list and train.export, skills.install and skills.list, and remember and recall from memory. npx aioum node call hardware.check prints what this machine has (the same report npm run hardware:check in offchain/ writes to .deploy/hardware.json) (Your node).

  4. Put a file in the workspace and read it back.

    npx aioum node call ws.write --args '{"path":"hello.md","content":"hi"}'
    npx aioum node call ws.read --args '{"path":"hello.md"}'
    

    The first prints the file's workspace path, its URL on the node and its size in bytes; the second prints its content. A tool ran on your machine, a file exists on your disk (~/.aioum-private/workspace, or PRIVATE_NODE_WORKSPACE); nothing was paid and nothing left the box.

  5. Connect Gmail or Google Calendar.

    npx aioum node connect gmail
    

    It prints open this URL to authorise: and a URL on accounts.google.com: open it in your browser and approve. The node asks only for gmail.send (for gcal, calendar.events) plus offline access, receives the tokens at its callback, stores them encrypted, and the tab reads "Connected gmail. You can close this tab." sendGmail (to, subject, body) now appears in node tools, and after the same step with gcal, createGcalEvent (title, time); call them with node call as in step 4. The node refreshes an expired token by itself; if you revoke the grant at Google, the next call fails with gmail: re-connect required and you repeat this step. Without the two Google values in the node's environment the command fails with OAuth not configured.

  6. Connect a key-based service.

    npx aioum node connect apikey --key sk-your-key --base-url https://api.example.com/v1/
    

    Here --key is the provider's key, not your wallet's. It prints ok, connected apikey and the base URL. The key is sent as Authorization: Bearer <key>; add --header X-Api-Key --prefix "" when the API wants it another way. Its one tool is call: npx aioum node call call --args '{"path":"weather?city=Oslo"}' resolves the path against the base URL, takes an optional method (GET when unset) and JSON body, and answers with the status line and the reply's first 4,000 characters. A path that resolves anywhere but the origin you connected is refused before any request is made. A wallet, an exchange account or an OAuth account (Google Ads, X Ads and the rest) is connected the same way with its own fields and the caps you set, from the Your node cards on the marketplace page or POST /connect (Connect local services).

  7. Give the node a memory, then recall it.

    npx aioum node memory add "I prefer window seats" --kind preference --tags travel
    

    It prints the item with its id. --kind is fact, preference, result or note, and --tags is comma-separated. npx aioum node memory list shows everything, newest first; npx aioum node call recall --args '{"query":"window seats","limit":"5"}' answers • [preference] I prefer window seats; npx aioum node memory delete <id> forgets one item, and npx aioum node memory forget-all wipes the brain.

  8. Wire a workflow that feeds a marketplace step into a local one. Write job.json. A step whose category starts with personal: runs on your node, where the tool is looked up among your connectors; any other category is a marketplace listing, matched against the catalogue and paid from the job's budget (serviceId pins one node, maxPricePerCall caps its price). An input is a literal or a reference to an earlier step's output.

    {
      "name": "trending to memory",
      "intervalSeconds": 3600,
      "budgetUsd": 1,
      "steps": [
        {
          "id": "s1",
          "category": "mcp:web-search",
          "tool": "search",
          "inputs": { "query": { "kind": "literal", "value": "most trending stock today" } }
        },
        {
          "id": "s2",
          "category": "personal:memory",
          "tool": "remember",
          "inputs": {
            "text": { "kind": "ref", "fromStep": 0, "extract": "text" },
            "kind": { "kind": "literal", "value": "fact" },
            "tags": { "kind": "literal", "value": "stocks" }
          }
        }
      ]
    }
    
    npx aioum node schedule create --file job.json
    

    It prints the job with its id, interval, budget, whether it is enabled and its next run. It runs every hour from now; npx aioum node schedule toggle <id> pauses it and resumes it.

  9. Fire it once.

    npx aioum node schedule run <id>
    

    The job comes back with its latest run first: whether it succeeded, what it spent, and one output per step. Step 1 was paid on the marketplace with a signed receipt; step 2 ran on your node for free and kept that answer: npx aioum node memory list shows it as a note tagged untrusted:step0, although the step asked for a fact. Marketplace output is tagged untrusted, and two sinks refuse it: remember downgrades it to a note, and a recipient field (sendGmail's to, once Gmail is connected) stops the run with untrusted recipient blocked. Only your own memory add creates a fact or a preference.

  10. See every job.

    npx aioum node schedules
    

    One row per job: id, name, interval, budget, enabled, last run and next run. npx aioum node schedule delete <id> removes one.

  11. Back it up.

    npx aioum node backup export ./aioum-backup.bin --passphrase 'twelve chars or more'
    

    The node bundles secrets.enc, memory.json and jobs.json, never the token, encrypts the bundle under that passphrase (at least 12 characters, and separate from the node's own), and the CLI writes it to the file (mode 0600) and answers wrote <file> (N bytes, encrypted with your passphrase). The passphrase goes into your shell history like any argument; a space before the command keeps it out in most shells.

  12. Restore, then restart the node.

    npx aioum node backup restore ./aioum-backup.bin --passphrase 'twelve chars or more'
    

    It prints which files were restored and restart the private node to load restored state. The node that restores must run under the same PRIVATE_NODE_PASSPHRASE (or a copy of .secretkey) as the one that exported: the secrets and memory inside stay encrypted under it. A wrong passphrase fails with restore failed: wrong passphrase or corrupt backup.

  13. Remove a connection, or rotate one.

    npx aioum node disconnect apikey
    

    The secret is deleted from secrets.enc. To rotate a key, run step 5 or 6 again: the new secret replaces the old. To rotate the node's own token, delete ~/.aioum-private/token and restart the node.

What you have now

A private node on a machine you control, behind a bearer token, with an encrypted backup you can restore, and your agents acting as you without anyone else holding you: a memory, a workspace, email, a calendar and a keyed account answer on your node, every secret encrypted on your disk, a scheduled workflow handing a paid answer to a local tool, and marketplace bytes never choosing who gets the email or what you believe.

Next: Agents from the command line — one agent from flags or a file, a run and its receipts, and a second agent that hires the first.