Skip to content

Event System

vega-core emits structured CoreEvent values through an EventBus as stages progress. vega-backend provides an event sink when starting a run and uses the events to update its database and stream real-time progress to the dashboard.

Location

src/events/

CoreEvent

@dataclass(frozen=True)
class CoreEvent:
    event_id: str
    run_id: str
    stage: StageName | None
    kind: str
    payload: Mapping[str, Any]
    message: str | None = None

Fields:

  • event_id — UUID generated per event.
  • run_id — identifies the plan, scan, or verify/patch run.
  • stage — which stage emitted the event (or None for global events).
  • kind — event type string (see tables below).
  • payload — structured data specific to the event kind.
  • message — optional human-readable summary.

EventSink protocol

class EventSink(Protocol):
    def __call__(self, event: CoreEvent) -> None: ...

Pass a callable to configure_core_runtime(event_sink=my_sink). The sink is called synchronously from the stage thread. Keep it non-blocking; use a queue or background thread for database writes.

EventBus

class EventBus:
    def __init__(self, sink: EventSink | None = None) -> None: ...

    def emit(self, event: CoreEvent) -> None: ...

    # Stage lifecycle helpers (called automatically by BaseStage)
    def stage_started(self, context: RunContext, stage: StageName) -> None: ...
    def stage_progress(self, context, stage, progress: Progress) -> None: ...
    def stage_completed(self, context, stage, output: StageOutput) -> None: ...
    def stage_failed(self, context, stage, error: StageError) -> None: ...
    def stage_cancelled(self, context, stage, progress: Progress | None) -> None: ...

    # Domain event helper (called by concrete stages)
    def domain_event(
        self, context, *, stage: StageName, kind: str,
        payload: Mapping[str, Any], message: str | None = None
    ) -> None: ...

BaseStage.run(...) calls the lifecycle helpers automatically. Concrete stages call context.events.domain_event(...) to emit business-level events.

Complete event kind reference

Stage lifecycle events

These are emitted automatically by BaseStage for every stage:

Kind When
stage_started Stage begins
stage_progress Intermediate progress update
stage_completed Stage finished with COMPLETED status
stage_failed Stage finished with FAILED status
stage_cancelled Stage finished with CANCELLED status

stage_progress payload includes a Progress object with completed, total, and optional message.

Plugin selection events

Kind Payload
plugin_selection_started {repo_id, registered_plugin_ids}
plugin_selection_completed {plugin_id, plugin_version, selection_reason, metadata}
plugin_selection_fallback {plugin_id, plugin_version, selection_reason, metadata}

Plan events

Kind Payload
plan_source_snapshot_read {source_snapshot, source_scope, file_count}
plan_component_manifest_ready {module_count, component_count}
plan_artifact_created {plan_artifact, module_count, component_count}

Threat model events

Kind Payload
threat_model_artifact_created {strategy, threat_model_artifact}
threat_model_artifact_created (static) {strategy, source: "plugin_static_threat_model", plugin_id, threat_model_artifact}
threat_model_cache_reused {artifact_ref, strategy, fingerprint}

Audit events

Worker events share a common payload from _component_worker_payload() and component_worker_completed adds finding_count. The base payload contains: {component_id, module_name, component_name, component_index, total_components, worker_number, total_workers, strategy}.

Kind Additional payload fields
component_worker_started (base only)
component_worker_completed {finding_count}
component_worker_failed {error}
component_worker_cancelled (base only)
component_worker_skipped (base only)
finding_updated see below
raw_findings_ready {raw_findings_artifact, finding_count, strategy}

finding_updated payload

The full canonical finding object is sent as the payload. Key fields include: finding_id, bug_id, title, blame_path, bug_class, severity, affected_paths, status, root_cause, buggy_locations, evidence, component_id, source_stage, source_strategy, and metadata.

See Audit Stage for the complete field list. Note that the canonical finding uses blame_path (not file_path) and affected_paths (not file_path/line_start/line_end).

Backend persists findings upserted from this event.

Triage events

Kind Payload
raw_findings_batch_received {run_id, finding_count}
finding_deduped {bug_id, finding_ids}

Verify events

Kind Payload
finding_verified {finding_id, status, triggerable, verification_evidence}
verification_evidence_created {verification_evidence, bug_count, verified_count}

Patch events

Kind Payload
patch_candidate_created {finding_id, attempt, changed_line_count}
patch_minimality_reviewed {finding_id, status, reason}
patch_apply_completed {finding_id, status, applied_paths}
patch_vulnerability_rechecked {finding_id, status, vulnerability_still_present}
patch_integration_test_completed {finding_id, status, summary}
patch_finding_completed {finding_id, status, confidence}
patch_created {patch, patch_count, accepted_patch_count, rejected_patch_count, skipped_unverified_count, validation: {...}}

Agent events

All agent events include a common base payload {task_id, purpose, provider, model}.

Kind Additional payload fields
agent_started (base only)
agent_log {line} — one event per log line, emitted after task completes
agent_stream {stream, chunk, [codex_event_type, item_type, text, command, usage]} — emitted live during execution
agent_completed {status, error: null, artifacts}
agent_failed {status, error: {code, message, retryable, metadata}, artifacts}
agent_cancelled {status, error: {code, message, ...}, artifacts}

Backend consumption

vega-backend receives CoreEvent values through the event sink and:

  1. Normalizes event kind: known kinds are handled explicitly; unknown kinds are stored as engine_<kind> events.
  2. stage_progress events update scan progress state in the database.
  3. finding_updated events upsert finding records.
  4. finding_verified events update finding verification status.
  5. patch_created and patch_finding_completed events update patch status fields (when backend adds first-class patch support).

Debugging

If events are not appearing in the dashboard:

  1. Confirm event_sink is passed to configure_core_runtime.
  2. Log each CoreEvent in the sink before forwarding it.
  3. Check event.kind — unexpected kinds are dropped silently by older backends.
  4. Check event.run_id matches the scan/verification id backend is tracking.