Audit Trail
Every file write the agent performs appends one line to an actor-stamped log. It is on by default, needs no configuration, and is designed so that reading it later tells you who changed what, when, and from where.
Location
| Default | ~/.oasis/audit.jsonl |
| Override | OASIS_AUDIT_FILE |
The file is JSON Lines: one complete JSON object per line, appended, never rewritten.
Line format
{"ts":"2026-08-24T11:42:07Z","actor":"milo@plant-3","tool":"Write","path":"/srv/plc/main.py","bytes":2841,"cwd":"/srv/plc"}| Field | Meaning |
|---|---|
ts | RFC 3339 timestamp in UTC, seconds precision |
actor | The opaque actor string from --actor / OASIS_ACTOR |
tool | Which tool performed the write |
path | The resolved absolute path written |
bytes | Size of the resulting file |
cwd | The sandbox root the write happened under |
What is recorded
The four tools that mutate files on disk:
| Tool | Recorded |
|---|---|
Write | Yes |
Append | Yes |
Edit | Yes |
MultiEdit | Yes, one line per file touched |
The audit trail records file writes, not everything the agent does
Bash can write files without appearing here, and protocol writes (ModbusWrite, OpcUaWrite, EtherCatWriteIo) are not in this log. For a full record of agent activity, capture the event stream: <id>-agent.jsonl for a background agent, or the control protocol for a live session.
Setting the actor
oasis-agent --actor "milo@plant-3"
export OASIS_ACTOR="ci-runner"The string is opaque. The agent never parses it. Use whatever your organisation reconciles against: an email, a service account, a change-request number, a ticket id.
Without it, actor is null. On a shared machine, or anywhere the log will be read by someone who was not there, set it.
Design properties
| Property | Behaviour |
|---|---|
| Best-effort | An unwritable audit path never fails a tool call. The agent keeps working; the failure is reported to stderr once |
| Append-only | The file is opened O_APPEND and each line written with a single call, so concurrent tool executions interleave whole lines rather than fragments |
| No rotation | The agent does not rotate or prune. Use logrotate or an equivalent |
Best-effort is a deliberate trade-off
The audit log records what happened; it does not gate what may happen. A full disk or a read-only home directory will stop the log without stopping the agent.
If your environment requires that no unaudited write can occur, enforce that outside the agent: a read-only mount, or a filesystem-level audit such as auditd.
Reading it
# Everything one actor did today
jq 'select(.actor == "milo@plant-3")' ~/.oasis/audit.jsonl
# Every write under a project, newest last
jq -r 'select(.cwd == "/srv/plc") | "\(.ts) \(.tool) \(.path)"' ~/.oasis/audit.jsonl
# Total bytes written per actor
jq -s 'group_by(.actor) | map({actor: .[0].actor, bytes: (map(.bytes) | add)})' ~/.oasis/audit.jsonlShipping it elsewhere
Point OASIS_AUDIT_FILE at a named pipe or a location your log shipper tails:
export OASIS_AUDIT_FILE=/var/log/oasis/audit.jsonlMake sure the directory exists and is writable. The agent creates the parent directory if it can, and silently skips the line if it cannot.
Rotation
/var/log/oasis/audit.jsonl {
weekly
rotate 52
compress
missingok
notifempty
copytruncate
}Use copytruncate rather than create. The agent holds the file open in append mode, and a rename would leave it writing to the rotated inode.
Related records
| Record | Contains | Where |
|---|---|---|
| Audit trail | One line per file write | ~/.oasis/audit.jsonl |
| Session history | The full conversation transcript | ~/.oasis/sessions/ |
| Agent event stream | Every event from a background agent | <id>-agent.jsonl |
| Conformance evidence | What was true about the machine | Audit reports |
The audit trail and an audit report are different things despite the shared word. The trail records what the agent did; the report records what the machine is.