Skip to content

Audit events

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.

audit:
sink: stderr

Four sink options:

SinkBehavior
stderrWrites one JSON line per event to stderr. Default.
fileWrites one JSON line per event into a date-rotated directory of .log files.
memoryStores events in memory. Useful for testing.
noneDrops all events. Disables auditing.
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 with event_type == "workflow.transition".
  • {stamp}-audit.log — every other event.

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

ValueStamp formatExample file
hourlyYYYY-MM-DD-HH2025-06-15-14-audit.log
daily (default)YYYY-MM-DD2025-06-15-audit.log
weeklyYYYY-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.

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.

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": {}
}
FieldTypeDescription
idstringUnique event ID (evt_ prefix + UUID)
timestampstringISO 8601 timestamp (UTC)
workflow_idstring or nullThe workflow instance this event belongs to
correlation_idstringGroups related events in the same operation (cor_ prefix + UUID)
actorstring or nullWho triggered the action (principal subject)
event_typestringWhat happened (see below)
payloadobjectEvent-specific data
trace_idstring (optional)Caller-supplied trace ID spanning multiple workflows in one logical operation. Opaque to the gateway; omitted when absent.
run_idstring (optional)Caller-supplied ID for grouping related workflow instances. Opaque to the gateway; omitted when absent.
Event typeWhen it fires
workflow.startedA new workflow instance is created via flowgate.command({definitionId})
workflow.transitionA transition is applied to the workflow (the full-weight record)
workflow.interactionA lightweight transition is applied (a transition marked lightweight)
workflow.transitionedA workflow successfully moves to a new state
workflow.cancelledA workflow is cancelled
workflow.completedA workflow reaches a terminal state
workflow.timed_outA workflow exceeded its timeoutMs and auto-transitioned
Event typeWhen it fires
transition.requestedA flowgate.command({workflowId, transition}) submit call is received
transition.rejectedA submission is rejected (guard failure, version conflict, actor mismatch, etc.)
transition.branchedA transition’s branch condition matched, changing the target state
Event typeWhen it fires
chain.stepOne step in a deterministic chain completed
chain.completedA deterministic chain finished (reached a non-deterministic state, terminal, or depth limit)
chain.failedA deterministic chain stopped because an executor failed
Event typeWhen it fires
executor.startedAn executor begins running
executor.succeededAn executor completed successfully
executor.failedAn executor failed after all retries
executor.retryingAn executor is retrying after a failure
fallback.selectedThe reliability layer is trying a fallback executor
Event typeWhen it fires
guard.evaluatedA guard was evaluated (payload includes pass/fail result)
{
"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"
}
}
}
{
"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"
}
}
{
"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."
}
}
{
"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"
}
}
{
"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"
}
}

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

  • Vector — use a file source pointing at your audit log, or capture stderr with a stdin source.
  • 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.

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 = 'flowgate.query'
AND args->>'workflowId' IS NOT NULL

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

WHERE tool_name = 'flowgate.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?”