Where they live
Per-repo events go to <cwd>/.xcoder/flow-events.jsonl. Override with FLOW_EVENTS_PATH (mostly for tests). Per-agent events (autopilot scoped) go to ~/.xcoder/events/<agentId>.jsonl.
Best-effort writes. A failed write logs to stderr but never throws — flow progress must not depend on event-sink availability.
Event shape
ts
interface SupervisorEvent {
type: SupervisorEventType
at: string // ISO 8601
phase: FlowPhase // current phase when emitted
taskId: string | null // active task, if any
data: Record<string, unknown> // type-specific payload
}flow.* events
| Type | When | Notable data |
|---|---|---|
flow.transition | Engine advances from one phase to the next. | { from, to, reason } |
flow.acceptance.passed | An acceptance check returned pass=true. | { invariant, checkId, reason, bypassedBy? } |
flow.acceptance.failed | An acceptance check returned pass=false. | { invariant, checkId, reason } |
flow.bypass | A bypass is recorded (env / config / command / flag). | { source, identifier, invariant, phase, reason } |
flow.guard.allow | A PreToolUse hook allowed the action. | { guardId, reason } |
flow.guard.block | A PreToolUse hook blocked the action (exit 2). | { guardId, reason } |
flow.guard.bypass | A PreToolUse hook allowed via a recorded bypass. | { guardId, reason, bypass } |
flow.tool.invoked | A state-mutating tool call was dispatched (I-12). | { toolName, toolInputKeys } |
flow.commit.format.violation | A commit was made with a non-conventional subject (post-commit signal). | { subject, expected } |
supervisor.* events mostly post-v1
| Type | When | Notable data |
|---|---|---|
supervisor.question.sent | Autopilot needs human input; task marked waiting-for-reply. | { replyToken, channel, body } |
supervisor.reply.received | Reply correlated to a question; task resumes. | { replyToken, body } |
supervisor.reply.unparseable | Inbound message couldn't be correlated. | { raw } |
supervisor.budget.warning | Session/daily/monthly cap hit warning_at_pct. | { scope, used, cap, pct } |
supervisor.budget.halt | Hard cap exceeded; engine halts. | { scope, used, cap } |
supervisor.routing | Task routed by performance profile. | { taskId, decision, successRateAtTier } |
task.* events
| Type | When | Notable data |
|---|---|---|
task.completed | Task wrapped successfully (engine reached idle). | { taskId, prNumber, duration_ms } |
task.failed.scoped-down | Task split into sub-tasks (scope-down). | { taskId, children } |
task.failed.escalated | Task escalated to human; autopilot won't retry. | { taskId, reason } |
task.scope_down.proposed | Engine suggested decomposing the task. | { taskId, proposed } |
iteration.cap.hit | Test↔implement loop hit its iteration cap. | { taskId, error } |
Reading events
CLI
bash
xc flow events # last 50, all types
xc flow events -n 200 # last 200
xc flow events --type flow.transition
xc flow events --type flow.bypass
xc flow events --json | jq 'select(.type == "flow.acceptance.failed")'Programmatic
ts
import { readEvents } from '@xcoder/flow-engine'
const events = readEvents(process.cwd(), {
types: ['flow.transition', 'flow.acceptance.failed'],
limit: 1000,
})Audit example
How many bypasses did this repo's agents take last week?
bash
xc flow events --type flow.bypass --json | jq -s 'map(select(.at >= (now - 86400*7 | strftime("%Y-%m-%dT%H:%M:%SZ")))) | length'Group bypasses by invariant:
bash
xc flow events --type flow.bypass --json | jq -s 'group_by(.data.invariant) | map({invariant: .[0].data.invariant, count: length})'