Skip to content

Events

An event is a typed domain signal whose deliveries are allowed to make the program do something. It is the third member of Sema’s telemetry-and-reaction family, and the one that closes the loop:

  • collector records telemetry that can never drive control flow.
  • monitor answers “has this stream’s distribution shifted?”
  • event is the construct whose deliveries do work — the missing counterpart that corpus code used to improvise as ambient alert(...) calls, watcher tasks with manual cancel, and approval-record polling.

Callback and listener APIs are invisible to the type system: they don’t appear in effect rows, they escape policies, and they hide the delivery graph. Fire-and-forget delivery with unbounded queues loses signals silently. Sema replaces all of that with a construct where the payload is a contract, delivery is journaled and bounded, handlers run under their own policy, and the compiler sees the complete emit/subscribe graph at load time.

An event declares a nominal payload record; a function emits it; a subscriber handles it:

event IncidentQuarantined:
sem "An ingested item was quarantined by a semantic guard"
incident: Incident
evidence: SemanticsViolation
key incident.region # optional per-key ordering/partition
def quarantine(i: Incident, v: SemanticsViolation) -> None !{event.emit, fs.write}:
audit_store(i, v)
emit IncidentQuarantined(incident=i, evidence=v)
subscriber quarantine_review on IncidentQuarantined:
sem "Queue quarantined incidents for analyst review"
where event.incident.severity >= Severity.high # deterministic, effect-free filter
queue ring(4096), on_full=block
handle event !{db.write, event.emit}:
review_queue.push(event.incident, event.evidence)

The verified crisis-logistics corpus uses exactly this shape to quarantine a public briefing that fails a semantic guard, so the block becomes a persisted record for review instead of a silent drop:

event IncidentQuarantined:
sem "A public briefing was blocked by a semantic guard before publication"
briefing: PublicBriefing sem "The withheld briefing, redacted but unpublished"
evidence: SemanticsViolation sem "Guard verdict with predicate, judge, and excerpts"
# ... inside publish_public_briefing, under an except SemanticsViolation branch:
emit IncidentQuarantined(briefing=safe, evidence=violation)
subscriber quarantine_review on IncidentQuarantined:
sem "Persist quarantined briefings for analyst review"
queue ring(256), on_full=block
handle event !{fs.write}:
write_quarantine_record("state/quarantine/briefings.jsonl", event.briefing, event.evidence)

The payload is a boundary contract at the emit site

Section titled “The payload is a boundary contract at the emit site”

An event declaration is a nominal payload record: its fields carry sem descriptors, where refinements, and coerce by normalizers exactly as struct fields do. The whole payload is a full boundary contract at the emit site — a payload that fails its contract never enters the stream. The failure is a typed ContractViolation blamed on the emitter, not on some downstream handler. emit adds event.emit to the caller’s effect row, and policies confine it per event type:

forbid event.emit except event.emit(IncidentQuarantined)

Subscribers: static, bounded, policy-confined

Section titled “Subscribers: static, bounded, policy-confined”

A subscriber is a static declaration, parallel in shape to a monitor. Registration happens at container/module load, so the compiler sees the complete delivery graph — it warns on events with no subscriber (a dead signal) and on statically detectable emit cycles. Three parts matter:

  • where <filter> — an effect-free deterministic filter. It cannot perform I/O or call a model; it only decides whether this delivery is relevant.
  • queue ring(n), on_full=<mode> — a bounded queue. ring(n) is required; an unbounded queue is a compile error (same rule as collectors). on_full is one of block (default — backpressure to the emitter), drop_oldest, or fail. Drops are journaled as EventDropped records — never silent.
  • handle event !{...} — the handler, with its own effect row. It runs under the subscriber’s policy envelope, never the emitter’s.

The robotics-cell corpus brings the cell to a deterministic safe stop when a monitor emits a fault event — the monitor detects drift, the event carries it, the subscriber reacts:

subscriber safe_stop on CellFaultDetected:
sem "Bring the cell to a deterministic safe stop when telemetry drifts"
queue ring(64), on_full=block
handle event !{ffi.call}:
hardware_safe_stop()
log.info("cell safe-stopped after telemetry drift", order=event.order_id)

Delivery semantics: journaled, ordered, exactly-once

Section titled “Delivery semantics: journaled, ordered, exactly-once”

Emission appends an EventEmitted record to the same hash-chained journal as model calls and contract verdicts — the event bus is not a side channel, and replay reproduces delivery order and handler effect traces exactly. Delivery is:

  • Asynchronous, with per-subscriber FIFO order per emitter (per key value when declared; cross-key deliveries are concurrent).
  • Exactly-once per subscriber within a run.
  • Structured — handlers run as structured children of the scope that owns the subscriber (the module’s container scope by default, or the enclosing supervise when declared inside one). There are no orphan handler tasks. A handler failure is a typed SubscriberFailure routed to the owning supervision scope; the emitter is never affected.

Shutdown drains queues under the container deadline and journals undelivered events as EventUndelivered. Handler-emitted events are depth-budgeted (default 16) against cycles; exceeding it is a typed EventCycleBudgetExceeded on the emitting handler.

Runtime lifecycle events are ordinary subscribers

Section titled “Runtime lifecycle events are ordinary subscribers”

The prelude declares runtime lifecycle events on this same construct — Alert (the target of the alert(...) sugar), MonitorVerdictChanged, HealEvent, ContainerStarted, PolicyDenied, RepairExhausted. So operational reactions like “page someone when a heal escalates” are ordinary subscribers, not special runtime hooks:

subscriber page_oncall on RepairExhausted:
queue ring(64), on_full=block
handle event !{net.connect}:
page(event.supervisor, event.outcome)
  • Emit under a policy without event.emit → typed denial.
  • A handler effect row exceeding the subscriber’s policy → compile error.
  • A contract-failing payload → emitter-blamed ContractViolation; nothing is delivered.
  • Queue overflow under on_full=fail → typed EventBackpressure at the emit site.
  • An event with no subscriber → dead-signal warning at compile time.
  • A calibrated semantics() guard inside a where filter → a decision site like any other; monitor coverage applies.
  • sema check <project> builds the emit/subscribe graph, warns on dead signals and static emit cycles, verifies handler effect rows against subscriber policies, and rejects unbounded queues.
  • Emission and delivery are journaled (EventEmitted, EventDropped, EventUndelivered), so replay reproduces the exact delivery sequence.
  • An event stream is a valid monitor target (monitor X on IncidentQuarantined:).