-
Notifications
You must be signed in to change notification settings - Fork 0
noetl events
noetl-events is the dedicated home for the wire-format event
envelope every NoETL Rust binary emits. Carved out of
noetl-executor::events in cli#49
- cli#50 as round 1+2 of the EE-4 sequence under noetl/ai-meta#49.
- crates.io:
noetl-events 0.1.0(first release, 2026-06-05). - Source:
repos/cli/events/. - Re-export contract:
noetl-executor::eventsre-exports every public symbol verbatim, so existing call sites in the cli + noetl-worker keep working through the executor's re-export. - Adopted directly by: noetl-server (server v2.9.0, via noetl/server#38).
Until EE-4, the envelope lived in noetl-executor::events. That
worked while only producers needed it (cli local-mode runner,
noetl-worker NATS pull consumer). Once noetl-server started
producing events itself in Phase D R2 (noetl/server#31)
the server had to mirror the envelope by hand in its
EventRequest struct + doc comments, kept in sync by code review.
That alignment is the kind of thing that quietly drifts on the
next round of changes.
The fix: a small, dependency-free crate that every component
can link without dragging in the rest of the executor's surface
(playbook parser, runtime trait, tools bridge, …). Server now
takes a direct dep on noetl-events and pins the shared subset
via From / TryFrom conversion impls — drift fails the build
instead of the kind-validation cycle.
pub struct ExecutorEvent {
pub execution_id: i64,
pub event_type: String,
pub step: String,
pub status: String,
pub created_at: DateTime<Utc>,
#[serde(alias = "payload")]
pub context: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub worker_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub meta: Option<serde_json::Value>,
}
#[async_trait]
pub trait EventSink: Send + Sync {
async fn emit(&self, event: ExecutorEvent) -> Result<()>;
}
pub struct EventEmitter {
pub sink: Arc<dyn EventSink>,
pub execution_id: i64,
}
pub struct NoopSink; // Default sink for tests / skeleton runs.Field naming intentionally mirrors the Python EventEmitRequest
shape so events produced by either stack project against the same
noetl.event columns. See the noetl/noetl wiki page
handle_event_timing
for the field catalogue and per-field semantics.
-
context(the field on the envelope) accepts bothcontextand the legacypayloadkey during deserialization (#[serde(alias = "payload")]) — pre-PR-EE-2 producers that still writepayloadkeep working. -
execution_idisi64(matches the Pythonnoetl.eventbigintcolumn + the executor'sBridgeContext.execution_id). -
event_idisOption<i64>; producers that don't fill it get the server-sidenoetl.snowflake_id()default. -
worker_id+metaare optional and skip serialization whenNone(older wire-format consumers don't see unfamiliar keys). -
created_atis stamped at emit time so per-component ordering is preserved across server-clock skew.
noetl-executor 0.4.0 (the first executor release after EE-4)
ships an events module that is a 1-line re-export:
// noetl-executor/src/events.rs
pub use noetl_events::{EventEmitter, EventSink, ExecutorEvent, NoopSink};Every existing call site (use noetl_executor::events::ExecutorEvent;)
keeps working unchanged. Direct callers (noetl-server, future
crates) can take noetl-events = "0.1" and skip the executor
entirely.
noetl/server#38 wired
the server-side adoption. The server's EventRequest struct
stays distinct — it legitimately carries five server-only
fields (result_kind, result_uri, event_ids, actionable,
informative) and uses String wire format for execution_id /
event_id (browser JSON-number precision). The SHARED SUBSET is
anchored to noetl_events::ExecutorEvent via:
impl From<noetl_events::ExecutorEvent> for EventRequest { ... }
impl TryFrom<&EventRequest> for noetl_events::ExecutorEvent { ... }with four wire-compat tests pinning the round-trip semantics.
See the noetl-server wiki page event-envelope for the server-side detail.
Workspace members that depend on noetl-events from inside the
cli workspace should set both path and version per Cargo's
recommendation:
# In a sibling workspace member's Cargo.toml
noetl-events = { version = "0.1.0", path = "../events" }Local dev uses the path; cargo publish resolves against the
crates.io version constraint. External consumers (noetl-server,
future bins) take the version dep only:
noetl-events = "0.1"| Version | Date | Headline |
|---|---|---|
0.1.0 |
2026-06-05 | First release. Wire-format envelope + EventSink trait + EventEmitter + NoopSink carved out of noetl-executor::events. Six tests preserved verbatim (envelope round-trip, alias compat, optional-field omit-when-none, full-fidelity all-fields-set). |
-
noetl-executorworkspace crate — ships the events module re-export. -
noetl/server
event-envelopepage — server-side adoption + conversion impls. -
agents/rules/observability.mdPrinciple 3 — snowflake IDs from the application (theevent_idfield). -
agents/rules/safety.md— public-repo discipline forcontextpayloads (no secrets). - noetl/noetl wiki: handle_event_timing — Python
EventEmitRequestfield catalogue. - noetl/ai-meta wiki: Umbrella: Rust Server FastAPI Parity Port — EE-4 sequence under #49.
NoETL CLI
Contexts
- Context model
context addcontext init --from-gatewaycontext updatecontext port-forwardcontext list / use / current / delete
Auth
Architecture
Cross-wiki