Telemetry kinds — full reference
The 22 kinds emitted on runtime.onTelemetry. Listed in the order they typically appear in a causal chain.
Domain (13)
action.dispatched
Emitted when runtime.dispatch(action, input, envelope?) enters the pipeline. Fires before the handler runs.
{
kind: "action.dispatched"
action: string // ActionDefinition.name
input: unknown // validated input
envelope: MessageEnvelope
appName: string
ts: string // ISO 8601
}action.completed
Handler returned without throwing. events published, retry loop exited successfully.
{
kind: "action.completed"
action: string
durationMs: number // since action.dispatched
emittedEvents: readonly string[] // event names returned
envelope: MessageEnvelope
appName: string
ts: string
}action.failed
Handler threw. Emitted per retry attempt. willRetry: false on the final attempt before dlq.recorded fires.
{
kind: "action.failed"
action: string
attempt: number // 1-indexed
maxAttempts: number
willRetry: boolean
error: SerializedError // { name, message, stack?, ...custom }
envelope: MessageEnvelope
appName: string
ts: string
}event.published
An event was applied to actors + projections + reactions and committed.
{
kind: "event.published"
event: { eventName: string; payload: unknown }
envelope: MessageEnvelope // derived from the publishing action's envelope
source: "in-process" | "external"
appName: string
ts: string
}source: "external" = arrived via runtime.applyExternalEvent(...) from the cross-service bus.
actor.transitioned
An actor moved between states. Fires AFTER the save commits. Pure data updates (no state change) don't fire this — only when target differs from the current state.
{
kind: "actor.transitioned"
actor: string // actor.name
key: string // instance key
tenant: string
from: string // previous state
to: string // new state
triggeringEvent: string // event.eventName
envelope: MessageEnvelope
appName: string
ts: string
}projection.folded
A projection consumed an event and saved new state.
{
kind: "projection.folded"
projection: string // projection.name
event: string // event.eventName
tenant: string
durationMs: number // load + reduce + save
envelope: MessageEnvelope
appName: string
ts: string
}reaction.fired
A when() reaction's body completed successfully.
{
kind: "reaction.fired"
sourceEvent: string // the event that triggered it
durationMs: number // reaction body wall time
envelope: MessageEnvelope
appName: string
ts: string
}reaction.failed
A when() reaction threw. The publish loop exits — subsequent reactions on the same event don't run.
{
kind: "reaction.failed"
sourceEvent: string
error: SerializedError
envelope: MessageEnvelope
appName: string
ts: string
}query.executed
runtime.query(name, input, tenant?) ran.
{
kind: "query.executed"
query: string // query.name
input: unknown // validated input
durationMs: number
tenant: string
appName: string
ts: string
}timer.scheduled
An actor entered a state with after: { timer: { delay, action } } and a timer was scheduled. Fires during applyEventToActor, AFTER the actor save commits.
{
kind: "timer.scheduled"
actor: string
key: string // actor instance key
timer: string // timer name
action: string // action to dispatch when due
fireAt: number // epoch ms
tenant: string
appName: string
ts: string
}timer.fired
runtime.fireDueTimers() triggered a scheduled timer.
{
kind: "timer.fired"
actor: string
key: string
timer: string
action: string
lateByMs: number // 0 if fired on time
tenant: string
appName: string
ts: string
}dlq.recorded
Retries exhausted, entry recorded to the DLQ. Fires after the last action.failed.
{
kind: "dlq.recorded"
action: string
attempts: number
error: SerializedError // last error
envelope: MessageEnvelope
appName: string
ts: string
}Orchestrator (9)
external.call.started
ctx.externalCall(def, request) invoked. Per attempt.
{
kind: "external.call.started"
call: string // ExternalCallDefinition.name
target: string // "provider/endpoint"
idempotencyKey?: string
envelope?: MessageEnvelope // present if called from inside a handler
appName: string
ts: string
}external.call.completed
External transport returned (success).
{
kind: "external.call.completed"
call: string
target: string
durationMs: number
status?: number // HTTP status if applicable
idempotencyKey?: string
envelope?: MessageEnvelope
appName: string
ts: string
}external.call.failed
External transport threw. Per attempt.
{
kind: "external.call.failed"
call: string
target: string
attempt: number
willRetry: boolean
error: SerializedError
envelope?: MessageEnvelope
appName: string
ts: string
}inbound.webhook.received
HTTP wire handled a defineInboundWebhook request — fires per request, before action dispatch (if any).
{
kind: "inbound.webhook.received"
webhook: string
source: string // "stripe", "twilio", …
signatureValid: boolean
dedupHit: boolean
routedTo?: string // action name (if discriminator matched)
appName: string
ts: string
}outbox.flushed
A batch of outbox events was flushed to the bus.
{
kind: "outbox.flushed"
outbox: string
events: number // count in batch
durationMs: number
failed: number // count that retry
appName: string
ts: string
}inbox.dedup.hit
Inbound message id matched a key in the inbox window — skipped processing.
{
kind: "inbox.dedup.hit"
inbox: string
messageId: string
firstSeenAt: string // when we first processed it
appName: string
ts: string
}queue.job.enqueued / .started / .completed
Queue worker lifecycle (@nwire/queue + adapter).
// enqueued
{ kind: "queue.job.enqueued"; queue, jobId, delay?, appName, ts }
// started
{ kind: "queue.job.started"; queue, jobId, waitedMs, appName, ts }
// completed
{ kind: "queue.job.completed"; queue, jobId, durationMs, ok, appName, ts }cron.fired
defineCron scheduler dispatched the bound action at its fire time.
{
kind: "cron.fired"
schedule: string // cron expression
cronName: string // CronDefinition.name
expected: string // ISO of expected fire time
actual: string // ISO of actual dispatch
lateByMs: number // actual - expected
appName: string
ts: string
}SerializedError
Errors are flattened before they hit the stream so the record is JSON-safe:
type SerializedError = {
name: string // err.name
message: string // err.message
stack?: string // err.stack
[k: string]: unknown // any enumerable own props on the error
}Error instances → all three plus enumerable custom fields. Non-Error throws → { name: "NonError", message: String(thrown) }.
Envelope
Every domain telemetry record carries:
interface MessageEnvelope {
messageId: string // this message's id
correlationId: string // chain root — same across one user request
causationId: string // parent message's id
tenant?: string
userId?: string
timestamp: string // ISO when envelope was minted
version: number // envelope schema version (1 today)
}Use correlationId to group records by user request (Studio's Live page does this). Use causationId to walk a chain backward.
Subscription
const detach = runtime.onTelemetry((rec) => {
// narrow on rec.kind
if (rec.kind === "action.dispatched") {
metrics.inc("nwire.actions", { name: rec.action })
}
})
// later
detach()Throwing in a listener is caught + logged; it never breaks domain dispatch. But listeners run synchronously on every emit — keep them cheap.
See also
- Concepts → Telemetry — the why
- @nwire/telemetry-otel — OTLP bridge
- Testing guide → TelemetryProbe