# 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

```toml title="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
```

| Key      | Default                    | Meaning                                                                                                            |
| -------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `boards` | the project's `[board] id` | Boards agents may touch, by id, name or port. Names are turned into ids when espx reads the file. `[]` allows none |
| `flash`  | `true`                     | Agents may flash the listed boards (`flash` and `run`)                                                             |
| `erase`  | `false`                    | Agents 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:

```text
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

```sh
espx agent
espx agent --dir ~/code/blinky --json
```

```text
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

```sh
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
```

| Flag              | Effect                                                              |
| ----------------- | ------------------------------------------------------------------- |
| `--boards B,..`   | Agents may touch these boards, by id, name or port, comma-separated |
| `--no-boards`     | Agents may touch no board                                           |
| `--bound-board`   | Go back to the default: only the project's `[board] id`             |
| `--flash on\|off` | Whether agents may flash the allowed boards                         |
| `--erase on\|off` | Whether 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

```sh
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:

```json
{
  "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
}
```

| Field         | Meaning                                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `ts`          | When the call started, milliseconds since the epoch                                                                                  |
| `client`      | The MCP client's name, such as `claude-code`                                                                                         |
| `tool`        | The tool called                                                                                                                      |
| `args`        | Its arguments                                                                                                                        |
| `board`       | The board id the call resolved to, if any                                                                                            |
| `project`     | The project directory whose policy was checked, if any                                                                               |
| `outcome`     | `ok`, `denied` or `failed`                                                                                                           |
| `detail`      | For `denied` and `failed`: the error as `{"code": .., "message": ..}`, the same code the tool result carried, such as `build_failed` |
| `denied`      | For a denied call: the refused action, `flash`, `erase` or `reset`                                                                   |
| `needs_human` | For a flash only a person can rescue: the `action` and `message`                                                                     |
| `ms`          | How 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

```sh
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.

## Related

- [MCP server](https://www.espx.dev/docs/agents/mcp)
- [Boards and ports](https://www.espx.dev/docs/cli/boards#bind-a-project-to-a-board)
- [Configuration](https://www.espx.dev/docs/reference/configuration#espxtoml)
