Aviary
Reference

Diagnostics events

The aviary:agent:* diagnostics_channel events published by @dudousxd/nestjs-agent-core, with exact payload shapes.

@dudousxd/nestjs-agent-core publishes lifecycle events on the aviary:agent:* node:diagnostics_channel namespace via @dudousxd/nestjs-diagnostics' emit('agent', <event>, payload). Both the dashboard's live feed and the -telescope "Agent" tab subscribe to the same channel — see Governance and packages/telescope.

Events

EventPayload fieldsPublisher
run.startedrunId: string; threadId: string; actorId: string; agentName?: string; persona?: stringpublishAgentRunStarted — published outside any checkpoint, so a durable run publishes it again each time it replays (a resume after an approval, a dispatch hop); count runs from run.finished / run.failed / run.cancelled instead.
messagerunId: string; threadId: string; role: 'user' | 'assistant'; textLength: numberpublishAgentMessage
tool-callrunId: string; toolCallId: string; toolName: string; toolType: 'read' | 'action'; status: string; durationMs?: number; replayed?: truepublishAgentToolCall — durationMs is the tool's own time, measured inside its checkpoint (tool:<callId>, or on the worker running a dispatched call); replayed: true when a durable replay re-publishes an outcome an earlier execution already settled — skip those when counting.
quota.exceededactorId: string; period?: 'day' | 'month'; reason?: string; usedTokens?: number; limitTokens?: number; usedUsd?: number; limitUsd?: numberpublishAgentQuotaExceeded — when the send gate refuses a turn with 429, and when the message queue pauses on quota. Token figures are absent on a USD-only cap.
run.failedrunId: string; threadId?: string; code: string (the stream error code); message: string; durationMs?: number; context?: AgentRunContextpublishAgentRunFailed — by the runner that settles the failure. A durable suspend publishes nothing.
run.cancelledrunId: string; threadId?: string; durationMs?: number; context?: AgentRunContextpublishAgentRunCancelled — instead of run.failed, by the runner that settles the Stop.
run.finishedrunId: string; threadId: string; steps: number; inputTokens: number; outputTokens: number; durationMs?: number; ttftMs?: number; costUsd?: number; modelId?: string; plus the AgentRunContext fieldspublishAgentRunFinished — ttftMs is turn start to the first non-empty text frame; costUsd sums the model calls with a known cost (absent when none had one); modelId is the last call's model.
delegatedrunId: string; fromAgent?: string; toAgent: string; detached?: booleanpublishAgentDelegated — detached: true when the delegate was started, not awaited
retrievedrunId: string; query: string; count: numberpublishAgentRetrieved — inject mode only (retrieval: { mode: 'inject' }). Agentic retrieval through a tool is not reported here; wrap the retriever with instrumentRetriever to see it on aviary:rag:retrieval.
skills.resolvedrunId: string; scopes: number; offered: number; omitted: number; promptChars: numberonce per run, at the skills:catalog checkpoint (counts only, never skill names)
memory.resolvedrunId: string; scopes: number; offered: number; omitted: number; pinnedOmitted: number; recalled: boolean; promptChars: numberonce per run, at the memory:digest checkpoint (counts only, never keys or text)
memory.writtenrunId: string; scope: string; chars: numbereach remember write (the length, never the fact)
tool.retrytoolName: string; toolCallId: string; attempt: number; message: stringeach in-place transient retry (toolTransientRetry), not the final outcome

These 12 are the point events, exported as a stable, compile-time-checked AGENT_DIAGNOSTIC_EVENTS array with a matching agentDiagnosticKey(event) helper that composes the telescope bridge's agent:<event> key — pass those keys to nestjsDiagnosticsTelescope({ exclude: [...] }) to mute a channel already recorded elsewhere.

RAG events (aviary:rag:*)

The RAG packages publish on their own namespace:

EventPayloadPublisher
retrievalRagRetrievalEvent — { retriever, store?, collection?, topK, chunks, zeroHit, topScore?, meanScore?, failed, error? }, with durationMs on the envelopeinstrumentRetriever(retriever, options?) from @dudousxd/nestjs-agent-rag (channel RAG_RETRIEVAL_CHANNEL). Only an instrumented retriever publishes it.
media.ingested{ mediaId, ownerType, ownerId, collection, source, size, mimeType, chunks, … }@dudousxd/nestjs-agent-rag-media
media.removed{ mediaId, ownerType, ownerId, … }@dudousxd/nestjs-agent-rag-media
media.skipped{ mediaId, mimeType, reason: 'unsupported-type' | 'too-large' | 'empty-text', ownerType?, ownerId?, collection?, … }@dudousxd/nestjs-agent-rag-media
media.failed{ mediaId, error, mimeType?, kind?, ownerType?, ownerId?, collection?, … }@dudousxd/nestjs-agent-rag-media

tool-call's toolType is narrower than ToolKind

AgentToolCallEvent.toolType is 'read' | 'action' — it does not include the 'agent' kind that ToolSpec.kind supports for delegation. Delegation gets its own delegated event instead.

AgentRunContext is { actorId: string; agentName?: string; tenantRef?: string; parentRunId?: string }. Durations are measured from the run's journaled run:started-at, so a run that suspended and resumed reports its whole wall time; the time to first token is read where the text is written and journaled with the step that wrote it.

Trace spans (traceId = runId)

Every run also emits five span-only events — never as point events on the base channel, only via trace() on the :start/:end/:asyncStart/:asyncEnd/:error sub-channels, correlated by traceId = runId. This is what lets the Telescope TRACES tab render a turn as a nested waterfall (llm calls, tool executions, retrieval, follow-ups, structured-output) with durations and error phases. They're emitted from inside the checkpointed step bodies, so a replayed (cached) step never re-emits — and, for dispatched turns (see Inline → durable), from whichever worker actually executes the step.

Span eventSTART payload fields
llm.turnrunId: string; step: number (zero-based model-call index)
tool.executionrunId: string; toolCallId: string; toolName: string; toolType: 'read' | 'action'
retrievalrunId: string; queryLength: number; topK: number
follow-upsrunId: string; step: number; count: number
structured-outputrunId: string; step: number; attempt: number

Payloads are metadata-only — model id, token counts, tool name/type, step index, query length — never prompt or output text. traceLlmTurn/traceToolExecution are exported for remote execution sites that need to emit the same span shape.

Spans need the span-aware bridge

Rendering these as a waterfall requires @dudousxd/nestjs-diagnostics-telescope 0.7+ and @dudousxd/nestjs-telescope 1.17+ (explicit RecordInput.traceId). Without them the spans are still emitted but unobserved — zero cost, since the phase envelopes are gated on subscriber presence. Span events are deliberately excluded from AGENT_DIAGNOSTIC_EVENTS: the point-event watcher has nothing to subscribe to on their base channels. They are listed in AGENT_SPAN_EVENTS (llm.turn, tool.execution, retrieval, follow-ups, structured-output).

How to subscribe

Directly, via node:diagnostics_channel:

import { subscribe } from 'node:diagnostics_channel';
import { channelName } from '@dudousxd/nestjs-diagnostics';

subscribe(channelName('agent', 'tool-call'), (message) => {
  // message: DiagnosticEvent<AgentToolCallEvent>
  //   { v?, ts: number, lib: 'agent', event: 'tool-call', traceId?, payload: AgentToolCallEvent }
});

Or via the telescope extension, which wires the same channel into a navigable entry type and dashboard page without any manual subscribe calls — see packages/telescope.

The dashboard's GET /ai-gateway/api/stream SSE endpoint forwards these same events flattened to LiveAgentEvent ({ event, ts, payload }) for browser clients — see Endpoints.

On this page