Appendices · reference

Wire protocol and error codes

8 min read · 2026-09-02 · vmlab 0.9

This chapter summarises the protocol the vmlab CLI speaks to the two daemons, and the error codes every failure carries. It is what an out-of-repo client needs to know. The full command list, with every argument and the reason each command is reachable from where it is, is generated from the source into docs/protocol.md in the repository; this chapter does not repeat it. How vmlab runs a lab explains which daemon owns what.

Shape

Both daemons listen on unix sockets under the runtime directory, see Files and directories: the supervisor on vmlabd.sock and each lab daemon on its lab's control.sock. A connection carries JSON lines, each one object tagged by type. A request is req, with a client-chosen id, a cmd string and an args object. The final answer is resp for the same id, carrying either ok with the result, or err with a prose message and a machine-readable code. Commands that produce output as they run, such as up and machine.logs with follow, send stream messages with a chunk before the final resp, and a connection that has subscribed receives event messages on the same line stream.

json
{"type":"req","id":1,"cmd":"machine.ip","args":{"machine":"dc01","nic":null}}
{"type":"resp","id":1,"ok":"10.0.0.10"}
{"type":"req","id":2,"cmd":"snapshot.restore","args":{"name":"nope","machine":"dc01","discard":false}}
{"type":"resp","id":2,"err":"\"dc01\" has no snapshot \"nope\"","code":"not_found"}

The message may be reworded between releases; the code is the contract. Inside the repository every surface builds requests through the typed vocabulary in the source rather than spelling the strings, and the generated reference is produced from that vocabulary, so the two cannot drift.

Error codes

A failed command answers with one of seven codes. The CLI maps each to its exit status, so a script can branch on $? without parsing output. Every command chapter under Commands names the codes that apply to it.

Codevmlab exit codeMeaning
unknown_command2The daemon does not know the command. Usually a version mismatch between CLI and daemon.
invalid_argument2An argument is missing, malformed or out of range.
not_found4The lab, machine, snapshot, template or path named does not exist.
conflict5The request contradicts current state: a build already running, a halted workspace, a port already claimed.
unsupported6This machine or host cannot do what was asked, such as a screen operation on a machine without a display.
failed1The operation was attempted and failed: the emulator, the agent, a registry or the guest reported an error.
internal1A fault in the daemon itself.

A command that succeeds exits 0. A CLI-side failure before any request is sent, such as no vmlab.wcl in any parent directory, also exits non-zero with a message and no code.

The supervisor socket

The supervisor owns the lab registry, the template store, the registry catalogue and global segments. Its commands fall into these groups.

GroupCommandsCalled by
Livenessping, version, fastpath, status, shutdownCLI, and daemons for ping and shutdown
Lab daemonslab.ensure, lab.release, lab.restartCLI
Global segmentsglobal.attach, global.detach, global.listLab daemons only
Template buildstemplate.list, template.build, template.stop_buildCLI
The storestore.list, store.remove, store.prune, store.export, store.import, store.pull, store.push, store.stop_pushCLI
Registriesregistry.search, registry.login, registry.namespaces, registry.namespace_add, registry.namespace_removeCLI

A lab daemon's socket

A lab daemon owns one lab: its machines, network, snapshots, playbooks and workspaces. Its commands fall into these groups.

GroupCommands
Labping, status, dns.table, up, pull, pull.cancel, run, down, destroy, shutdown
Machine lifecyclemachine.start, machine.stop, machine.restart, machine.destroy, machine.capabilities, machine.ip, machine.osinfo, machine.stats, machine.logs, machine.repair_agent
Display and inputmachine.screenshot, machine.sendkeys, machine.mouse_move, machine.mouse_click, machine.mouse_drag, machine.ocr, machine.find_image
Guest agentmachine.exec, machine.tty_open, machine.tty_resize, machine.push_file, machine.pull_file, machine.tail, machine.eventlog, machine.clipboard_get, machine.clipboard_set
Playbooksplaybook.list, playbook.check, playbook.apply
Snapshotssnapshot.take, snapshot.restore, snapshot.delete, snapshot.list
Workspaceworkspace.flush, workspace.resolve, workspace.diff

Every lab-daemon command is called by the CLI. machine.tty_open answers with the path of a second unix socket the caller connects to and pipes a raw terminal over, which is what vmlab shell does.

Events on the wire

A subscriber receives each event as an object with event and data. The supervisor forwards every event from every lab daemon it has adopted unchanged, and adds its own: lab.daemon_crashed, host.disk_low for the store's filesystem, segment.peer.up and segment.peer.down, and the template.op.* family. The names and payloads are in Events.