A tall stone wall inside the Plan Forge shop densely covered with hundreds of small bronze rune-stamps arranged in tidy categorical grid rows like a vast stamp-collection display, each stamp glowing softly amber, a smith with a magnifying glass examining one stamp while another smith catalogs in a ledger
Appendix V

Event Catalog

Every event Plan Forge emits over the WebSocket hub and the run journal, grouped by family, with emitter, trigger, the key payload fields, consumers, and retention. This appendix is the ebook companion to the canonical schema at pforge-mcp/EVENTS.md: the source-of-truth JSON examples live in the schema file; this page provides the orientation, classification, and lifecycle guidance that schema files rarely carry.

One transport, three audiences. Every event flows through the local WebSocket hub on ws://127.0.0.1:3101. Three different consumers read the same stream: the dashboard (live UI), the run journal (.forge/runs/*.jsonl for replay), and external bridges (Telegram, Slack, OpenClaw). Adding a new consumer is a matter of opening a socket and filtering by type; the schema below tells you exactly what fields each consumer can rely on.

Orientation

Plan Forge emits 38 distinct event types across eight families. The two most-watched families are lifecycle (run/slice progression) and LiveGuard (drift, incidents, secret scans). The remaining six families round out the picture: skills, Crucible, bridge approvals, escalation/CI, the lone client→server message, and the Tempering validation event.

FamilyCountWhat it tells you
Lifecycle7Run and slice progression, the primary signal the dashboard renders on the Progress tab.
Skills4Per-step skill execution, surfaces the same Progress UI but for forge_run_skill rather than forge_run_plan.
Crucible3Idea→spec smelt progression; powers the Forge-Master and Crucible dashboard tabs.
Bridge4External-channel approvals and notification dispatch status (Telegram, Slack, Discord, webhooks).
Escalation & CI2Quorum escalation and GitHub Actions dispatch.
Client→server1The single inbound message type clients can send (set-label).
LiveGuard10Drift, incidents, secret scans, watch snapshots, fix proposals, the production-ops feedback loop.
Tempering1The bug-fix validation event, emitted only on the green leg of the bug lifecycle.

Every emitted event shares a five-field envelope (documented below). Two enums, source and security_risk, are referenced throughout. Subscription mechanics are at Consuming the stream, and retention rules for events that escape the WebSocket (logged to .forge/runs/, posted to OpenClaw, etc.) are at Retention.

Common envelope

Every event, lifecycle, skill, LiveGuard, all of them, carries the same five-field envelope. Consumers can rely on these fields being present even on event types this catalog does not list (forward-compatible by design).

FieldTypeExamplePurpose
versionstring"1.0"Schema version. Always "1.0" today; reserved for future breaking changes.
typestring"slice-completed"The event-type identifier, the column heading you filter on. Stable across releases.
timestampstring (ISO-8601 UTC)"2026-05-18T09:30:00.000Z"Emission time. Always UTC; never local time.
sourceenum (9 values)"orchestrator"Which subsystem emitted the event, see below.
security_riskenum (5 values)"none"Risk classification at emission time, see below. Defaults to "none".

source enum

The nine subsystems that emit events. New subsystems are added rarely; existing values are never repurposed.

ValueSubsystem
orchestratorPlan execution engine in orchestrator.mjs, emits the lifecycle family.
workerPer-slice child process. Rare, most worker telemetry is wrapped by the orchestrator.
hubThe WebSocket hub itself, emits connected on new sessions.
bridgeExternal notification bridge, emits bridge-notification-*.
liveguardThe LiveGuard subsystem, emits the entire LiveGuard family.
crucibleThe idea→spec funnel, emits crucible-smelt-*.
skillSkill runner, emits the skill family.
watcherCross-project watcher, emits watch-*.
auditAudit-classifier loop. Rare, emits reclassification events when run.

security_risk enum

The risk classification attached to every event at emission time. Subscribers (and OpenClaw) can filter by this field to focus on high-risk activity.

ValueMeaningTypical event types
noneRoutine lifecycle activity with no security implication.slice-started, slice-completed, skill-*
lowActivity that touches managed secrets or external networks but is expected.bridge-notification-sent, ci-triggered
mediumQuorum dispatch, agent handoff, escalation, outside the routine path but authorised.slice-escalated, approval-requested
highDrift or incident events that warrant a human glance.liveguard-incident, liveguard-secret-scan (when findings present)
criticalReserved for active-incident escalations, emitted by the audit subsystem only.(none in default catalog)

Lifecycle events

source: "orchestrator" (one exception). The Progress tab on the dashboard is driven entirely by this family; the run journal at .forge/runs/<run-id>.jsonl persists every one of them for replay.

EventEmitterTriggerKey payload
connectedhubA client opens a WebSocket connection to ws://127.0.0.1:3101.clientId, label, historySize (how many past events are about to be replayed).
run-startedorchestratorrunPlan() begins.plan, mode, model, sliceCount, executionOrder.
slice-startedorchestratorA slice begins execution (after its gate passes).sliceId, title.
slice-completedorchestratorA slice passes all validation gates.sliceId, status: "passed", duration (ms), tokens (in/out + model), cost_usd.
slice-failedorchestratorA slice or its validation gate fails after retry budget exhausted.sliceId, status: "failed", error, failedCommand.
run-completedorchestratorAll slices finish (mixed pass/fail allowed).status, results (passed/failed counts), totalDuration, cost, sweep, analyze, report.
run-abortedorchestratorExecution aborted via forge_abort.sliceId (the slice that was running), reason.

Full JSON examples: EVENTS.md — Event Types.

Skill events

source: "skill". Emitted by skill-runner.mjs on every forge_run_skill invocation. The structure mirrors lifecycle events deliberately: dashboard code reuses the same renderer for both families.

EventTriggerKey payload
skill-startedSkill begins execution.skillName, stepCount, args.
skill-step-startedA skill step begins.skillName, stepNumber, stepName.
skill-step-completedA skill step finishes (pass or fail).stepNumber, stepName, status, duration.
skill-completedAll skill steps finish.skillName, status, stepsPassed, stepsFailed, totalDuration.

Crucible events

source: "crucible". Emitted as smelts progress through the idea→hardened-spec funnel. The payload is wrapped in a data object, matching the LiveGuard convention rather than the flat-payload lifecycle convention, consumers should branch on family rather than assume one shape.

EventTriggerKey payload (under data)
crucible-smelt-startedforge_crucible_submit creates a new smelt.id, lane, source.
crucible-smelt-updatedforge_crucible_ask records an answer and advances the interview.id, questionIndex, totalQuestions.
crucible-smelt-finalizedforge_crucible_finalize claims a phase number and writes docs/plans/Phase-NN.md.id, phaseName, planPath.

Bridge events

source: "bridge". Emitted by the notification bridge when it pauses for external approval or dispatches a webhook. Configure the bridge via the extensions/ notify-* extensions and PFORGE_BRIDGE_SECRET (see Appendix U — Server Ports).

EventTriggerKey payload
approval-requestedThe bridge pauses execution and requests external approval.runId, plan, channels, timeoutMinutes.
approval-receivedAn external approval callback is received.runId, action (approve / deny), approver.
bridge-notification-sentA webhook notification is successfully dispatched to a channel.channel, platform, eventType, status: "sent".
bridge-notification-failedA webhook dispatch fails (network error, bad status, etc.).channel, error.

Escalation & CI events

source: "orchestrator". Two events that mark deliberate routing decisions: quorum escalation when a slice's complexity score exceeds the threshold, and CI dispatch when a plan run triggers a GitHub Actions workflow.

EventTriggerKey payload
slice-escalatedA slice is escalated to quorum for multi-model consensus review.sliceId, reason, models (array of model IDs).
ci-triggeredA CI workflow is dispatched from a plan run.workflow, ref, inputs.

Client→server messages

The only inbound message type the hub honours. Send it once after opening the WebSocket to identify your client in the session registry (visible in the dashboard's Connections badge).

MessagePurposePayload
set-labelUpdate the client's label in the session registry.{ "type": "set-label", "label": "my-dashboard" }

LiveGuard events

source: "liveguard". The production-ops feedback family, emitted by forge_drift_report, forge_incident_capture, forge_alert_triage, forge_secret_scan, forge_fix_proposal, and the watcher tools. Most carry security_risk: "low" or higher; filter on security_risk >= medium to drive paging.

EventTriggerKey payload (under data)Default risk
liveguard-driftDrift score changes.score, delta, violations, timestamp.low (escalates with delta)
liveguard-incidentAn incident is captured or resolved.id, severity, description, status.high
liveguard-triageforge_alert_triage runs.alertCount, topSeverity, rankedAlerts.medium
liveguard-secret-scanA secret scan completes.clean, findingsCount, scannedAt.none if clean; high if findings.
liveguard-tool-completedAny LiveGuard tool finishes executing.tool, status, durationMs.none
fix-proposal-readyforge_fix_proposal generates a new fix plan.fixId, plan (path to LIVEGUARD-FIX plan), source.medium
watch-snapshot-completedforge_watch builds a snapshot of a target project.target, runState, runId, anomalyCount, cursor, counts.none
watch-anomaly-detectedforge_watch detects one or more anomalies (one event per invocation, not per anomaly).target, runId, anomalies (array of {code, severity, message}).medium (escalates with severity)
watch-advice-generatedforge_watch analyze-mode produces narrative advice from a frontier model.target, runId, model, tokensIn, tokensOut, durationMs, advicePreview.low

Anomaly codes used in watch-anomaly-detected: stalled, tokens-zero, high-retries, slice-failed, all-skipped, gate-on-prose, model-escalated, quorum-dissent, quorum-leg-stalled, skill-step-failed.

Tempering events

source: "audit". One event, emitted only on the green leg of the bug lifecycle, when forge_bug_validate_fix confirms all scanners pass re-run. There is no matching tempering-bug-validated-broken; the validation tool returns the result to the caller without emitting on the red leg, to keep the dashboard's Bugs Fixed tile a positive-only feed.

EventTriggerKey payload (under data)
tempering-bug-validated-fixedforge_bug_validate_fix confirms a bug is fixed, all scanners pass re-run.bugId, scanner, verdict: "fixed", attempt (timestamp, scanners array, result).

Consuming the stream

The simplest possible consumer, a Node script that prints every event:

consume.mjs, minimal event subscriber
import WebSocket from "ws";

const ws = new WebSocket("ws://127.0.0.1:3101");

ws.on("open", () => {
  ws.send(JSON.stringify({ type: "set-label", label: "my-consumer" }));
});

ws.on("message", (raw) => {
  const evt = JSON.parse(raw.toString());
  // Filter however you like:
  if (evt.security_risk === "high" || evt.security_risk === "critical") {
    console.error(`[HIGH] ${evt.type}`, evt);
  } else if (evt.type?.startsWith("slice-")) {
    console.log(`[LIFECYCLE] ${evt.type} sliceId=${evt.sliceId} status=${evt.status ?? ""}`);
  }
});

On connection the hub replays buffered events from the in-memory ring (default ~500 events, see hub.mjs). To enable bearer-token auth on the hub, set PFORGE_BRIDGE_SECRET, the consumer then sends Authorization: Bearer <secret> on the upgrade request.

Retention

Events live in up to four places after emission. The retention rules below tell you how long each consumer keeps them, and which fields each consumer can rely on having.

SinkWhat is keptRetentionHow to read it
WebSocket hub (in-memory ring)All events.~500 events (oldest evicted). Wiped on hub restart.Connect to ws://127.0.0.1:3101; the hub replays the ring on connected.
Run journalLifecycle, skill, escalation, CI, everything tied to a runId.Forever (until you delete the file). One JSONL per run at .forge/runs/<run-id>.jsonl.forge_home_snapshot, or jq over the JSONL file.
LiveGuard cacheThe most recent liveguard-drift, liveguard-secret-scan, liveguard-incident snapshots.One snapshot per type at .forge/liveguard-*.json, overwritten on next emission.forge_drift_report, forge_secret_scan (returns the cached snapshot when within cacheMaxAgeMinutes).
OpenClaw analytics (opt-in)Whatever the PreAgentHandoff hook posts, typically a roll-up of drift, MTTR, and open incidents.Determined by the OpenClaw deployment; not Plan Forge's responsibility.OpenClaw API.

See also