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.
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.
| Family | Count | What it tells you |
|---|---|---|
| Lifecycle | 7 | Run and slice progression, the primary signal the dashboard renders on the Progress tab. |
| Skills | 4 | Per-step skill execution, surfaces the same Progress UI but for forge_run_skill rather than forge_run_plan. |
| Crucible | 3 | Idea→spec smelt progression; powers the Forge-Master and Crucible dashboard tabs. |
| Bridge | 4 | External-channel approvals and notification dispatch status (Telegram, Slack, Discord, webhooks). |
| Escalation & CI | 2 | Quorum escalation and GitHub Actions dispatch. |
| Client→server | 1 | The single inbound message type clients can send (set-label). |
| LiveGuard | 10 | Drift, incidents, secret scans, watch snapshots, fix proposals, the production-ops feedback loop. |
| Tempering | 1 | The 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).
| Field | Type | Example | Purpose |
|---|---|---|---|
version | string | "1.0" | Schema version. Always "1.0" today; reserved for future breaking changes. |
type | string | "slice-completed" | The event-type identifier, the column heading you filter on. Stable across releases. |
timestamp | string (ISO-8601 UTC) | "2026-05-18T09:30:00.000Z" | Emission time. Always UTC; never local time. |
source | enum (9 values) | "orchestrator" | Which subsystem emitted the event, see below. |
security_risk | enum (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.
| Value | Subsystem |
|---|---|
orchestrator | Plan execution engine in orchestrator.mjs, emits the lifecycle family. |
worker | Per-slice child process. Rare, most worker telemetry is wrapped by the orchestrator. |
hub | The WebSocket hub itself, emits connected on new sessions. |
bridge | External notification bridge, emits bridge-notification-*. |
liveguard | The LiveGuard subsystem, emits the entire LiveGuard family. |
crucible | The idea→spec funnel, emits crucible-smelt-*. |
skill | Skill runner, emits the skill family. |
watcher | Cross-project watcher, emits watch-*. |
audit | Audit-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.
| Value | Meaning | Typical event types |
|---|---|---|
none | Routine lifecycle activity with no security implication. | slice-started, slice-completed, skill-* |
low | Activity that touches managed secrets or external networks but is expected. | bridge-notification-sent, ci-triggered |
medium | Quorum dispatch, agent handoff, escalation, outside the routine path but authorised. | slice-escalated, approval-requested |
high | Drift or incident events that warrant a human glance. | liveguard-incident, liveguard-secret-scan (when findings present) |
critical | Reserved 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.
| Event | Emitter | Trigger | Key payload |
|---|---|---|---|
connected | hub | A 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-started | orchestrator | runPlan() begins. | plan, mode, model, sliceCount, executionOrder. |
slice-started | orchestrator | A slice begins execution (after its gate passes). | sliceId, title. |
slice-completed | orchestrator | A slice passes all validation gates. | sliceId, status: "passed", duration (ms), tokens (in/out + model), cost_usd. |
slice-failed | orchestrator | A slice or its validation gate fails after retry budget exhausted. | sliceId, status: "failed", error, failedCommand. |
run-completed | orchestrator | All slices finish (mixed pass/fail allowed). | status, results (passed/failed counts), totalDuration, cost, sweep, analyze, report. |
run-aborted | orchestrator | Execution 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.
| Event | Trigger | Key payload |
|---|---|---|
skill-started | Skill begins execution. | skillName, stepCount, args. |
skill-step-started | A skill step begins. | skillName, stepNumber, stepName. |
skill-step-completed | A skill step finishes (pass or fail). | stepNumber, stepName, status, duration. |
skill-completed | All 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.
| Event | Trigger | Key payload (under data) |
|---|---|---|
crucible-smelt-started | forge_crucible_submit creates a new smelt. | id, lane, source. |
crucible-smelt-updated | forge_crucible_ask records an answer and advances the interview. | id, questionIndex, totalQuestions. |
crucible-smelt-finalized | forge_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).
| Event | Trigger | Key payload |
|---|---|---|
approval-requested | The bridge pauses execution and requests external approval. | runId, plan, channels, timeoutMinutes. |
approval-received | An external approval callback is received. | runId, action (approve / deny), approver. |
bridge-notification-sent | A webhook notification is successfully dispatched to a channel. | channel, platform, eventType, status: "sent". |
bridge-notification-failed | A 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.
| Event | Trigger | Key payload |
|---|---|---|
slice-escalated | A slice is escalated to quorum for multi-model consensus review. | sliceId, reason, models (array of model IDs). |
ci-triggered | A 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).
| Message | Purpose | Payload |
|---|---|---|
set-label | Update 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.
| Event | Trigger | Key payload (under data) | Default risk |
|---|---|---|---|
liveguard-drift | Drift score changes. | score, delta, violations, timestamp. | low (escalates with delta) |
liveguard-incident | An incident is captured or resolved. | id, severity, description, status. | high |
liveguard-triage | forge_alert_triage runs. | alertCount, topSeverity, rankedAlerts. | medium |
liveguard-secret-scan | A secret scan completes. | clean, findingsCount, scannedAt. | none if clean; high if findings. |
liveguard-tool-completed | Any LiveGuard tool finishes executing. | tool, status, durationMs. | none |
fix-proposal-ready | forge_fix_proposal generates a new fix plan. | fixId, plan (path to LIVEGUARD-FIX plan), source. | medium |
watch-snapshot-completed | forge_watch builds a snapshot of a target project. | target, runState, runId, anomalyCount, cursor, counts. | none |
watch-anomaly-detected | forge_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-generated | forge_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.
| Event | Trigger | Key payload (under data) |
|---|---|---|
tempering-bug-validated-fixed | forge_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:
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.
| Sink | What is kept | Retention | How 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 journal | Lifecycle, 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 cache | The 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
- pforge-mcp/EVENTS.md, the canonical JSON-schema source for every event. This appendix is the orientation and classification companion; full payloads live there.
- Chapter 29 — Integrating from Outside, building external consumers (dashboards, paging, custom bridges) that subscribe to the hub.
- Compliance & Data Residency — Observability Export, OTel collector wiring; many of the events in this catalog are also emitted as OTel spans.
- Chapter 12 — The Dashboard, the reference UI consumer; reading its source is the fastest way to see how each event family is rendered.
- Appendix T —
openclaw, configuring the optional OpenClaw analytics sink. - Appendix U — Server Ports,
PFORGE_BRIDGE_SECRET,PLAN_FORGE_WS_PORT, and other env vars that gate hub access. - Customization — Lifecycle Hooks Reference, the hooks that fire in response to lifecycle events (
postSlice,preDeploy, etc.).