Skip to main content
Version: 2.0.0

Audit and history

This page is for after the fact: who ran which command and why, and everything that was done to one plate. Two archives answer those two questions, and both work against either backend.

  • orca audit answers "who did what". Every confirmed @dangerous operation writes a row: the action name, its danger level, the operator's reason, and the arguments the call was made with.
  • orca ops-history answers "what happened to this plate". Per action the runtime archives a TrackingRecord: which labware was affected, which device operations ran, where the record came from, and when.

An operator's own commands land in both. A @dangerous verb writes the audit row. If the command changed physical state, the ledger writes the operation record too.

orca audit​

orca audit list                                       # most recent last
orca audit list --limit 200 # cap the rows returned
orca audit list --action-name labware.edit_location # filter to one action

The audit surface is a single list verb taking --action-name and --limit (default 50). There is no audit get, and no --since, --verb or --execution filter.

 timestamp             action                       level      reason
2026-09-08T14:22:03 labware.edit_location PHYSICAL moved to shaker by hand
2026-09-08T14:24:11 thread.skip_method CRITICAL sealer pre-heating
2026-09-08T14:30:05 device.execute PHYSICAL

Rows come back oldest first, so the newest is at the bottom. --limit keeps the most recent N.

The table shows four columns. --json adds call_args, the bound arguments of the call, which is the field that matters after the fact: an injected method or action carries its source there, not a repr of the compiled object, so you can read back exactly what was spliced into a run.

What gets audited​

What does not​

Workflow-driven work. A shake inside an @orca.action is in the operations archive, not the audit log.

Where it lives​

The audit trail an operator reads through this verb is an in-memory ring buffer, capped at 1000 entries and wiped on restart. It is for live inspection without parsing a file.

The durable mirror is a rotating log file at <log_dir>/orca_audit.log, written by the orca.audit logger on every recorded call. It survives daemon restarts. A hosted deployment persists its own copy and inherits its own retention.

orca ops-history​

The operations archive is what the volume and tip-layout reads are folded from. See State.

orca ops-history get <execution-id>                      # every record for one execution
orca ops-history search --labware <name> # records touching one labware
orca ops-history search --device <name> # records touching one device
orca ops-history search --execution-id <id> --operation aspirate
orca ops-history search --thread-id <id> --source operator

get resolves the execution id from a prefix. search does not: its filters are passed through as exact values.

get output​

 action_id      thread_id     method_id     source     timestamp     ops   affected_labware_ids
3f1c9a2b4d5e a4b2c1d0e9f8 9d8e7c6b5a4f observed 1780000012.4 2 7a3f1c0e-...
3f1d0b3c5e6f a4b2c1d0e9f8 9d8e7c6b5a4f observed 1780000042.9 1 7a3f1c0e-...

The affected-labware column carries full labware UUIDs. Those are the ids labware journey and labware history accept; the display names are not resolvable.

search filters​

All filters combine with AND.

FlagEffect
--execution-id <id>Filter by owning execution.
--action-id <id>Filter by action id.
--thread-id <id>Filter by thread id.
--method-id <id>Filter by method id.
--source <s>Filter by tracking source: observed, declared, driver_observed, operator.
--operation <op>Records holding at least one op of this DeviceOperation value: aspirate, dispense, pick_up_tips, drop_tips, discard_tips, mix, shake, seal, incubate, centrifuge, read, delid, run_protocol, the four 96-head variants, set_volume, set_tip_state, set_mounted_tips, confirm_mounted_tips, initial_state, well_usage, action_continued, observation_gap.
--labware <name>Records that touched this labware name.
--device <name>Records that touched this device name.

There is no --labware-id, --kind, --since, --until or --limit filter.

Results come back in ascending timestamp order across every execution bucket, so a cross-execution search is one chronological merge whichever store answered.

Finding an operator's own commands​

An ad-hoc command belongs to no execution and no thread, so its record is filed under the sentinel _system in both positions, with source: operator. Per-execution lookups exclude that bucket, and ops-history get _system cannot reach it because get resolves its argument against the live execution list. Search for it instead:

orca ops-history search --execution-id _system
orca ops-history search --source operator --device ml_star

source: operator is how an auditor tells a hand-sent command from the engine's own, and nothing more. The fold that computes volumes and tip layouts reads the operation and ignores the source, so an operator's command counts exactly like a workflow's.

Batched device commands go down the same dispatch path as a single one, so each command in a batch records its own operation. The external-control flag is taken once for the whole batch rather than per command, because releasing between commands lets the engine schedule the device in the middle of it.

Durability​

Records are append-only, and the default store appends JSONL files, so the archive survives a runtime restart. A finished execution reads back after a restart rather than answering not-found. A hosted deployment injects its own indexed archive behind the same interface. See Stores.

If a record is wrong, that is the record. The fix is in code, not in the archive.

Reading history back after a restart​

orca execution detail <id> still answers for an execution the live registry has forgotten: the thread list is rebuilt from the durable execution record, which carries a per-thread summary (name, template, status, error, pause reason) and the same completed and active counting rule as the live view.

What it does not carry is per-thread location and method progress. That trail is in the operations archive:

orca ops-history get <execution-id>

labware journey and history​

orca labware history <id> [--limit 20]      # location changes only
orca labware journey <id> [--kind move] [--kind action]

history lists the positions this labware has occupied, in sequence order. journey merges the actions that ran at each location into the same timeline, ordered by timestamp. journey needs a full labware UUID, because the cloud backend has no list-instances endpoint to resolve a prefix against. --json on journey carries each action's device, operation, typed details, and the execution, thread, action and method ids that produced it, which is the bridge back into ops-history search.

Rendering and provenance are covered in Monitor.

See also​

  • Mid-flight inspection: the same reads while the run is still going.
  • Operator interventions: the commands that write the audit rows you are reading.
  • State: how the operations archive becomes a volume or a tip layout.
  • Stores: where each archive is kept and what swaps it out.
  • CLI overview: the danger levels and --reason behind every audited call.