Agent policy

Choose which boards agents may flash, erase and reset, and read every call they made.

An agent working through espx mcp can write firmware to real hardware. The [agent] table in a project's espx.toml decides which boards it may touch and what it may do to them. Every flash, erase and reset an agent asks for is checked against it. Commands you run yourself in a terminal are never checked.

The [agent] table

espx.toml
[board]
id = "58:8C:81:53:AD:2C"

[agent]
boards = ["58:8C:81:53:AD:2C"]   # boards an agent may flash, erase or reset; [] = none
flash = true                     # default true
erase = false                    # default false
KeyDefaultMeaning
boardsthe project's [board] idBoards agents may touch, by id, name or port. Names are turned into ids when espx reads the file. [] allows none
flashtrueAgents may flash the listed boards (flash and run)
erasefalseAgents may erase the listed boards

Resetting a board with the reset tool needs only the board to be listed. Reading is never checked: devices, monitor_read, and also board_info and monitor_start, even though those two reset the board through the serial lines when they open it.

The defaults mean:

  • A project bound with espx new --board-id (or project_new with board_id) can be flashed and reset by its agent, but not erased.
  • A project with no [board] id and no boards list allows no board at all.
  • An agent working outside any project may touch no board.

A refused call fails with policy_denied, and the message names the keys to set:

espx.toml does not let an agent erase board 58:8C:81:53:AD:2C. A person can run the command, or allow it in the project's espx.toml with [agent] boards = ["58:8C:81:53:AD:2C"] and erase = true.

Recordings played through monitor_start with replay are not boards and are never checked.

See the policy

espx agent
espx agent --dir ~/code/blinky --json
agents working in /home/you/blinky/espx.toml may:
  boards: 58:8C:81:53:AD:2C (bench-c6) (the project's [board] id)
  flash:  yes
  erase:  no
  reset:  yes

--json prints the resolved policy as {"boards": [..], "flash": true, "erase": false}, with names turned into ids. espx agent works only inside a project and never creates an espx.toml.

Change it

espx agent set --boards bench-c6,lab-s3
espx agent set --no-boards
espx agent set --bound-board
espx agent set --flash off --erase on
FlagEffect
--boards B,..Agents may touch these boards, by id, name or port, comma-separated
--no-boardsAgents may touch no board
--bound-boardGo back to the default: only the project's [board] id
--flash on|offWhether agents may flash the allowed boards
--erase on|offWhether agents may erase the allowed boards

Pass at least one flag. Keys you leave out stay as they are. A board does not need to be plugged in to be listed; a name is stored as its board's id.

agent set edits espx.toml in place. It replaces the boards, flash and erase lines inside [agent], keeping their indentation and trailing comments, adds the missing ones, and adds an [agent] section when there is none. Every other line stays as it was. If the policy is written as an inline table (agent = { .. }) or with dotted keys, espx cannot edit it safely and stops with policy_not_editable without writing anything.

Allow one thing

espx agent allow flash
espx agent allow erase --board bench-c6
espx agent allow reset

agent allow makes the smallest change that lets agents do one thing to one board. It adds the board to boards unless the policy already lists it, and turns flash or erase on for those actions. Without --board it uses the project's board, else the one attached. This is what the desktop app's Allow button does on a denied call.

Both agent set and agent allow print the resulting policy, or, with --json, the same document as espx agent --json.

The audit log

Every tool call an agent makes through espx mcp, allowed or not, is appended to ~/.espx/agent.log as one JSON object per line:

{
  "ts": 1762862645123,
  "client": "claude-code",
  "tool": "flash",
  "args": { "dir": "/work/blinky" },
  "board": "58:8C:81:53:AD:2C",
  "project": "/work/blinky",
  "outcome": "ok",
  "ms": 8400
}
FieldMeaning
tsWhen the call started, milliseconds since the epoch
clientThe MCP client's name, such as claude-code
toolThe tool called
argsIts arguments
boardThe board id the call resolved to, if any
projectThe project directory whose policy was checked, if any
outcomeok, denied or failed
detailFor denied and failed: the error as {"code": .., "message": ..}, the same code the tool result carried, such as build_failed
deniedFor a denied call: the refused action, flash, erase or reset
needs_humanFor a flash only a person can rescue: the action and message
msHow long the call took

A failed call that returned a result rather than an espx error, such as a cancelled job or a rejected run, is logged with the code command_failed. Each entry is written in one append, so several espx mcp processes can log at once without mixing lines.

Read the log

espx log
espx log --tail 20
espx log --json

espx log prints one line per call with its time, client, tool, outcome, duration and board, and the error message under a denied or failed call. --tail N keeps the last N entries. --json prints them as one JSON document. The desktop app's AI agents page shows the same log live, grouped by client.