Audit events

Reference for the structured audit events that every workflow action emits.

Every workflow action emits a structured JSON audit event. You don’t add logging – it’s already there. The gateway records what happened, when, and who did it, automatically.

Configuration

audit:
  sink: stderr

Four sink options:

Sink Behavior
stderr Writes one JSON line per event to stderr. Default.
file Writes one JSON line per event into a date-rotated directory of .log files.
memory Stores events in memory. Useful for testing.
none Drops all events. Disables auditing.

File sink

audit:
  sink: file
  path: ./audit/
  rotation: daily

path is a directory, not a single file. The gateway creates it on first write if it doesn’t exist, then writes one JSON line per event into date-stamped .log files inside it.

Events are split into two categories per rotation window:

audit.rotation controls the stamp granularity (defaults to daily):

Value Stamp format Example file
hourly YYYY-MM-DD-HH 2025-06-15-14-audit.log
daily (default) YYYY-MM-DD 2025-06-15-audit.log
weekly YYYY-Www (ISO week) 2025-W24-audit.log

Each write appends and flushes, so the trail survives a crash. Because rotation is built in, you don’t need an external rotator like logrotate.

Memory sink

audit:
  sink: memory

Keeps events in process memory. Useful in tests where you want to assert on what events were emitted without touching the filesystem.

Event shape

Every event has the same structure:

{
  "id": "evt_a1b2c3d4e5f6789012345678abcdef01",
  "timestamp": "2025-06-15T14:32:08.123456Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_f6a1b2c3d4e5789012345678abcdef01",
  "actor": "agent@default",
  "event_type": "workflow.started",
  "payload": {}
}
Field Type Description
id string Unique event ID (evt_ prefix + UUID)
timestamp string ISO 8601 timestamp (UTC)
workflow_id string or null The workflow instance this event belongs to
correlation_id string Groups related events in the same operation (cor_ prefix + UUID)
actor string or null Who triggered the action (principal subject)
event_type string What happened (see below)
payload object Event-specific data
trace_id string (optional) Caller-supplied trace ID spanning multiple workflows in one logical operation. Opaque to the gateway; omitted when absent.
run_id string (optional) Caller-supplied ID for grouping related workflow instances. Opaque to the gateway; omitted when absent.

Event types

Workflow lifecycle

Event type When it fires
workflow.started A new workflow instance is created via praxec.command({definitionId})
workflow.transition A transition is applied to the workflow (the full-weight record)
workflow.interaction A lightweight transition is applied (a transition marked lightweight)
workflow.transitioned A workflow successfully moves to a new state
workflow.cancelled A workflow is cancelled
workflow.completed A workflow reaches a terminal state
workflow.timed_out A workflow exceeded its timeoutMs and auto-transitioned

Transitions

Event type When it fires
transition.requested A praxec.command({workflowId, transition}) submit call is received
transition.rejected A submission is rejected (guard failure, version conflict, actor mismatch, etc.)
transition.branched A transition’s branch condition matched, changing the target state

Deterministic chains

Event type When it fires
chain.step One step in a deterministic chain completed
chain.completed A deterministic chain finished (reached a non-deterministic state, terminal, or depth limit)
chain.failed A deterministic chain stopped because an executor failed

Executors

Event type When it fires
executor.started An executor begins running
executor.succeeded An executor completed successfully
executor.failed An executor failed after all retries
executor.retrying An executor is retrying after a failure
fallback.selected The reliability layer is trying a fallback executor

Guards

Event type When it fires
guard.evaluated A guard was evaluated (payload includes pass/fail result)

Example events

workflow.started

{
  "id": "evt_a1b2c3d4e5f6789012345678abcdef01",
  "timestamp": "2025-06-15T14:32:08.123456Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_f6a1b2c3d4e5789012345678abcdef01",
  "actor": "agent@default",
  "event_type": "workflow.started",
  "payload": {
    "definitionId": "deploy_pipeline",
    "input": {
      "service": "payments",
      "environment": "staging"
    }
  }
}

transition.requested

{
  "id": "evt_b2c3d4e5f6a1789012345678abcdef02",
  "timestamp": "2025-06-15T14:32:09.456789Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_a1b2c3d4e5f6789012345678abcdef02",
  "actor": "agent@default",
  "event_type": "transition.requested",
  "payload": {
    "transition": "deploy",
    "expectedVersion": 4,
    "fromState": "ready_to_deploy"
  }
}

transition.rejected

{
  "id": "evt_c3d4e5f6a1b2789012345678abcdef03",
  "timestamp": "2025-06-15T14:32:09.789012Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_a1b2c3d4e5f6789012345678abcdef02",
  "actor": "agent@default",
  "event_type": "transition.rejected",
  "payload": {
    "transition": "deploy",
    "code": "GUARD_REJECTED",
    "message": "One or more guards rejected the transition."
  }
}

executor.succeeded

{
  "id": "evt_d4e5f6a1b2c3789012345678abcdef04",
  "timestamp": "2025-06-15T14:32:10.123456Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_a1b2c3d4e5f6789012345678abcdef02",
  "actor": "agent@default",
  "event_type": "executor.succeeded",
  "payload": {
    "transition": "deploy",
    "candidate": 0,
    "attempt": 1,
    "kind": "cli"
  }
}

chain.completed

{
  "id": "evt_e5f6a1b2c3d4789012345678abcdef05",
  "timestamp": "2025-06-15T14:32:11.456789Z",
  "workflow_id": "wf_d4e5f6a1b2c3",
  "correlation_id": "cor_f6a1b2c3d4e5789012345678abcdef01",
  "actor": null,
  "event_type": "chain.completed",
  "payload": {
    "steps": 3,
    "finalState": "ready_to_deploy"
  }
}

Piping to your observability stack

Audit events are structured JSON, one line per event. They’re designed to be piped into whatever you already use:

The correlation_id field ties related events together across a single operation. The workflow_id groups everything for one workflow instance. Between the two, you can reconstruct the full story of any workflow run.

Filtering in dashboards

When querying audit records, remember: tool_name and event_type are different axes.

Filter by tool (which MCP call triggered the record):

WHERE tool_name = 'praxec.query'
  AND args->>'workflowId' IS NOT NULL

This finds all workflow-get and workflow-explain calls.

WHERE tool_name = 'praxec.command'
  AND args->>'definitionId' IS NOT NULL

This finds all workflow-start calls.

Filter by what happened (event_type in the payload):

WHERE event_type = 'workflow.started'
WHERE event_type = 'transition.requested'
  AND payload->>'transition' = 'deploy'

The two axes are complementary. tool_name answers “what did the model call?” — event_type answers “what did the runtime record as a result?”