Skip to content

Events Reference

Agentrail hosts emit a unified stream of typed events for UI integration, observability, and trace persistence.

When To Read This Page

Read this page when:

  • you are building a streaming UI or trace panel
  • you need to handle specific event types on the client
  • you want to understand which events are persisted to the trace log
  • you need to integrate orchestration visibility into a dashboard

Type Hierarchy

The central event union is:

ts
import type { AgentrailEvent, AgentrailHostEvent } from "@agentrail/app";
import type { RuntimeEvent } from "@agentrail/core";

// AgentrailEvent is the top-level union for all SSE payloads
type AgentrailEvent = RuntimeEvent | ExtendedSseEvent | AgentrailHostEvent;

// AgentrailHostEvent covers everything above the runtime layer
type AgentrailHostEvent =
  | AgentrailContextCompactionStartEvent
  | AgentrailContextCompactionEndEvent
  | AgentrailContextUsageEvent
  | AgentrailErrorEvent
  | AgentrailOrchestrationRunStartEvent
  | AgentrailOrchestrationRunCompleteEvent
  | AgentrailSubagentSpawnedEvent
  | AgentrailSubagentStatusEvent
  | AgentrailSubagentJobStartedEvent
  | AgentrailSubagentJobCompletedEvent
  | AgentrailSubagentJobFailedEvent
  | AgentrailSubagentMessageEvent
  | AgentrailWaitRegisteredEvent
  | AgentrailWaitResolvedEvent
  | AgentrailSubagentClosedEvent;

For most UI consumers, AgentrailEvent is the type you want to annotate a parsed SSE payload with.


Host Events

These host events are generated by the host layer, not the runtime:

context_compaction_start

ts
interface AgentrailContextCompactionStartEvent {
  type: "context_compaction_start";
}

Emitted when the host begins compacting old session history via the summarize function.

context_compaction_end

ts
interface AgentrailContextCompactionEndEvent {
  type: "context_compaction_end";
}

Emitted when compaction is complete and the summarized history has been persisted.

context_usage

ts
interface AgentrailContextUsageEvent {
  type: "context_usage";
  inputTokens: number;
  outputTokens: number;
  budgetUsedPct?: number; // 0–100; requires contextWindow on the profile
}

Emitted after each turn with token counts. budgetUsedPct is set if the profile's contextWindow is configured.

error

ts
interface AgentrailErrorEvent {
  type: "error";
  error: { message: string };
}

Emitted when the host encounters an error it cannot recover from during a stream.


Orchestration-Mapped Events

These events are translated from internal orchestration state by mapOrchestrationEvent. They let a UI track sub-agent progress without coupling to the raw orchestration schema.

Event typeWhen emitted
orchestration_run_startA new orchestration run is initialized
orchestration_run_completeThe run has finished (success or error)
subagent_spawnedA new sub-agent has been created by the orchestrator
subagent_statusA sub-agent's status changes (e.g. idle → working → done)
subagent_job_startedA sub-agent picks up a job to process
subagent_job_completedA sub-agent successfully completes a job
subagent_job_failedA sub-agent job fails
subagent_messageA message is queued to a sub-agent's mailbox
wait_registeredA WaitCondition is registered by a sub-agent
wait_resolvedA WaitCondition is resolved
subagent_closedA sub-agent has been closed
ts
interface AgentrailOrchestrationRunStartEvent {
  type: "orchestration_run_start";
  runId: string;
  initialTask: Record<string, unknown>;
}

interface AgentrailOrchestrationRunCompleteEvent {
  type: "orchestration_run_complete";
  runId: string;
  status: string;
  error?: unknown;
}

interface AgentrailSubagentSpawnedEvent {
  type: "subagent_spawned";
  agent: Record<string, unknown>;
}

interface AgentrailSubagentStatusEvent {
  type: "subagent_status";
  agentId: string;
  status: string;
}

interface AgentrailSubagentJobStartedEvent {
  type: "subagent_job_started";
  agentId: string;
  jobId: string;
  inputIds: string[];
}

interface AgentrailSubagentJobCompletedEvent {
  type: "subagent_job_completed";
  agentId: string;
  job: Record<string, unknown>;
}

interface AgentrailSubagentJobFailedEvent {
  type: "subagent_job_failed";
  agentId: string;
  job: Record<string, unknown>;
}

interface AgentrailSubagentMessageEvent {
  type: "subagent_message";
  agentId: string;
  input: unknown;
}

interface AgentrailWaitRegisteredEvent {
  type: "wait_registered";
  wait: unknown;
}

interface AgentrailWaitResolvedEvent {
  type: "wait_resolved";
  waitId: string;
  resolution: unknown;
}

interface AgentrailSubagentClosedEvent {
  type: "subagent_closed";
  agentId: string;
  reason?: string;
  finalStatus?: string;
}

Runtime Events (Summary)

Runtime events come from @agentrail/core and are emitted during agent execution. Types use a dotted-namespace convention (session.*, turn.*, message.*, tool.*). The most important ones for UI consumers:

Event typeWhen emitted
session.startAgent begins processing a request
session.endAgent finishes (all turns complete)
turn.startA new LLM turn starts
turn.completeA turn finishes
compactionIn-loop reactive compaction rewrites history
message.updateStreaming text delta from the LLM
tool.beforeA tool call is about to be dispatched (after any onBeforeToolCall plugin hooks have run)
tool.afterA tool invocation completes
waiting_for_user_inputThe agent is paused waiting for user input
permission_requestA tool's checkPermissions returned "ask"; execution is blocked pending host approval
skill_start / skill_endA skill sub-agent is invoked

Tracing fields on every RuntimeEvent

Every RuntimeEvent carries three additional fields for distributed tracing and log correlation:

ts
interface RuntimeTracingFields {
  /** Stable correlation ID for the entire request chain.
   * Shared by the root agent and all spawned sub-agents.
   * In the app layer this equals the route-level traceId / requestTraceId. */
  readonly chainId: string;
  /** Nesting depth: 0 = root agent, 1 = direct sub-agent, etc. */
  readonly depth: number;
  /** Zero-based turn counter within this agent's loop.
   * Pre-loop events (session.start, initial turn.start, initial prompt messages)
   * carry turnIndex = 0. */
  readonly turnIndex: number;
}

These fields are automatically stamped by agentLoop and propagated to sub-agents via the orchestration subsystem. You can use chainId to join all events across a multi-agent invocation in your telemetry backend.

tool.before field reference

ts
{
  type: "tool.before";
  toolCallId: string;
  toolName: string;
  /**
   * Effective input passed to the tool.
   * When a `onBeforeToolCall` plugin hook modifies the arguments, this field
   * reflects the modified value rather than the raw model-generated arguments.
   */
  args: unknown;
  /**
   * Original model-generated arguments, always equal to the raw tool call
   * payload from the model.  Identical to `args` when no plugin modified the
   * input.  Use this field for audit logging and debugging.
   */
  rawArgs: unknown;
}

Note for consumers that used args for audit purposes: if you have a plugin or tracing integration that reads tool.before.args and relies on it being the unmodified model output, switch to rawArgs. args now reflects the effective (possibly plugin-modified) arguments that were actually executed.

permission_request field reference

Emitted when a tool's checkPermissions hook returns "ask". Execution is blocked until the host provides an interactive approval mechanism; in the current release, the tool call is always denied and the model receives an error result.

ts
{
  type: "permission_request";
  toolCallId: string;
  toolName: string;
  /** Optional human-readable reason why approval is being requested. */
  reason?: string;
}

This event is persisted to the trace log (TRACE_PERSISTED_EVENT_TYPES) so that audit tooling can record which tool calls required permission.


Trace Persistence

Not all events are persisted to the trace log. High-frequency streaming events (message.update, message.start, message.end) are intentionally excluded to avoid log bloat.

The TRACE_PERSISTED_EVENT_TYPES set defines what gets persisted:

ts
import { TRACE_PERSISTED_EVENT_TYPES } from "@agentrail/app";

// Runtime / skill events that are persisted:
// "session.start", "session.end", "turn.start", "turn.complete", "compaction",
// "tool.before", "tool.after",
// "skill_start", "skill_end", "waiting_for_user_input",
// "context_compaction_start", "context_compaction_end", "error"
//
// Orchestration-mapped events (all persisted):
// "orchestration_run_start", "orchestration_run_complete",
// "subagent_spawned", "subagent_status",
// "subagent_job_started", "subagent_job_completed", "subagent_job_failed",
// "subagent_message", "wait_registered", "wait_resolved", "subagent_closed"

if (TRACE_PERSISTED_EVENT_TYPES.has(event.type)) {
  await persistTraceEvent(event);
}

context_compaction_start and context_compaction_end represent request-boundary persisted compaction. compaction represents in-loop reactive compaction inside the runtime and does not rewrite messages.jsonl.


Consuming Events

Browser (fetch + ReadableStream)

ts
import type { AgentrailEvent } from "@agentrail/app";

async function streamChat(message: string, sessionId?: string) {
  const response = await fetch("/api/stream", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      message,
      tenantId: "default",
      userId: "user-1",
      sessionId,
    }),
  });

  // Capture the session ID returned by the host
  const newSessionId = response.headers.get("X-Session-Id");

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    for (const line of chunk.split("\n")) {
      if (!line.startsWith("data: ")) continue;
      const raw = line.slice(6).trim();
      if (!raw || raw === "[DONE]") continue;

      const event = JSON.parse(raw) as AgentrailEvent;
      handleEvent(event);
    }
  }

  return newSessionId;
}

function handleEvent(event: AgentrailEvent) {
  switch (event.type) {
    case "message.update":
      appendText(event.delta ?? "");
      break;
    case "tool.before":
      showToolSpinner(event.toolName ?? "tool");
      break;
    case "tool.after":
      hideToolSpinner();
      break;
    case "context_usage":
      updateTokenBudget(event.budgetUsedPct);
      break;
    case "context_compaction_start":
      showCompactionIndicator();
      break;
    case "context_compaction_end":
      hideCompactionIndicator();
      break;
    case "orchestration_run_start":
      initOrchestrationPanel(event.runId);
      break;
    case "subagent_spawned":
      addSubagentCard(event.agent);
      break;
    case "subagent_status":
      updateSubagentStatus(event.agentId, event.status);
      break;
    case "session.end":
      markComplete();
      break;
    case "error":
      showError(event.error.message);
      break;
  }
}

Node.js Server-Side

ts
import { mapOrchestrationEvent } from "@agentrail/app";

// Use the same line-splitting approach; node-fetch or undici support streaming
const res = await fetch("http://localhost:3000/api/stream", { ... });
const reader = res.body!.getReader();
// ... same loop as above

WorkflowTraceEventEnvelope

The stream route can optionally call onTraceEvent to persist a structured envelope around each trace-eligible event:

ts
interface WorkflowTraceEventEnvelope {
  id: string; // "{source}-{timestamp}-{seq}"
  timestamp: string; // ISO 8601
  sequence: number;
  source: "runtime" | "orchestration";
  event: Record<string, unknown>;
}

This is what the playground server uses to persist its trace log alongside session history.


Design Intent

The event layer is meant to be:

  • composable: you can consume only the event types you care about
  • stream-friendly: events are newline-delimited JSON, easy to parse in any language
  • UI-friendly: orchestration events are pre-mapped so UIs don't need to understand internal orchestration schema
  • append-only: events represent observations, not mutable state

Events are not meant to replace:

  • persisted session history (use appendMessages for that)
  • workflow state stores
  • domain-specific application state

Released under the Apache 2.0 License.