> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent-assisted administration

> What a script, a monitoring round or a conversational agent can rely on when it drives an OxiMail box through its command line: JSON contracts, exit codes, dry runs, uniform confirmations, diagnostics with the server stopped.

Every operation on an OxiMail box goes through one binary, `oximail`, and its command surface holds a few promises. Each verb that a machine reads speaks JSON. Each exit code means the same thing everywhere. Each write is shown before it is made. The diagnostics work while the server is down.

Those are exactly the properties an automatic operator needs: a cron script, a nightly monitoring round, or an AI agent that runs the box for its administrator and reports back. None of them was designed for AI. They exist because operations demanded them, and a test in the server's CI fails when a new verb breaks one. That is why you can build on them.

This page is the contract for whoever writes that automation. The [CLI reference](./cli) maps the verbs; `oximail <command> --help` stays the authority on flags.

## The contract

| Guarantee | What it means for an automaton |
| - | - |
| **`--json` is a monitoring contract** | Once a verb emits JSON, its field set only grows. A script written today still reads tomorrow's output. Parse the JSON, never the human text, which carries no stability promise. |
| **Exit codes mean something** | `0` all good, `1` degraded (the command did its work and something is red, such as a DNS record still to create), `2` or more: the command could not conclude. A `1` is an answer, not a crash, so a round can decide without reading the text. |
| **A one-shot mutator is a dry run by default** | It prints what it would do and touches nothing until `--apply`. The automaton *shows* the change; a human reads it; then it is applied. |
| **A destructive verb needs its confirmation flag** | `--yes` for one object, `--force` to override a cardinality, `--apply` for a sweep. It is the same grammar everywhere, so the automaton needs one safeguard, not one per verb. |
| **Diagnostics work with the server stopped** | They read the database directly, so they are usable *during* the incident, when they matter most. |
| **Façade, never engine** | A verb goes through the same store methods and validators as the JMAP API. A CLI write cannot bypass a validation, and an automaton that makes a mistake is stopped by the same checks as a human in the interface. |
| **No silent failure** | An error surfaces, as text and as an exit code. This is the precondition for delegating anything. |

## Where to start

Three commands give the state of a box, in this order:

```bash theme={null}
oximail doctor --json
oximail status --json
oximail schema check --json
```

* `doctor` is the composite check: configuration, database, queue, DNS, DKIM, certificate, DANE, backup plan, key escrow, age of the last recovery drill. Read every `status`: `ok`, `warn`, `fail`, and `skipped`. `skipped` means nothing was measured, which is never a pass. Each entry also carries its `family`, its dated `facts` (`observedAt`, `expiresAt`) and, on a `warn` or a `fail`, the `remedy`: the verb to propose, as typed ([the doctor round](./operations#the-doctor-round)).
* `status` counts accounts, messages, the queue, bans and storage.
* `schema check` says whether the binary accepts the database on disk (`accepts`, `newer` or `error`). Read it before a deploy or a rollback.

## The operating guide ships with the binary

```bash theme={null}
oximail skill show
```

It prints the operating guide for an agent: the order of the gestures, the contract above, and what to never do. The binary prints it itself, so the guide always matches the installed version. There is nothing to download, and no copy to go stale. An agent that connects to a box over ssh can read it first.

## Delegation keeps a human in charge

The automaton proposes; a human applies. The dry run and the confirmation flags exist so that the person responsible reads what will be written before it is written. On a box that serves people, show the dry run before running `--apply`.

**The journal says who decided.** A process that runs the verbs for an agent declares it in the environment of each verb it starts: `OXIMAIL_CLI_CHANNEL=agent` and `OXIMAIL_CLI_DECIDED_BY=<the name of the person who opened the session>`. The administrative acts of that verb are then journaled as `cli-agent` under that person's name, never as a command they typed, and never in the agent's name: the agent decides nothing. Any other channel value, `agent` without a name, or a name without `agent` is refused.

## Limits, stated up front

* **The contract binds the verbs created after it.** A few older one-shot migration verbs (`backfill-*` and their family) execute by default and take `--dry-run`. Their default is not flipped: changing a shipped default silently changes what a pinned script does. Their `--help` says so.
* **Read verbs speak JSON; a verb whose output is a file does not.** `print-systemd-unit` prints a systemd unit, which is text by nature. `--help` shows whether a verb takes `--json`.
* **A question is answered by a flag, never by a prompt on a pipe.** `oximail setup --config-file` refuses, by name, a question its file does not answer. `oximail setup dns` runs without a terminal and takes `--replace-mx`, `--replace-spf`, `--replace-dmarc` and `--public-ipv4`: a replace question left open keeps the existing record and ends with exit 1, an undecided public IPv4 stops the run before anything is published, with exit 2 ([setup subcommands](../first-boot#individual-setup-subcommands)).
* **Human text is not a contract.** Messages get clearer from one version to the next. Only the JSON output and the exit codes are stable.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.