Wheelhouse docs

Config as code with agent.yaml#

You will end up with a file that says what the router should look like, and a command that tells you the difference between the file and reality. Keep the file in version control and you have config as code, with the same review and history as anything else you keep there.

Before you start#

  • Shell access to the router, or an API token with the operator role.
  • A licence if you are going to apply anything. Planning is a read.

The format is the configuration tree#

/config/wheelhouse/agent.yaml
service:
  dns:
    forwarding:
      cache-size: 10000
  ssh:
qos:
  interface:
    eth1:
      egress: WAN-OUT

The format is sniffed, so agent.yaml is YAML and an inline document posted to the API is JSON. Both describe the same tree.

A key with no value#

ssh: in YAML, or {} in JSON, is how the file says this node exists. What that means depends on the router, so the engine reads it against live state:

Live stateWhat the engine does
The node is missingPlans a bare set, and the plan says so in a warning
The node already has childrenSatisfied; nothing to do
The node holds a valueA contradiction; the plan refuses to run, naming the node and the value the router holds

An explicit empty string (host-name: "") is refused where the document is read, before any diffing.

Step 1 — Plan#

bash
wheelhouse-agent plan --file /config/wheelhouse/agent.yaml
set service dns forwarding cache-size 10000
set service ssh
warning: service.ssh: planned as a bare set (a node with no value)

2 change(s) needed

The exit code is the interface. plan exits 2 when it found a difference, the way diff -q does, and 0 when the router already matches. That drops into a shell test or a CI gate without parsing any text.

bash
wheelhouse-agent plan --file agent.yaml || echo "the router does not match the file"

Two more flags:

bash
wheelhouse-agent plan --file agent.yaml --json      # for scripts
wheelhouse-agent plan --file agent.yaml --full      # also delete what the file omits

--full is the dangerous one. Without it the file is a set of assertions: what it names must be so, and everything else is left alone. With it the file is the whole truth, and anything on the router the file does not mention is deleted. Read a --full plan very carefully the first time.

Step 2 — Apply#

apply stages by default and only commits when told to — the same rule the UI obeys.

bash
# Stage into a running agent's Commit Bar; a human still commits.
wheelhouse-agent apply --file agent.yaml --agent-url https://127.0.0.1:8443

# Or commit, protected by VyOS' own commit-confirm.
wheelhouse-agent apply --file agent.yaml --commit --confirm-minutes 2

Two refusals worth knowing:

  • apply refuses to stage without --agent-url. The staging area belongs to the running agent, not to a process that is about to exit.
  • apply refuses --commit without a confirm window. A committing automation without a rollback is how a fleet goes dark.

Staging through the agent authenticates with the break-glass token (--admin-token or --admin-token-file), and nothing creates that file by default — create one deliberately.

Step 3 — Or do it over the API#

POST /api/reconcile takes a desired document and returns the operations. apply: true stages the difference; the Commit Bar still commits.

bash
R=https://<router>:8443
T=wh_...
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '{"desired_state":{"service":{"dns":{"forwarding":{"cache-size":"10000"}}}},"apply":false}' \
  "$R/api/reconcile"

Reconcile → the page does the same thing with a text box: paste or point at a document, read the plan, stage the difference.

GET /api/reconcile/file diffs the agent's own file against live. Its ?file= parameter takes a .yaml, .yml or .json name inside the data directory, needs the operator role, and answers 400 if the file plans an empty value.

Step 4 — Keep the file somewhere#

Version control, and the same review as your other infrastructure. The file is also something a backup has to contain — Take a backup that is actually complete names it.

Check it worked#

bash
wheelhouse-agent plan --file /config/wheelhouse/agent.yaml
echo $?     # 0 means the router matches the file

Then confirm the change is really on the router:

bash
show configuration commands | match cache-size

Undoing it#

An applied change is a commit like any other — roll it back. The file itself is a file; edit it, or the router will drift back the next time somebody applies it.

See also#


Checked against agent/desired.go · agent/reconcile.go · docs/deploy.md · docs/adr/001-desired-state-file.md · ui/src/pages/Reconcile.tsx

Updated 2026-09-02 desired-state config-as-code automation