Your node: workspace, models, wallet and accounts
One machine is yours: it runs your private node, and everything on it is a tool your agents get beside the paid listings, free, unmetered and private. A workspace with files, a shell, a browser and ffmpeg; models from Hugging Face installed on your hardware; a wallet and an exchange account with caps you set; your ad, analytics and publishing accounts through your own OAuth apps; your AI subscriptions and keys; the skills an agent loaded; a trainer for models of your own. The marketplace never sees any of it. The marketplace page shows them under Your node, one card per connector with Connect or Install, and the builder's Your node step lists their tools for an agent to tick.
Start the node in offchain/ with npm run private-node (Your private node).
The runner reaches it at AIOUM_PRIVATE_NODE_URL (http://127.0.0.1:8770) with the token from
~/.aioum-private/token; the app asks for that token once, under Your node, when it runs on
another machine. A run's log shows every step on your node as Local · tool, free.
The workspace
~/.aioum-private/workspace (PRIVATE_NODE_WORKSPACE) is the folder your agents work in.
Tools: ws.list {path?}, ws.read {path}, ws.write {path, content}, ws.run {cmd, timeoutMs?} (a shell inside the root, 120 s and 1 MB of output by default, up to 600 s),
ws.fetch {url, path} (a download), ws.screenshot {url, path?, fullPage?} and ws.browse {url}
(a headless Chromium through Playwright: npm i playwright && npx playwright install chromium in
offchain/, or PLAYWRIGHT_MODULE), and ws.ffmpeg {args} (ffmpeg on the PATH, with the
hardware decoder the optimizer found put in front of the input). A path outside the root is
refused. Every file has a URL, /workspace/<path> on the node behind its token, so a result an
agent renders shows inline on its page and a pipeline hands files from stage to stage. Whatever an
agent writes, downloads, screenshots or renders lands here; skills it loads go under skills/. A workspace file reaches the GPU rented for a project too: name it as a job's input
(/workspace/<path>) and the runner ships it to the pod.
Hugging Face models on your machine
hf.install {repo, kind, task?, quant?} installs a model from huggingface.co:
kind: "llm"pulls it into Ollama,ollama pull hf.co/<repo>[:<quant>]. Ollama must answer atOLLAMA_HOST(127.0.0.1:11434); else "Ollama is not reachable … Install it from https://ollama.com/download and start it".kind: "pipeline"makes a Python venv at~/.aioum-private/models/venvwith transformers, diffusers, torch, torchvision, accelerate, safetensors and soundfile (a few hundred MB the first time), downloads the model, and serves it through a small worker on127.0.0.1:8790that loadspipeline(task, model)on demand. Without python3: "install Python 3.9 or newer".
The install is a job: the answer carries its id and hf.status {id} reports progress and the
log; nothing is pretended. Before anything downloads the node reads the model's size from the Hub
and refuses one your machine cannot hold, naming the largest quantization that fits, a smaller
variant of the family, or a rented GPU (the compute:gpu listings). hf.list shows what is
installed and whether Ollama, Python and the worker are up; hf.remove {repo} removes one;
hf.run {repo, inputs} runs it (an LLM through Ollama's /api/generate, a pipeline through the
worker; images and audio land in the workspace under hf/), tuned for the machine. On the
marketplace, the Hugging Face models card takes a repo id, LLM, through Ollama or
Pipeline, in Python, and Install.
What the machine can do, and the settings that fit it
npm run hardware:check reads the machine, CPU cores, RAM, GPUs and VRAM (nvidia-smi, rocm-smi,
Apple Silicon through system_profiler), disk, Docker and the NVIDIA runtime, Ollama, ffmpeg's
hardware encoders, python3, and writes .deploy/hardware.json. The catalogue reads it: a local
template with requires (gpu, vramGb, ramGb, cpus) is listed only when the machine meets
it, else skipped with "needs a GPU with ≥ 12 GB (this machine: none); rent one: the compute:gpu
listings". hardware.check {refresh?} on the node shows the same report.
hardware.optimize {kind, model?, …} gives the settings that fit, kind one of ollama,
diffusers, whisper, ffmpeg, docker or train, each rule cited from the project's own
documentation: the largest quantization and context that fit for Ollama; dtype, attention
slicing, VAE tiling and CPU offload for diffusers; compute type and model size for whisper; the
hardware decoder and encoder for ffmpeg; --gpus, shm size and memory for docker. hf.run, the
compute listing, ws.ffmpeg and the trainer apply them on their own, and a run's log says what
was set: "Tuned for Apple M4: fp16, attention slicing". npm run hardware:optimize -- --kind diffusers --model <repo> prints the same from a shell. The full story, with training:
Make your own model.
Task and hardware optimizers
Two listings anyone can run, and the runner's built-in fallback when none is live. The task
optimizer (optimize:prompt, templates optimize-prompt-openai, -gemini, -anthropic) is
a language model behind one system prompt: optimize.prompt {prompt, budgetUsdc} returns
{brief: {goal, deliverable, constraints, qualityBar, references, kinds}} as JSON, nothing the
person did not say and no invented numbers. The hardware optimizer (optimize:hardware,
template optimize-hardware, keyless, the optimizer adapter) runs the rules of
hardware.ts and optimize.ts on what a buyer posts: optimize.hardware {needs, hardware, keys?, budgetUsdc?, hours?, prices?} with the needs as kinds (["video:gen", "audio:tts", "compute:gpu"]), the buyer's own hardware.check report and the names of the rental keys it
holds, never a value. It answers {plan: [{need, where: node | rent, why, stage, estimates}], estimates: [{stage, local: {can, minutes?, why}, rented: {minutes, why}, where, assumes}], jobs?, rent?: {provider, template, instance, pricePerHour?, hours, keyed, withinBudget?}, missingKeys, localMaxMin, rentedMinutes, line, rules}, localMaxMin (10 minutes per stage), job (the figures: clips,
seconds, fps, the cut's length, the answer's tokens, the training steps) and jobs (a live
compute listing that runs jobs) optional. The rules: frame analysis (4 GB) and render (12 GB)
are CUDA containers, so a node runs them only with an NVIDIA GPU under Docker's NVIDIA runtime,
never Apple Silicon or a CPU; assembly is ffmpeg with the encoder the build lists, on the node;
a local model runs over MPS, CUDA or the CPU when it fits and answers within the limit; a
training run takes trainingPlan's figure. Every estimate is stated work over a stated class:
captions per second, render frames per second, memory bandwidth for a model's tokens, training
steps per second, for Ampere-or-newer NVIDIA, Volta and Turing, older NVIDIA, Apple Silicon over
MPS and a CPU, plus a container's fixed start (120 s rented, 60 s on the node), each rule naming
its source (the NVIDIA compute-capability list and A100 datasheet, Apple's M4 memory bandwidth,
llama.cpp's bits per weight, gpu-jobs.ts's 1 fps and 24 fps, the NVIDIA encode matrix: A100
and H100 carry no NVENC, and a rented pod's own probe of h264_nvenc at start, which moves the
assembly's rented figure to the libx264 rate where the session will not open) and saying which
figures are ours. A stage over the limit, or
impossible on the node, leaves it: to the job listing when one is live, else to the first
provider whose key the buyer holds, named for its instance and price even without a key. This
Mac, a video job, no key: video:gen and compute:gpu rent, the rest stays, and the line reads
"Hardware: rent a GPU for frame analysis and render (your M4 cannot run the CUDA jobs; assembly
stays on your node) · RunPod GeForce RTX 4090, about 7 min · missing key: RUNPOD_API_KEY (or
VAST_API_KEY, LAMBDA_API_KEY, DIGITALOCEAN_TOKEN)". With a RunPod key: "Hardware: renting a
GeForce RTX 4090 on RunPod for frame analysis and render (…) · ~$0.34/h, about 7 min, 1 h
planned". The runner's setup calls a live one first, takes its answer when it carries the
estimates, and applies the same rules itself otherwise (Build an agent network).
A wallet and an exchange, with caps
Wallet holds an EVM key, encrypted on the node: wallet.connect takes a private key or a
recovery phrase, the chain id (Ethereum, Base, Arbitrum, OP Mainnet, Polygon, BNB Chain,
Avalanche), its RPC URL, and two caps, Most one transaction may spend (USD) and Most all
transactions may spend per day (USD). Tools: wallet.address, wallet.balance {token?},
wallet.send {to, amount, token?}, wallet.swap {sell, buy, amount, slippageBps?} (a 0x quote,
ZEROX_API_KEY on the node), wallet.sign {message}, wallet.execute (a transaction a quote
gave: exact allowance, simulation, receipt) and wallet.signOrder (a CoW order). Exchange
holds API keys for Binance, Coinbase, Kraken, Bybit or OKX, or Alpaca for US stocks and ETFs on your own
brokerage account (paper money unless you say live), with the same two caps: exchange.balances,
exchange.ticker {symbol}, exchange.orderbook {symbol, depth?}, exchange.order {symbol, side, type, amount, price?}, exchange.cancel {id}, exchange.orders, each signed on the node the way
the venue documents.
A cap of 0, the default, refuses every spend: an agent can look but not move money. Spends are valued in USD through the chain's Chainlink feed, or the venue's own ticker; on a chain with no feed the caps apply in the asset's units. A refusal names the cap and today's total; the day resets at 00:00 UTC. No agent can raise a cap: only you, by connecting again. The trading pipelines and pay in any token run through these tools and stop at these caps.
Your accounts
Gmail and Google Calendar connect through a Google OAuth client in the node's environment
(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), asking for gmail.send and gmail.readonly, or
calendar.events: sendGmail, readGmail, createGcalEvent. The other accounts are OAuth apps
you make at the provider and paste into the card: the card says "Make an app at the provider with
this callback URL, then paste its credentials here", and the callback is
<node URL>/oauth/<provider>/callback, http://127.0.0.1:8770/oauth/googleads/callback on a
laptop, PRIVATE_NODE_PUBLIC_URL in front of it on a server. Connect opens the provider's
consent page; the tokens land on your node, encrypted, and renew themselves.
Agent inbox gives the agents an email address of their own on AgentMail (an API key from agentmail.to; free for three inboxes): receive, read, reply, send, and wait for the next message. The key stays on the node; marketplace-derived content is never sent without your say.
| Card | Make at the provider | Tools |
|---|---|---|
| Google Ads | a Web application OAuth client in the Google Cloud console, plus the developer token from the Google Ads API Center (and a manager account id when you act through one) | googleads.search, googleads.campaign.create, .pause, .enable |
| YouTube, Google Analytics, Search Console | the same kind of Google OAuth client | youtube.comment, youtube.analytics; ga.report; gsc.query |
| TikTok Ads | an app in TikTok for Business (My Apps › Basic Information): its App ID, secret and the advertiser authorization URL, with this node's callback as its redirect | tiktokads.campaigns, .campaign.create, .adgroups, .ads, .report and more |
| Reddit Ads | a web app at reddit.com/prefs/apps, plus your username for the User-Agent Reddit asks for | redditads.me, .businesses, .accounts, .campaigns, .campaign.create, .report |
| Snapchat Ads | an OAuth app in Snap Business Manager › Business Details | snapads.accounts, .campaigns, .adsquads, .ads, .stats |
| Hootsuite | a Hootsuite developer app | hootsuite.profiles, hootsuite.post |
| X Ads | an X developer app with Ads API access and this callback URL; requests are signed with OAuth 1.0a and the tokens do not expire | xads.accounts, .campaigns, .lineItems, .promotedTweets, .stats, xads.call |
| Ghost | a custom integration on your Ghost site: its admin URL and Admin API key | ghost.publish {title, html, status?}, ghost.posts |
oauth.call {provider, method, url, body?, headers?} reaches any endpoint of a connected
provider with its token. The builder's Its data step shows every connected account, and their
tools sit under Your node.
Your subscriptions and keys
You pay for a Claude plan, or you hold an API key. Set it once on your node and your own agents think with it: nothing per call on a plan, no key in any file an agent can read. They are yours alone. They are never listed, never sold, in no agent's list of tools, and an agent that belongs to anyone else is refused.
Under Your node, the Subscriptions and keys card shows each one with its state: Connected and the model that answered, Limit reached until 14:00, Not funded, Not reachable with the reason. Add asks what it is and, for a key, the key, then makes one small real call: nothing shows as connected before that call answered. Test makes the call again and says the model, the seconds, and for a plan whether a usage limit is active. Take keys from your .env lists the keys found there by name; tick the ones your node takes. A key goes to your node, is kept encrypted there, and is never shown again.
| Kind | How it is reached | Its models |
|---|---|---|
claude-plan | Claude Code, signed in on this machine, run headless; the newest one found, or the binary you pin | the names your Claude Code takes (fable, opus, sonnet on 2.1), or a model's full name |
anthropic | Anthropic's API, with your key | the key's own list |
openai | any OpenAI-compatible API at the base URL you give: OpenAI, OpenRouter, Groq, Together, a server of your own | the endpoint's own list |
ollama | Ollama on this machine, http://127.0.0.1:11434 | Ollama's own list |
huggingface, fal, runpod, replicate | keys your node's tools read |
Order. Planning, judging, writing and your studio each have an order. The first in it is asked. At its usage limit or out of credit it is marked with the reason and the time it resets, and the next is asked; the job goes on. One you leave out of a purpose is never asked for it. Until you set an order it is your plan, then your key, then your local model. Open Order on the card and move them with the arrows.
aioum subs add claude-plan --model fable
aioum subs add anthropic --key-env ANTHROPIC_API_KEY
aioum subs add openai --key-env OPENROUTER_API_KEY --base-url https://openrouter.ai/api/v1
aioum subs add ollama
aioum subs list
aioum subs test <id>
aioum subs model <id> fable # the model it asks for from now on; none: its own default
aioum subs use planner <id> <id>
aioum subs import # the keys in your .env, by name
aioum subs import FAL_KEY # your node takes this one and tests it
aioum subs owners --add <owner id>
aioum subs remove <id>
Through MCP the same are aioum_subs_list, aioum_subs_add, aioum_subs_test,
aioum_subs_remove, aioum_subs_use and aioum_subs_import.
The card adds your owner id, the one your agents carry, when you add a subscription;
aioum subs owners --add names another of yours. The runner reads your node at its start and
every two minutes, so a new login or a new key is used without a restart
(AIOUM_AGENTS_SUBS=0 turns the reading off). With the node stopped the runner plans as before:
Claude Code found on this machine, ANTHROPIC_API_KEY, a local Ollama.
On the node the tools are subs.list, subs.add {kind, label?, secret?, baseUrl?, path?, model?, owner?}, subs.remove {id}, subs.test {id}, subs.ask {id?, purpose?, system, prompt, schema?, images?, model?}, subs.use {purpose, order}, subs.import {names?} and subs.owners {add?, remove?}. They answer only a request that carries your node's token, and tools/list, /health
and /connections never name them.
Your own runner
Your node runs an agent runner of its own. It starts with the node, comes back by itself when it
stops, and stops with the node. Your agents, their projects, their runs and what they spent stay
on your machine, in ~/.aioum-private/runner/.
Its wallet is yours. The runner pays every call your agents make from the wallet connected on
your node. The key stays encrypted on the node and is handed to the runner as it starts: no
file, no log and no command line holds it. What your agents spend is bound by the budget you give
each one; the wallet's two caps bind the wallet.* tools. On the local development network, with
no wallet connected, the node makes one, keeps it encrypted, and funds it with gas and 100 test
USDC. On any other chain you connect your wallet first, and the runner starts with it.
It works for one owner. Everything it holds is its owner's: the wallet's address, or the id
in PRIVATE_NODE_OWNER. A request that names another owner is refused. The runner listens on
127.0.0.1:8771 and answers only your node's token.
It plans with what you already have. The subscriptions and keys on your node are its planners, in your order, with nothing to set up.
The app finds it at your node. Under Your network, My own runner shows the runner's
wallet, what the wallet holds and what it plans with. Use my own runner points the app at
it, and In use shows once it is. The app needs one address, your node's: the runner answers
under /runner there. On the machine that runs the app, the app reaches your node with the
token from disk. For a node on another machine, put its address under Advanced and its token
under Your node.
TOKEN=$(cat ~/.aioum-private/token)
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8770/runner # running or not, its wallet, its balance, its planner
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8770/runner/agents # the runner itself, behind your node's token
scripts/devstack.sh status # the row own-runner: its state, port and folder
PRIVATE_NODE_RUNNER=0 starts the node without a runner; PRIVATE_NODE_RUNNER_PORT moves its
port. A snapshot holds the runner's records with the rest of your node, and
npm run network -- counts names them as own runner.
Your agents come with you. The agents you made before are on the runner you used then. Under My own runner the app says how many wait there, and Bring my agents over copies them: your agents, their projects, their runs and their spend, under the ids they had.
aioum node runner import --dry-run # how many, nothing written
aioum node runner import
Nothing is taken off the runner they come from. An agent that is published there goes on answering its listing there; its copy on your runner is not published. A project that was running is paused on your runner: your wallet pays for nothing you did not start.
Selling from your node. Go live on Earn with no selling node of your
own at hand, and your private node starts one: the catalogue node's code as a child of your
node on the port after its runner's, your wallet its operator, the listing you went live with
its catalogue. The keys the listing needs are kept under ~/.aioum-private/selling/, readable by
you alone, and handed to the selling node as it starts; your wallet's key goes over a pipe and
never into a file, a command line or an environment a process listing shows. It puts up 100 ZAI
of your own and the listing's bond, and the staking node backs the rest to the floor; from then
on the app reads your listings, their backing and their live state from that node. It comes back
with your node and stops with it. PRIVATE_NODE_SELLING_URL is the address buyers reach it at
on a real network; PRIVATE_NODE_SELLING_PORT moves its port.
Start at login. Off unless you switch it on: Your network, Start my
node at login. Your machine then starts the node, and the runner inside it, when you log in:
a launch agent on macOS, a systemd user unit on Linux. The file it writes carries the node's
settings and no key, token or passphrase; a node that opens its secrets with a passphrase from
its environment cannot be started this way, and the switch says so. On macOS a program started
at login is kept out of ~/Downloads, ~/Desktop and ~/Documents: keep the node elsewhere,
or grant Full Disk Access to node. The switch names that when it applies.
aioum node autostart # on or off, the file, what stands in the way
aioum node autostart on
aioum node autostart off
Skills and training
skills.install {id, files?} puts a skill's folder in the workspace under skills/<id>/, from
the runner's index or the files an agent hands it, so the skill's scripts run with ws.run
(Skills); skills.list shows what is there. The train connector runs real
trainers in a container: train.lora {base, kind, dataset, …} for a language, image, speech or
video model, train.distill {teacher, student, prompts} for a student made from an Ollama
teacher, train.status, train.list, and train.export {to: workspace | huggingface | ollama};
when the machine cannot run it the answer carries the exact run arguments for a rented GPU
(Make your own model).
Next: A shared runner — when you would rather not run your own: a runner someone else offers, paid from your own account inside a cap you sign.