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 (orNonefor 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:
- Normalizes event kind: known kinds are handled explicitly; unknown kinds are
stored as
engine_<kind>events. stage_progressevents update scan progress state in the database.finding_updatedevents upsert finding records.finding_verifiedevents update finding verification status.patch_createdandpatch_finding_completedevents update patch status fields (when backend adds first-class patch support).
Debugging
If events are not appearing in the dashboard:
- Confirm
event_sinkis passed toconfigure_core_runtime. - Log each
CoreEventin the sink before forwarding it. - Check
event.kind— unexpected kinds are dropped silently by older backends. - Check
event.run_idmatches the scan/verification id backend is tracking.