Agents
An agent can make machines, run commands in them, read and write their files, pause them and delete
them, through wm-mcp, an MCP server. Give it a key of its own, holding only what it needs.
{
"mcpServers": {
"wheelmachine": {
"command": "/path/to/wm-mcp",
"args": [],
"env": { "WHEELMACHINE_API_KEY": "ak_live_..." }
}
}
}Set it up#
Build wm-mcp#
It is built with wm, from the same source: go build -o bin/ ./cmd/... in
frontend/cli/ writes both. See Install.
Give it a key of its own#
$ wm keys create agent --scope machines:read,machines:write,machines:exec --expires 30dThose three scopes are all its tools need. Leave out keys:write and projects:write, add
--project to keep the agent to one project's machines, and revoke the key when the work is done:
wm keys revoke agent.
Add it to your MCP client#
Add wm-mcp as a server your MCP client starts itself; most clients take an entry of the shape above.
Put the key in its env, or run wm login on the same computer and leave env out,
so the key stays out of the client's settings. WHEELMACHINE_BASE_URL works here as it does for
wm.
What the agent can do#
A machine is named by its id (m_...) or its name in every tool. Each tool answers compact JSON.
| Tool | What it does | Arguments |
|---|---|---|
list_machines | The account's machines, newest first, a page at a time. | project, state, labels, limit, after |
create_machine | Make a machine; it answers once the machine runs. | name, project, region, cpus, labels, from_snapshot, sleep_when_idle, idle_after_s, secrets, network, wait_s |
get_machine | One machine as it is now. | machine |
run_command | Run a command; answers its exit_code, stdout and stderr. | machine, command or shell, cwd, env, root, max_output_bytes |
read_file | Read a file: text as it is, other bytes as base64. | machine, path, max_bytes |
write_file | Write text to a file, making the folders on the way. | machine, path, content |
pause_machine | Pause a machine, memory kept. | machine |
start_machine | Run a paused, stopped or archived machine again. | machine, wait_s |
stop_machine | Stop a machine; its files stay. | machine |
copy_machine | A new machine, a copy of this one as it is now. | machine, name, labels, wait_s |
snapshot_machine | Keep a machine's files as they are now. | machine, note |
delete_machine | Delete a machine and its files, for good. | machine |
list_regions | Places a machine can be asked to run near. | none |
list_gpus | GPUs, where they are and their price an hour (not open to every account yet). | none |
list_secrets | The account's secrets by name, each with the machines given it; never a value. | none |
give_secrets | Say which of the account's secrets a machine's commands get, by name. | machine, secrets |
set_network | Set what a machine may reach on the internet: open, closed, or only what it lists; at once, without a restart. | machine, network |
whoami | The account and key in use, and whether the account can spend. | none |
Arguments in bold are needed. Tools that only read say so to the client, and delete_machine,
stop_machine, write_file and run_command say they can change or take things
away, so a client can ask you before it lets the agent use them.
When a tool fails#
A call the API refuses comes back as the tool's result, marked as an error, with the API's own words, so the agent can put it right:
{"status": 403, "code": "forbidden", "detail": "this key does not hold the cloud:machines:exec scope", "request_id": "req_01jb2xk3n4p5q6r7s8t9v0w1x2"}The code is one of the API's error codes; when the API asked for a wait,
retry_after_s says how long.
Good habits for an agent#
- Label what it makes.
labelssuch as{"task": "issue-412"}let it, and you, find its machines again withlist_machines. - Let machines sleep. A machine pauses by itself once it is idle (five minutes when not said), and wakes for the next command. Delete it when the task is done.
- Start long work in the background.
run_commandmay run 10 minutes. For more, start the work withnohup ... > log.txt 2>&1 &and read the log withread_file. - Mind the output.
run_commandgives back at mostmax_output_bytesof each of stdout and stderr (64 KiB when not said): their start and their end, withtruncatedtrue. Write long output to a file and read the part it needs. - A failed command is not a failed tool. A non-zero
exit_codeis the command's own answer.
MCP versions#
wm-mcp speaks MCP over standard input and output (JSON-RPC 2.0, one message a line) and writes its
log to standard error only. It speaks revision 2026-07-28, where every request carries its version, and the
revisions that begin with initialize: 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
Agents that read the docs#
For an agent that reads rather than calls tools: llms.txt lists every page, each page is
there as Markdown (the same address, ending .md), llms-full.txt is the whole
site in one file, and openapi.yaml (or openapi.json) is the
API's contract.