Audit log
agents-cli writes a JSONL line to ~/.local/state/agents-cli/audit.log for every command that mutates state (profile registration, profile removal, symlink swap, update profile). Read-only commands (list, current, show, doctor) do not write to the log.
The location follows the XDG Base Directory Specification. Override with XDG_STATE_HOME or with AGENTS_CLI_DIR (see reference).
Format
Newline-delimited JSON — one entry per line, no array wrapper:
{"ts":"2026-06-09T10:21:33Z","event":"use","from":"alpha","to":"beta","actor":"lucas","cwd":"/Users/lucas/work","exit":0}
{"ts":"2026-06-09T10:21:38Z","event":"update_profile","profile":"beta","from":"7f3a8c1","to":"9e2b441","ff":true,"exit":0}
{"ts":"2026-06-09T10:22:11Z","event":"install","profile":"gamma","url":"git@example.com:workspace/gamma.git","target":"/Users/lucas/.config/agents-cli/profiles/gamma","exit":0}
The file is append-only — agents-cli never rewrites past entries. Truncation, rotation, and shipping to a log aggregator are the operator's responsibility.
Common fields
Every entry includes:
| Field | Type | Meaning |
|---|---|---|
ts | string (RFC 3339, UTC, second precision) | When the event happened. |
event | string | One of install, add, remove, use, update_profile. |
actor | string | OS user that ran the command ($USER). |
cwd | string | Working directory at invocation. |
exit | integer | Process exit code. Non-zero means the command failed; the entry is still written. |
Per-event fields
event=install
| Field | Meaning |
|---|---|
profile | Profile name (after --name override, if any). |
url | Git URL passed to install. |
target | Final clone destination on disk. |
used | Boolean — true if --use was passed and the swap succeeded. |
event=add
| Field | Meaning |
|---|---|
profile | Profile name. |
source | Original path passed to add. |
target | Canonical path the directory was copied to. |
event=remove
| Field | Meaning |
|---|---|
profile | Profile name that was unregistered. |
force | Boolean — true if --force was used to remove the active profile. |
event=use
| Field | Meaning |
|---|---|
from | Previous active profile name. null if ~/.agents was unset or broken. |
to | New active profile name. |
event=update_profile
| Field | Meaning |
|---|---|
profile | Profile name. |
from | Commit SHA before the pull (short). |
to | Commit SHA after the pull (short). Identical to from if no change. |
ff | Boolean — whether the pull fast-forwarded. Always true on success (non-FF refuses). |
Querying the log
The log is grep-friendly and jq-friendly:
# All profile swaps in the last 24 hours
jq -c 'select(.event=="use" and .ts > (now - 86400 | todateiso8601))' \
~/.local/state/agents-cli/audit.log
# Which profile was active at noon yesterday?
jq -c 'select(.event=="use" and .ts < "2026-06-08T12:00:00Z") | .to' \
~/.local/state/agents-cli/audit.log | tail -1
# Count failed update attempts per profile
jq -c 'select(.event=="update_profile" and .exit!=0) | .profile' \
~/.local/state/agents-cli/audit.log | sort | uniq -c
Rotation
agents-cli does not rotate the file. The expected workflow is either:
- Let it grow (each entry is < 200 bytes; this is fine for years).
- Use
logrotateornewsyslogagainst~/.local/state/agents-cli/audit.logif you want size-bounded retention. - Ship it to a log aggregator with a tail-and-forward agent of your choice.
What is NOT logged
By design, the log contains no secrets, no profile contents, no command output, and no environment dumps. The schema is fixed and small to make it auditable and easy to ship.