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:
{stamp}-transitions.log— events withevent_type == "workflow.transition".{stamp}-audit.log— every other event.
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:
- Vector – use a
filesource pointing at your audit log, or capture stderr with astdinsource. - Prometheus – parse events into metrics (transitions per second, failure rate, latency histograms).
- Elasticsearch / OpenSearch – index the JSONL directly.
- CloudWatch / Datadog / Splunk – forward the log file or stderr stream through their agents.
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?”