Event Reference and Observers¶
This document serves as the technical reference catalog and developer guide for the Episteme event system. It details all domain event schemas, available built-in observers, and patterns for emitting and consuming events.
For the conceptual system design, contextvar dispatch mechanics, and architectural guarantees, see Event System Architecture.
Base Event Schema¶
All pipeline events are Pydantic v2 models defined in pipeline/events/models.py inheriting from BaseEvent:
| Attribute | Type | Description |
|---|---|---|
timestamp |
datetime |
UTC timestamp when the event was created (defaults to datetime.now(timezone.utc)). |
run_id |
str \| None |
Identifier for the pipeline run the event belongs to. |
phase |
str \| None |
Pipeline phase name active when the event occurred (e.g., "phase_2_extraction"). |
Event Reference Catalog¶
Entity & Maturation Events¶
Events related to entity processing, textual envelope injection, entity linking, and centroid description synthesis.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
EntityProcessed |
Phase 2 (Extraction) | entity_id: strentity_name: strentity_type: str |
Emitted when an entity mention is extracted and processed. |
EnvelopeInjectionAttempted |
Phase 2 (Extraction) | mention_name: strchunk_id: str |
Attempt made to locate and attach a textual sentence envelope to a mention. |
EnvelopeInjectionFailed |
Phase 2 (Extraction) | mention_name: strmention_quote: strchunk_id: strreason: str |
Failed to locate or inject a textual envelope for a mention after all fallbacks. |
EntityLinkingCandidatesRetrieved |
Entity Linking / Fusion | mention_id: strmention_name: strcandidate_count: intcandidates: list[dict] |
Dense vector retrieval fetched candidate canonical entities for a mention. |
EntityLinkingReranked |
Entity Linking / Fusion | mention_id: strcandidate_id: strscore: floataccepted: boolthreshold: float |
Cross-encoder scored an entity linking candidate against an acceptance threshold. |
EntityMaturationSynthesized |
Maturation / Fusion | entity_id: strentity_name: strenvelope_count: inttop_k_used: intsynthesized_description: str |
LLM synthesized an aggregated entity description from top-K textual envelope centroids. |
Extraction & Relation Events¶
Events capturing candidate generation, cross-encoder reranking, and LLM relation decoding.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
DenseCandidatesGenerated |
Phase 2 (Dense Retrieval) | candidate_count: intcandidates: list[dict]entities_count: int |
Dense index retrieved candidate entity pairs for relation extraction. |
RerankerScoreAssigned |
Phase 2 (Reranker) | candidate_pair: tuple[str, str]score: floataccepted: boolthreshold: floatreranker_input: dict \| None |
Cross-encoder scored an entity pair, determining if it advances to LLM extraction. |
CandidateRejectedByThreshold |
Phase 2 (Reranker) | candidate_pair: tuple[str, str]score: floatthreshold: floatreason: str |
Candidate pair rejected due to score falling below the acceptance threshold. |
LLMRelationDecoded |
Phase 2 (LLM Extractor) | candidate_pair: tuple[str, str]relation: str \| Nonedirection: str \| Noneconfidence: float \| None |
LLM extracted or decoded a semantic relation between a candidate pair. |
SchemaValidationRejectedRelation |
Phase 2 (Validation) | candidate_pair: tuple[str, str]relation: strreason: str |
Extracted relation rejected because it does not conform to the active schema taxonomy. |
Graph & Fusion Events¶
Events recording persistence into Neo4j and entity fusion / resolution.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
TripleCommitted |
Persistence / Phase 2 & 3 | subject_id: strpredicate: strobject_id: strconfidence: floatscope: strsource_chunk_id: str |
Validated triple written to the property graph store. |
FusionDecisionMade |
Phase 3 (Fusion) | entities_fused: list[str]fusion_type: strconfidence: floatreason: str \| None |
Entity resolution decision merged multiple entity nodes into a canonical node. |
Pipeline Lifecycle & Projection Events¶
Events tracking phase execution, component start/stop boundaries, chunk generation, and projection.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
PhaseCompleted |
Pipeline Orchestrator | phase_name: strduration_seconds: floatartifact_count: intsuccess: boolerror_message: str \| None |
Pipeline phase finished execution. |
ComponentStarted |
Component Runners | component_name: strinput_description: str \| None |
Subsystem component began processing. |
ComponentCompleted |
Component Runners | component_name: strduration_seconds: floatoutput_description: str \| Nonesuccess: bool |
Subsystem component finished processing. |
ChunksGenerated |
Phase 1 (Chunking) | document_id: strdocument_title: strchunk_count: inttoken_count: int |
Text chunking completed for a source document. |
ArtifactProjected |
Projection Layer | artifact_type: strduration_seconds: floatsuccess: bool |
Intermediate artifact projected into the projection graph. |
LLM & Inference Telemetry Events¶
Events capturing detailed latency, prompt/completion tokens, and model parameters for cost and performance tracking.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
LLMDurationMeasured |
LLM Client Facades | model_name: strprompt_tokens: intcompletion_tokens: intduration_seconds: floatoperation: str |
Latency and token consumption measured for an LLM call. |
LLMGenerationCompleted |
LLM Client Facades | model_name: strprompt: Anyoutput_text: strtotal_tokens: intduration_seconds: floatcached: boolmodel_parameters: dict \| None |
Comprehensive generation record forwarded to observability backends (e.g., Langfuse). |
EmbeddingGenerationCompleted |
Embedding Models | model_name: strtext_count: inttotal_characters: inttotal_tokens: intduration_seconds: floatcached: bool |
Batch vector embedding generation completed. |
Progress, Evaluation & Validation Events¶
Events tracking progress indicators, epistemic metrics, and schema violation warnings.
| Event Class | Emitted In / Phase | Key Attributes | Description |
|---|---|---|---|
ProgressStarted |
Task Runners | task_name: strtotal_items: int \| Nonedescription: str |
Progress tracking started for an iterative or batch workload. |
ProgressAdvanced |
Task Runners | task_name: stradvance: int |
Progress advanced by advance units. |
ProgressCompleted |
Task Runners | task_name: str |
Progress tracking completed for a task. |
EvaluationCompleted |
Phase 5 (Evaluation) | evaluation_id: strrun_id: strmetrics: dict[str, float]outcome: str |
Evaluation suite finished running. |
EvaluationScoreLogged |
Evaluation / Metrics | metric_name: strscore: floatcomment: str \| Nonetarget_id: str \| None |
Specific evaluation score logged (e.g., OEP, GM-GBS, modularity). |
ValidationViolationDetected |
Validation Layer | rule_name: strsource_id: strtarget_id: strdescription: str |
Graph structural or schema violation detected. |
Built-in Observers¶
The event system includes several built-in observers located in pipeline/events/:
| Observer | Module | Primary Sink | Purpose & Configuration |
|---|---|---|---|
LoggingObserver |
observers.py |
Python standard logging / stdout |
Translates domain events into human-readable log messages with appropriate log levels. |
JsonlRunObserver |
observers.py |
Append-only .jsonl file |
Persists structured JSON lines for every event. Serves as the primary research audit trail and replay log. |
MetricsObserver |
observers.py |
In-memory counters / dictionaries | Aggregates summary statistics (acceptance rates, candidate counts, error counts) during execution. |
RichProgressObserver |
progress_observer.py |
Interactive TTY Console | Displays live, animated progress bars for tasks emitting ProgressStarted/Advanced/Completed. Requires an interactive terminal. |
LangfuseObserver |
langfuse_observer.py |
Langfuse API | Maps component lifecycle to spans, LLM events to generations, and evaluation metrics to scores. |
CompositeObserver |
observers.py |
Multiple Observers | Dispatches each event sequentially to an arbitrary list of child observers. |
How-To Guides¶
Emitting Events in Pipeline Components¶
Components retrieve the active emitter via get_event_emitter() from task-local context and emit typed event instances:
from pipeline.events import get_event_emitter
from pipeline.events.models import DenseCandidatesGenerated, RerankerScoreAssigned
class DenseRetrievalService:
def retrieve_candidates(self, entities: list[dict], run_id: str) -> list[dict]:
# Perform retrieval...
candidates = [...]
# Emit domain event:
get_event_emitter().emit(
DenseCandidatesGenerated(
run_id=run_id,
phase="phase_2_extraction",
candidate_count=len(candidates),
candidates=[{"pair": c["pair"], "score": c["score"]} for c in candidates[:10]],
entities_count=len(entities),
)
)
return candidates
Do not pass event_emitter through component constructors. get_event_emitter() uses Python's contextvars to
automatically resolve the active emitter for the current asynchronous execution context.
Configuring Observers for a Pipeline Run¶
Use CompositeObserver to fan out events to multiple sinks, and bind the emitter using use_event_emitter:
from pathlib import Path
from pipeline.events import (
SimpleEventEmitter,
CompositeObserver,
LoggingObserver,
JsonlRunObserver,
use_event_emitter,
)
from pipeline.events.langfuse_observer import LangfuseObserver
# 1. Instantiate desired observers:
observers = [
LoggingObserver(),
JsonlRunObserver(filepath=Path("runs/run_001/events.jsonl")),
]
# Add Langfuse if configured:
if langfuse_enabled:
observers.append(LangfuseObserver())
# 2. Wrap in CompositeObserver and register with emitter:
emitter = SimpleEventEmitter()
emitter.register_observer(CompositeObserver(observers=observers))
# 3. Bind to task-local context for pipeline execution:
with use_event_emitter(emitter):
result = await pipeline.run(pipeline_input)
Implementing a Custom Observer¶
To implement a new observer, subclass EventObserver and define the on_event handler:
import logging
from pipeline.events.bus import EventObserver
from pipeline.events.models import PipelineEvent, TripleCommitted
logger = logging.getLogger(__name__)
class TripleCounterObserver(EventObserver):
"""Custom observer that counts committed triples."""
def __init__(self) -> None:
self.count: int = 0
def on_event(self, event: PipelineEvent) -> None:
try:
if isinstance(event, TripleCommitted):
self.count += 1
except Exception as e:
# Observers must isolate exceptions so the pipeline is not interrupted
logger.warning(f"Error in TripleCounterObserver: {e}")
Best Practices¶
- Keep Payloads Compact:
- Do not embed entire documents, giant prompt templates, or full graph states inside events.
- Use stable identifiers (
entity_id,chunk_id,document_id) and counts. - Ensure Timezone Awareness:
- All events default to
datetime.now(timezone.utc). Never emit naive datetime objects. - Isolate Failures:
- Observers must catch their own exceptions. An observer crash must never fail the pipeline.
- Side-Effect Only:
- Observers must never mutate the received event instance or attempt to alter pipeline execution flow.