Redouble AI Nucleo documentation
Part 13 · Inside the runtime

Inside events

ai.redouble.nucleo.events · nucleo-core

This page is for people working on the runtime itself: the full category hierarchy, the rules the event classes keep, what every event carries and renders, and the tests that hold each rule. To use events, read the guide first.

Category hierarchy

Every framework-managed event belongs to exactly one sealed category. Broad subscribers pick categories; within a category, pattern matching on sealed subtypes is exhaustive.

JobEvent (unsealed contract - framework categories below, plus room for app-specific events)
├── LifecycleEvent (sealed) - platform-fired job state transitions
│   ├── JobScheduled (final)
│   ├── JobStartedEvent (final)
│   ├── TerminalEvent (sealed) - permits the base class and the two verdict markers
│   │   ├── AbstractTerminalEvent (sealed base of the four the dispatcher fires for a job it ran)
│   │   │   ├── JobCompletedEvent (final, also a SuccessEvent)
│   │   │   ├── JobFailedEvent, JobTimedOut (final, also FailureEvents)
│   │   │   └── JobCancelled (final)             - explicit stop, neither success nor failure
│   │   ├── SuccessEvent (sealed) - JobCompletedEvent
│   │   └── FailureEvent (sealed) - JobFailedEvent, JobTimedOut,
│   │                               DependencyFailureEvent (final, outside the base class)
│   ├── OrchestratorIdleEvent (final)
│   └── OrchestratorResumedEvent (final)
│
├── WorkflowTerminationEvent (sealed) - workflow-scope cleanup signal (parallel to LifecycleEvent)
│   └── WorkflowCompleteEvent (final)
│
├── ProgressEvent (sealed) - user-visible progress / streaming updates
│   └── JobProgressEvent (non-sealed, parametric payload)
│       └── ContentStreamEvent (inherits ProgressEvent via parent)
│
├── RetryEvent (sealed, in retry/) - retry / recovery signals
│   ├── RateLimitRetryEvent (final) - 429 transparent retries
│   ├── TransientErrorRetryEvent (final) - 5xx fault AND 529 overload retries (the error details distinguish them)
│   ├── OutputTruncationRetryEvent (final)
│   └── ResponseCorrectionRetryEvent (final)
│
├── NotificationEvent (sealed) - discrete user notifications
│   └── UserNotificationEvent (final)
│
├── OperationalEvent (sealed) - high-volume operational telemetry
│   └── LimiterEvent (final record)
│
├── SystemEvent (sealed) - no JobSnapshot, framework-internal state
│   └── SchedulerEvent (final)
│
└── HeartbeatEvent (sealed, in heartbeat/) - the heartbeat control plane, no JobSnapshot
    ├── HeartbeatRequested, CancelHeartbeat (records) - the two requests a job publishes
    └── HeartbeatScheduled, HeartbeatRebound, HeartbeatFired, HeartbeatFireFailed (records) - the Heart's notifications

JobEvent itself is deliberately not sealed. The sealed categories close the framework's own event surface while leaving room for application-specific events to implement JobEvent directly. One ships: MessageCompleteEvent, the end of one exchange in a chat session, outside every category, so it reaches only subscribers that ask for it by type or subscribe to everything. Every other concrete event in the three packages is in exactly one category, and every concrete event is final except three: JobProgressEvent (so applications can add parametric progress payloads), ContentStreamEvent, and MessageCompleteEvent. EventCategoryContractTest pins the permits of every category, the membership of every event, the finality rule, the readable set and the seven MsgType values.

Two sibling packages carry their own tables: Inside heartbeat events and Inside retry events.

Orthogonal markers

Event structure

MsgType is the seven-valued type a person-facing event answers from msgType(): the four lifecycle phases STARTING, PROGRESSING, COMPLETING, FAILING, and the three general-purpose values STATUS_UPDATE, MESSAGE, ERROR. The table below says which event answers which.

All events expose:

All events are immutable: no event exposes a public mutator, and what varies is given at construction. Can be published from any thread, delivered asynchronously to observers on dedicated virtual threads.

What each event carries and says

EventCarriesmessage()msgType() / title()
JobScheduledstate (QUEUED from the dispatcher, with the requirements and priority; the one-argument form is ACCEPTED with no requirements and priority 0)none-
JobStartedEventthe attemptJob started (attempt <n>)-
JobCompletedEventthe result, as getResult() (empty for a null result) and result()Job completed successfully (attempt <n>)-
JobFailedEventthe error (getError, also the terminal getCause)Job failed: <error message>, or unknown error without one-
JobTimedOutthe budget; getError() is null, a timeout has no exceptionJob timed out after <s> seconds-
JobCancelledwhether it was forced; a second constructor takes the message, for a publisher that states it at constructionJob cancelled or Job forcefully cancelled, or the publisher's message: the dispatcher builds a queued job's cancellation with the remover's, Cancelled while queued: <reason> from a cancel by id, Workflow cancelled: <reason> from a workflow cancel, Cancelled by shutdown at shutdown-
DependencyFailureEventthe error; a failure outside AbstractTerminalEvent, so the interface defaults: no responses, no metadata, timing from the snapshotJob failed due to dependency failure: <class>: <message>, or Unknown-
JobProgressEvent

the payload and an optional percent; hasProgress() is true when a percent of zero or more was giventhe payload's text form, null for a null payloadno percent STATUS_UPDATE, 0 STARTING, 1-99 PROGRESSING, 100 COMPLETING; the title is the job's display name
ContentStreamEventa StreamChunk (as chunk()) and the artifacts given with it, on any chunk, copied at construction and null when noneStreaming chunk: <first fifty characters>, Streaming complete on the lastPROGRESSING / Streaming Content, then COMPLETING / Streaming Complete at 100% on the last chunk; the human message is the chunk's content, else Streaming... or Stream completed
UserNotificationEventtitle, notification, severity, job state, optional details; the four factories read the state off the snapshot and carry no detailsthe notificationERROR for an error, COMPLETING for a success, MESSAGE for a warning or an info; the title as given
OrchestratorIdleEventthe exchange's LLM responses and metadata, as givenReady-
OrchestratorResumedEventnothing beyond the snapshotProcessing-
WorkflowCompleteEventsuccess and reason; the one-argument form is a success reading Workflow completed successfullythe reasonCOMPLETING / Workflow Complete or FAILING / Workflow Failed
MessageCompleteEventsuccess and an error description on failureMessage processed or Message processing failedCOMPLETING / Message Complete or FAILING / Message Failed
SchedulerEventa type (STARTED, STOPPING, STOPPED, ERROR) and, for an error, its causethe type's default message unless a caller gave one; Scheduler error: <message> (or Unknown) for an error-
LimiterEventone admission transition: account name and category, capacity, in-use, waiters, wait, reject reason, status indicator, amount<name> HELD (<inUse>/<capacity>, waiters=<n>), <name> GRANTED (<inUse>/<capacity>), <name> GRANTED after <ms>ms, <name> REJECTED (<reason>), <name> RELEASED (<inUse>/<capacity>)-

EventMessageContractTest holds every row of this table; the queued job's cancellation message is held by KernelExecutionContractTest.aJobTakenOutOfAQueueIsSettledAsCancelled in harness, and the immutability by EventCategoryContractTest.noEventExposesAPublicMutator. The retry and heartbeat tables are in their own packages.

The terminal family

AbstractTerminalEvent is the base of the four events the dispatcher fires for a job it ran: completion, failure, timeout and cancellation. Each carries the attempt count, the LLM responses and the metadata collected during the run (an empty list or map when none were given, never null), and the instant the terminal state was reached, which is getCompletionTime(); getDuration() runs from the snapshot's start to that instant and is zero for a job that never started. getResult() is present on a completion only; the error is read through getError() and getCause(). getCause() is the failure's error on a FailureEvent and empty otherwise, so a cancellation has none. DependencyFailureEvent is the one terminal event outside the base class: the job never ran.

Delivery scope

A subscription to a sealed category receives every member of it and nothing outside it: a LifecycleEvent subscriber sees a start and a completion, never a progress event and never a WorkflowCompleteEvent, which is parallel to lifecycle. An event with no snapshot reaches a global subscription (Job.class, JobEvent.class, no workflow) and a by-type subscription for all jobs (Job.class and the event's type or category, which is how a Stethoscope hears every HeartbeatEvent), never a subscription scoped to a job type or to a workflow: those never see a SchedulerEvent, a heartbeat event or a job-less LimiterEvent. EventDeliveryScopeTest holds both rules on the real bus.

Built-in observers

ObserverScopeSubscribes toPurpose
EventLoggerGlobalJobEvent (excludes OperationalEvent in its predicate)Color-coded console log of all job events
SystemHealthReporterGlobalJobEvent (filter: LimiterEvent + LifecycleEvent)Sampled system snapshot: limiters sorted by utilisation with gradient bar and status column, running jobs by type, active workflows, queue depths plus the count of jobs parked in admission. Red/yellow row tinting for blocked or throttled limiters. For token_bucket limiters the used: column is the rolling-minute sum of LimiterEvent.amount over GRANTED_* events (true throughput against the per-minute capacity), since inUse for token buckets is bucket depletion and would read near-empty under sustained pressure. Other categories display inUse directly.
StreamingLogObserverPer-workflowJobEventStreaming console output with reasoning/tool boxes
OpenTelemetryObserverGlobalJobEventTranslates events into OTel spans (optional dep)
MicrometerObserverGlobalJobEventForwards events to a Micrometer registry (optional dep)

A deployment that persists job runs adds its own LifecycleEvent subscriber; none ships with the runtime.

^