> ## Documentation Index
> Fetch the complete documentation index at: https://comis-fix-skill-import-vetting-gate.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Bus

> Typed events for cross-module communication

Comis uses a `TypedEventBus` for cross-module communication. Instead of packages importing each other directly, they emit and subscribe to typed events. The bus provides compile-time safety -- TypeScript enforces that you emit events with the correct payload shape and subscribe to events that actually exist.

## How It Works

The `TypedEventBus` wraps Node.js `EventEmitter` with a type-safe generic interface. All typed events are defined in the `EventMap` interface, which is composed from 5 domain-specific sub-interfaces (`MessagingEvents`, `AgentEvents`, `ChannelEvents`, `InfraEvents`, `TerminalEvents`).

API methods include `emit()`, `emitSafely()`, `on()`, `off()`, `once()`, `removeAllListeners()`, `listenerCount()`, and `setMaxListeners()`. All methods are constrained by `EventMap` -- you cannot emit an event name that does not exist in the map, and every payload is type-checked at compile time.

Use `emit()` when subscriber failure is part of the publisher's control flow and must surface. Use `emitSafely()` for observational notifications after an authoritative state change or external side effect. It invokes every subscriber in registration order, contains synchronous throws, and contains promise rejections without delaying the publisher. Publishers must log both the immediate `failures` and the asynchronously resolved `pendingFailures` with bounded, sanitized error metadata; neither failure path may alter the authoritative result.

```typescript theme={}
import type { TypedEventBus, EventMap } from "@comis/core";

// Subscribe to an event
eventBus.on("message:received", (payload) => {
  // TypeScript knows payload has: message, sessionKey
  console.log(`Message received in session ${payload.sessionKey}`);
});

// Emit an event (payload type-checked at compile time)
eventBus.emit("tool:executed", {
  toolName: "web_search",
  durationMs: 1500,
  success: true,
  timestamp: Date.now(),
});
```

## Event Naming Convention

Events follow a `subsystem:action` naming pattern (for example, `message:received`, `tool:executed`, `graph:started`). Events are organized into 5 subsystems:

| Subsystem         | Event Count | Scope                                                                                                                |
| ----------------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `MessagingEvents` | 38          | Message lifecycle, sessions, subagent lifecycle, compaction, context, execution control, announcements               |
| `AgentEvents`     | 43          | Skills, tools, audit, observability, model selection, security, graphs, cache, SEP                                   |
| `ChannelEvents`   | 44          | Channel lifecycle, queuing, streaming, typing, auto-reply, policies, delivery, steer, inbound reactions              |
| `InfraEvents`     | 55          | Approvals, config, plugins, hooks, auth, diagnostics, media, scheduler, system, MCP, notifications, background tasks |
| `TerminalEvents`  | 4           | Terminal session lifecycle, keystrokes, session eviction                                                             |

## Most Commonly Used Events

A quick reference for the events developers are most likely to need when building plugins or adapters:

| Event                          | Subsystem | When It Fires                     | Typical Use               |
| ------------------------------ | --------- | --------------------------------- | ------------------------- |
| `message:received`             | Messaging | Incoming message from any channel | Logging, analytics        |
| `message:sent`                 | Messaging | Agent sends a response            | Delivery tracking         |
| `tool:executed`                | Agent     | After a tool completes            | Audit logging, metrics    |
| `session:created`              | Messaging | New conversation session begins   | Session setup hooks       |
| `session:expired`              | Messaging | Conversation session ends         | Cleanup, analytics        |
| `channel:registered`           | Channel   | Channel adapter registered        | Health monitoring         |
| `queue:enqueued`               | Channel   | Message enters command queue      | Queue depth monitoring    |
| `graph:started`                | Agent     | Execution graph begins            | Pipeline monitoring       |
| `graph:completed`              | Agent     | Execution graph finishes          | Pipeline results          |
| `config:patched`               | Infra     | Runtime config modified           | Config-reactive behavior  |
| `plugin:registered`            | Infra     | Plugin loaded                     | Plugin lifecycle tracking |
| `diagnostic:message_processed` | Infra     | Full message lifecycle complete   | End-to-end metrics        |

## Complete Event Reference

<AccordionGroup>
  <Accordion title="MessagingEvents (38 events)">
    Message lifecycle, session management, subagent lifecycle, compaction, context engine lifecycle, context engine metrics, response filtering, execution control, and dead-letter queue events.

    | Event Name                           | Key Payload Fields                                                                                                                                                                                                                                                                                                       |
    | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `announcement:dead_lettered`         | runId, channelType, reason, timestamp                                                                                                                                                                                                                                                                                    |
    | `announcement:dead_letter_delivered` | runId, channelType, attemptCount, timestamp                                                                                                                                                                                                                                                                              |
    | `compaction:started`                 | agentId, sessionKey, timestamp                                                                                                                                                                                                                                                                                           |
    | `compaction:recommended`             | agentId, sessionKey, contextPercent, contextTokens, contextWindow, timestamp                                                                                                                                                                                                                                             |
    | `compaction:flush`                   | sessionKey, memoriesWritten, trigger, success, timestamp                                                                                                                                                                                                                                                                 |
    | `context:masked`                     | agentId, sessionKey, maskedCount, totalChars, persistedToDisk, timestamp                                                                                                                                                                                                                                                 |
    | `context:compacted`                  | agentId, sessionKey, fallbackLevel, attempts, originalMessages, keptMessages, timestamp                                                                                                                                                                                                                                  |
    | `context:rehydrated`                 | agentId, sessionKey, sectionsInjected, filesInjected, skillsInjected, overflowStripped, timestamp                                                                                                                                                                                                                        |
    | `context:overflow`                   | agentId, sessionKey, contextTokens, budgetTokens, recoveryAction, timestamp                                                                                                                                                                                                                                              |
    | `context:evicted`                    | agentId, sessionKey, evictedCount, evictedChars, categories, timestamp                                                                                                                                                                                                                                                   |
    | `context:reread`                     | agentId, sessionKey, rereadCount, rereadTools, timestamp                                                                                                                                                                                                                                                                 |
    | `context:pipeline`                   | agentId, sessionKey, tokensLoaded, tokensEvicted, tokensMasked, tokensCompacted, thinkingBlocksRemoved, budgetUtilization, evictionCategories, rereadCount, rereadTools, sessionDepth, sessionToolResults, cacheHitTokens, cacheWriteTokens, cacheMissTokens, cacheFenceIndex, durationMs, layerCount, layers, timestamp |
    | `context:pipeline:cache`             | agentId, sessionKey, cacheHitTokens, cacheWriteTokens, cacheMissTokens, timestamp                                                                                                                                                                                                                                        |
    | `context:dag_compacted`              | conversationId, agentId, sessionKey, leafSummariesCreated, condensedSummariesCreated, maxDepthReached, totalSummariesCreated, durationMs, timestamp                                                                                                                                                                      |
    | `context:integrity`                  | conversationId, agentId, sessionKey, issueCount, repairsApplied, errorsLogged, issueTypes, durationMs, timestamp                                                                                                                                                                                                         |
    | `execution:aborted`                  | sessionKey, reason, agentId, timestamp                                                                                                                                                                                                                                                                                   |
    | `execution:budget_warning`           | agentId, sessionKey, totalTokens, llmCallCount, projectedCallsLeft, timestamp                                                                                                                                                                                                                                            |
    | `execution:output_escalated`         | agentId, sessionKey, originalMaxTokens, escalatedMaxTokens, timestamp                                                                                                                                                                                                                                                    |
    | `execution:prompt_timeout`           | agentId, sessionKey, timeoutMs, timestamp                                                                                                                                                                                                                                                                                |
    | `execution:signed_replay_recovered`  | agentId, sessionKey, blocksRemoved, thoughtSignaturesStripped, succeeded, timestamp                                                                                                                                                                                                                                      |
    | `message:received`                   | message, sessionKey                                                                                                                                                                                                                                                                                                      |
    | `message:terminal`                   | channelType, channelId, sourceMessageId, outcome, reason, timestamp                                                                                                                                                                                                                                                      |
    | `message:sent`                       | channelType, channelId, messageId, content, sourceChannelType, sourceChannelId, sourceMessageId                                                                                                                                                                                                                          |
    | `message:streaming`                  | channelId, messageId, delta, accumulated                                                                                                                                                                                                                                                                                 |
    | `response:filtered`                  | channelType, channelId, sourceMessageId, suppressedBy, timestamp                                                                                                                                                                                                                                                         |
    | `session:created`                    | sessionKey, timestamp                                                                                                                                                                                                                                                                                                    |
    | `session:expired`                    | sessionKey, reason                                                                                                                                                                                                                                                                                                       |
    | `session:cross_send`                 | fromSessionKey, toSessionKey, mode, timestamp                                                                                                                                                                                                                                                                            |
    | `session:ping_pong_turn`             | fromSessionKey, toSessionKey, turnNumber, totalTurns, tokensUsed, timestamp                                                                                                                                                                                                                                              |
    | `session:sub_agent_spawned`          | runId, parentSessionKey, agentId, task, timestamp                                                                                                                                                                                                                                                                        |
    | `session:sub_agent_completed`        | runId, agentId, success, runtimeMs, tokensUsed, cost, timestamp, cacheReadTokens, cacheWriteTokens                                                                                                                                                                                                                       |
    | `session:sub_agent_archived`         | runId, sessionKey, ageMs, timestamp                                                                                                                                                                                                                                                                                      |
    | `session:sub_agent_spawn_prepared`   | runId, parentSessionKey, agentId, task, depth, maxDepth, artifactCount, timestamp                                                                                                                                                                                                                                        |
    | `session:sub_agent_spawn_queued`     | runId, parentSessionKey, agentId, task, queuePosition, activeChildren, maxChildren, timestamp                                                                                                                                                                                                                            |
    | `session:sub_agent_spawn_rejected`   | parentSessionKey, agentId, task, reason, currentDepth, maxDepth, currentChildren, maxChildren, timestamp                                                                                                                                                                                                                 |
    | `session:sub_agent_spawn_started`    | runId, parentSessionKey, agentId, task, depth, timestamp                                                                                                                                                                                                                                                                 |
    | `session:sub_agent_result_condensed` | runId, agentId, level, originalTokens, condensedTokens, compressionRatio, taskComplete, diskPath, timestamp                                                                                                                                                                                                              |
    | `session:sub_agent_lifecycle_ended`  | runId, agentId, parentSessionKey, endReason, durationMs, tokensUsed, cost, condensationLevel, timestamp                                                                                                                                                                                                                  |

    The `context:*` events provide per-turn observability into context engine decisions:

    * **`context:evicted`** fires when context history is dropped to fit the token budget — in pipeline mode when the dead content evictor removes superseded tool results, and in DAG mode when the LCD assembler evicts older history under budget (`categories.lcd_history` carries the dropped count). Counts and ids only; never message content.
    * **`context:reread`** fires when the re-read detector identifies duplicate tool calls in the session (pipeline mode).
    * **`context:pipeline`** fires after every pipeline run with a full metrics snapshot (pipeline mode). This is always emitted, even when no optimization was needed.
    * **`context:pipeline:cache`** fires post-LLM to patch cache-specific metrics (cache hit/write/miss tokens) once the API response is available.
    * **`context:dag_compacted`** fires after a DAG compaction pass creates new summaries (DAG mode).
    * **`context:integrity`** fires when the DAG integrity checker detects and repairs structural issues (DAG mode).

    See [Compaction](/agents/compaction) for details on what triggers each event.
  </Accordion>

  <Accordion title="AgentEvents (47 events)">
    Skill lifecycle, tool execution, audit logging, observability (tokens and latency), cache break detection, model failover, security, execution graph, per-node sub-agent budget breaches, pipeline authoring telemetry, small-model graph authoring (repair + intent synthesis), provider health, SEP (step-execution planning), and exec command blocking events.

    | Event Name                           | Key Payload Fields                                                                                                                                                                                                                                    |
    | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `audit:event`                        | timestamp, agentId, tenantId, actionType, classification, outcome, metadata                                                                                                                                                                           |
    | `cache:graph_prefix_written`         | graphId, nodeId, cacheWriteTokens, timestamp                                                                                                                                                                                                          |
    | `command:blocked`                    | agentId, commandPrefix, reason, blocker, timestamp                                                                                                                                                                                                    |
    | `graph:started`                      | graphId, label, nodeCount, timestamp                                                                                                                                                                                                                  |
    | `graph:node_updated`                 | graphId, nodeId, status, previousStatus, durationMs, error, tokensUsed, cost, timestamp                                                                                                                                                               |
    | `graph:completed`                    | graphId, status, durationMs, nodeCount, nodesCompleted, nodesFailed, nodesSkipped, cancelReason, timestamp, graphCacheReadTokens, graphCacheWriteTokens, graphCacheEffectiveness, nodeEffectiveness, nodeTokenSpend, nodeCost                         |
    | `graph:driver_lifecycle`             | graphId, nodeId, typeId, phase                                                                                                                                                                                                                        |
    | `pipeline:authored`                  | action, capabilityClass, schemaValid, repaired, agentId, sessionKey, timestamp                                                                                                                                                                        |
    | `graph:repaired`                     | pattern, nodeCount, capabilityClass, agentId, sessionKey, timestamp                                                                                                                                                                                   |
    | `graph:synthesized_from_intent`      | pattern, nodeCount, agentId, sessionKey, timestamp                                                                                                                                                                                                    |
    | `subagent:budget_exceeded`           | graphId, nodeId, agentId, tokenBudget, tokensUsed, timestamp                                                                                                                                                                                          |
    | `subagent:delivery_retried`          | runId, channelType, attempt, transient, timestamp                                                                                                                                                                                                     |
    | `subagent:delivery_deadlettered`     | runId, channelType, attempt, transient, timestamp                                                                                                                                                                                                     |
    | `subagent:steered`                   | runId, agentId, mode, timestamp                                                                                                                                                                                                                       |
    | `subagent:killed`                    | runId, agentId, sessionKey, killedBy, runtimeMs, idleMs, thresholdMs, timestamp                                                                                                                                                                       |
    | `memory:review_completed`            | agentId, sessionsReviewed, memoriesExtracted, duplicatesSkipped, durationMs, timestamp                                                                                                                                                                |
    | `model:auth_cooldown`                | keyName, provider, cooldownMs, failureCount, timestamp                                                                                                                                                                                                |
    | `model:catalog_loaded`               | providerCount, modelCount, timestamp                                                                                                                                                                                                                  |
    | `model:fallback_attempt`             | fromProvider, fromModel, toProvider, toModel, error, attemptNumber, timestamp                                                                                                                                                                         |
    | `model:fallback_exhausted`           | provider, model, totalAttempts, timestamp                                                                                                                                                                                                             |
    | `observability:cache_break`          | provider, reason, tokenDrop, tokenDropRelative, previousCacheRead, currentCacheRead, callCount, changes, toolsChanged, ttlCategory, agentId, sessionKey, timestamp, toolsAdded, toolsRemoved, toolsSchemaChanged, systemCharDelta, model, effortValue |
    | `observability:latency`              | operation, durationMs, timestamp, metadata                                                                                                                                                                                                            |
    | `observability:token_usage`          | timestamp, traceId, agentId, channelId, executionId, provider, model, tokens, cost, latencyMs, cacheReadTokens, cacheWriteTokens, sessionKey, savedVsUncached, cacheEligible, responseId, cacheCreation                                               |
    | `provider:degraded`                  | provider, failingAgents, timestamp                                                                                                                                                                                                                    |
    | `provider:recovered`                 | provider, timestamp                                                                                                                                                                                                                                   |
    | `security:injection_detected`        | timestamp, source, patterns, riskLevel, agentId, sessionKey, traceId                                                                                                                                                                                  |
    | `security:injection_rate_exceeded`   | timestamp, sessionKey, count, threshold, action                                                                                                                                                                                                       |
    | `security:memory_tainted`            | timestamp, agentId, originalTrustLevel, adjustedTrustLevel, patterns, blocked                                                                                                                                                                         |
    | `security:sandbox_downgrade_refused` | timestamp, parentAgentId, childAgentId, violatedDimensions, parentPosture, childPosture                                                                                                                                                               |
    | `sender:trust_resolved`              | agentId, senderId, trustLevel, displayMode, sessionKey, timestamp                                                                                                                                                                                     |
    | `sep:plan_extracted`                 | agentId, sessionKey, stepCount, timestamp                                                                                                                                                                                                             |
    | `skill:created`                      | skillName, scope, agentId, timestamp                                                                                                                                                                                                                  |
    | `skill:executed`                     | skillName, durationMs, success, timestamp                                                                                                                                                                                                             |
    | `skill:failed`                       | skillName, error, phase, agentId, timestamp                                                                                                                                                                                                           |
    | `skill:loaded`                       | skillName, source, timestamp                                                                                                                                                                                                                          |
    | `skill:prompt_invoked`               | skillName, invokedBy, args, timestamp                                                                                                                                                                                                                 |
    | `skill:prompt_loaded`                | skillName, source, bodyLength, timestamp                                                                                                                                                                                                              |
    | `skill:registry_reset`               | clearedMetadata, clearedPromptCache, timestamp                                                                                                                                                                                                        |
    | `skill:rejected`                     | skillName, reason, violations, timestamp                                                                                                                                                                                                              |
    | `skill:updated`                      | skillName, scope, agentId, timestamp                                                                                                                                                                                                                  |
    | `skills:reloaded`                    | agentId, skillCount, timestamp                                                                                                                                                                                                                        |
    | `tool:executed`                      | toolName, durationMs, success, timestamp, userId, traceId, agentId, sessionKey, params, errorMessage, errorKind, description, truncated, fullChars, returnedChars                                                                                     |
    | `tool:policy_filtered`               | profile, agentId, filtered, timestamp                                                                                                                                                                                                                 |
    | `tool:started`                       | toolName, toolCallId, timestamp, agentId, sessionKey, traceId, description                                                                                                                                                                            |

    The `graph:driver_lifecycle` event fires at each phase transition during typed node execution. The `phase` field indicates which stage the driver has reached:

    | Phase              | When It Fires                                                                                      | Description                                                                                                              |
    | ------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
    | `initialized`      | After the coordinator calls `initialize()` on the driver and receives the first action             | The driver has started execution and returned its initial action (typically `spawn` or `spawn_all`)                      |
    | `progress`         | When the driver returns a `progress` action                                                        | The driver is reporting intermediate status (e.g., "Round 2 of 3 complete"). Used for observability and UI updates       |
    | `completed`        | When the driver returns a `complete` action with final output                                      | The node transitions to the `completed` state. Output is stored and dependent nodes become eligible for scheduling       |
    | `partial_complete` | When the driver returns a `partial_complete` action                                                | Partial results available (e.g., some sub-tasks succeeded, others failed)                                                |
    | `failed`           | When the driver returns a `fail` action, or when an unhandled error occurs during driver execution | The node transitions to the `failed` state. Dependent nodes may be skipped depending on the graph's `onFailure` strategy |
    | `aborted`          | When the coordinator calls `onAbort()` due to timeout, cancellation, or graph shutdown             | The node transitions to the `failed` state with an abort reason. Resources are cleaned up                                |

    Subscribe to this event for driver-level monitoring, logging, or triggering downstream actions based on typed node progress. See the [Pipelines: Driver Lifecycle](/developer-guide/pipelines#driver-lifecycle) documentation for the full execution flow.

    **Per-node token budgets:** `subagent:budget_exceeded` fires when a graph node's sub-agent exceeds its per-node token budget. The payload is counts and ids only -- `tokenBudget` is the cap that was breached, `tokensUsed` is the node's actual spend, and `agentId` is the node's child agent (never task text or sub-agent output). The breaching node fails terminally (honoring the graph's `on_failure`); see [Execution Graphs: Token budgets](/agents/execution-graphs#token-budgets). For completed nodes, `graph:node_updated` carries the optional `tokensUsed` and `cost` fields so per-node spend is observable from the bus alone; both are absent when the underlying run did not report them.

    **Per-subagent cost rollup:** the `graph:completed` payload carries an optional `nodeTokenSpend` (nodeId → tokens) and `nodeCost` (nodeId → corrected dollars) ledger, present only when at least one node recorded the respective metric. `nodeCost` accumulates each node's `graph:node_updated` cost delta — the same corrected dollars that feed the graph-wide total — so a per-subagent corrected-\$ rollup (a node plus all its descendants) is computable from the bus alone. Both maps are counts/ids only (never task text or sub-agent output).

    **Pipeline authoring telemetry:** `pipeline:authored` fires once per `pipeline` authoring invocation -- `graph.define` and `graph.execute` -- carrying the `action` (`define` or `execute`), the calling model's server-resolved `capabilityClass` tier (`frontier` / `mid` / `small` / `nano`, or `unknown` when the tier cannot be resolved), whether the call parsed and validated against the graph schema (`schemaValid`, the real verdict -- `false` is still emitted, and the user-facing validation error still throws), and `repaired`. A present-but-malformed authoring call that fails schema **either** at the request-contract parse **or** at the graph parse/validate step counts as `schemaValid: false`; only the bespoke pre-checks (a `define` call with no `nodes`, or an `execute` call rejected by the agent-to-agent policy gate) emit nothing -- an empty/garbage call or a policy rejection is not an authoring attempt. The emit is **best-effort**: a telemetry failure (e.g. an observability-buffer write error) is logged and swallowed, never surfaced as the `graph.define`/`graph.execute` result. The payload is counts, ids, and enum labels only -- never a pipeline body, a node `type_config` value, a task or label string, or any secret. The tier is resolved daemon-side from the authoring agent, never tool-supplied. `repaired` is `false` unless the small-model graph repair producer (`orchestration.authoring.repairProducer`, default `true`) repaired the call -- opted out (`false`), it stays `false`. The system aggregate of this event -- the small-model pipeline-authoring failure rate and the pre-committed build/defer gate -- is surfaced by [`obs.system.health`](/reference/json-rpc#obs-system-health).

    **Small-model graph authoring:** `graph:repaired` and `graph:synthesized_from_intent` are the audit events for the small-model DAG-authoring layer (on by default -- see [`orchestration.authoring`](/reference/config-yaml#orchestration)). Both are **counts/ids/enums-only** -- the payload carries the matched/requested canonical `pattern` (`research-fanout` / `debate` / `vote` / `map-reduce`, a closed enum), the resulting `nodeCount`, and correlation ids; **never** a graph body, a node `type_config` value, a task or label string, or the one-line intent text. Both emits are **best-effort** (a telemetry failure is logged and swallowed, never surfaced as the operation result), and both fire **after** the synthesized/repaired graph passes the same validation a hand-authored graph runs -- so the event always reflects a *governed* graph.

    * `graph:repaired` fires when the daemon repairs a weak-tier model's schema-invalid graph to a canonical template. It additionally carries the calling `capabilityClass` tier (`frontier` / `mid` / `small` / `nano`, or `unknown`), resolved daemon-side. A conservative match is required: an ambiguous shape returns a structured did-you-mean instead, and no event is emitted.
    * `graph:synthesized_from_intent` fires when the `pipeline` tool's `from_intent` action synthesizes a graph from a one-line intent. It omits the tier (the synthesizer is tier-agnostic). The one-line intent text is the highest-risk leak and is intentionally absent from the payload.

    See [Execution Graphs: Small-model authoring](/agents/execution-graphs#small-model-authoring) for the behavior and [Pipelines: from\_intent](/agent-tools/pipelines#from-intent) for the action.

    **Attributed sub-agent kills:** `subagent:killed` fires at the runner's kill chokepoint for every force-killed sub-agent run, naming who initiated it via the closed `killedBy` union (`parent` | `health_monitor` | `operator` | `system`). A daemon health-monitor stuck-kill carries the `idleMs`/`thresholdMs` telemetry; the free-text kill reason never rides the bus (it stays on the run's failure record and the WARN log). `sessionKey` is the killed child's key, so the trajectory record lands in the child's own file and `comis explain` on the child yields the `subagent_stuck_killed` verdict.

    **Self-healing completion delivery:** `subagent:delivery_retried` and `subagent:delivery_deadlettered` track the retry/dead-letter routing when a sub-agent completion delivery falls back to a direct channel send and that send fails. Like `subagent:budget_exceeded`, both payloads are counts and ids only -- never the announcement text, the sub-agent output, or the error string.

    * `subagent:delivery_retried` fires once **per retry** of a *transient* delivery failure. `runId` is the sub-agent run, `channelType` is the target channel, `attempt` is the 1-based retry number, and `transient` is always `true` (only transient failures are retried).
    * `subagent:delivery_deadlettered` fires when a delivery is finally **dead-lettered** -- either after exhausting `security.agentToAgent.delivery.maxRetries` retries for a transient failure, or immediately for a permanent one. `attempt` is the number of retries that were exhausted (`0` for an immediate permanent dead-letter), and `transient` records whether the dead-letter followed an exhausted transient retry (`true`) or a permanent failure (`false`).

    **Mid-flight steering:** `subagent:steered` fires when [`subagent.steer`](/reference/json-rpc#subagent-steer) **injects** a steering message into a running child -- i.e. only when [`security.agentToAgent.steerInject`](/reference/config-yaml#security) is `true` (default `false`); the flag-off kill+respawn path does not emit it. Like the other `subagent:*` events, the payload is counts/ids/mode only -- `runId` is the steered run (the work is preserved; the same run continues), `agentId` is the owning agent, and `mode` is the closed union `"steer" | "followup"` recording which path landed the inject (`steer` when the child was streaming, `followup` when it was idle). The steering message body is **never** included -- no `message`, `text`, or task string is ever on the bus.

    See [Resilience: Self-healing delivery retries](/agents/resilience#self-healing-delivery-retries) for the transient/permanent classification and the backoff schedule.

    **Sandbox no-downgrade refusal:** `security:sandbox_downgrade_refused` fires when the fail-closed [no-downgrade invariant](/security/threat-model) refuses a sub-agent spawn because the child's resolved sandbox posture would be less confined than its spawner's. `parentAgentId`/`childAgentId` are agent ids; `violatedDimensions` lists the offending dimensions (`exec` / `filesystem` / `network` / `uid`); `parentPosture` and `childPosture` carry each dimension as a closed enum **label** (e.g. exec `always`/`never`). The payload is enum labels and ids only -- never filesystem paths, network hosts, uid numbers, or any credential value, so it does not leak the operator's sandbox topology. Gated by [`security.agentToAgent.sandboxNoDowngrade`](/reference/config-yaml) (default on).

    **Cache cost fields:** The `observability:token_usage` event includes prompt caching data when the provider supports it. `cost.cacheRead` and `cost.cacheWrite` are dollar costs for cached token reads/writes. `savedVsUncached` is the net savings compared to uncached pricing (positive = money saved). `cacheEligible` indicates whether the model supports prompt caching. `sessionKey` identifies the conversation session for per-session cost aggregation.
  </Accordion>

  <Accordion title="ChannelEvents (44 events)">
    Channel registration, sender blocking, command queuing, streaming delivery, typing indicators, auto-reply, send policies, debounce, group history, follow-up chains, priority lanes, elevated routing, retry engine, ack reactions, inbound reaction capture, steer lifecycle, block coalescing, unified delivery pipeline, delivery queue, channel health monitoring, delivery hooks, and sub-agent proxy typing events.

    | Event Name                    | Key Payload Fields                                                                                                      |
    | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
    | `ack:reaction_sent`           | channelId, channelType, messageId, emoji, timestamp                                                                     |
    | `autoreply:activated`         | channelId, senderId, activationMode, reason, timestamp                                                                  |
    | `autoreply:suppressed`        | channelId, senderId, reason, injectedAsHistory, timestamp                                                               |
    | `channel:deregistered`        | channelType, pluginId, timestamp                                                                                        |
    | `channel:health_changed`      | channelType, previousState, currentState, connectionMode, error, lastMessageAt, timestamp                               |
    | `channel:health_check`        | channelType, state, responseTimeMs, timestamp                                                                           |
    | `channel:reaction_received`   | messageId, reactorId, emoji, channelType, channelId, timestamp                                                          |
    | `channel:registered`          | channelType, pluginId, capabilities, timestamp                                                                          |
    | `coalesce:flushed`            | channelId, chatId, blockCount, charCount, trigger, timestamp                                                            |
    | `debounce:buffered`           | sessionKey, channelType, bufferedCount, windowMs, timestamp                                                             |
    | `debounce:flushed`            | sessionKey, channelType, messageCount, trigger, timestamp                                                               |
    | `delivery:aborted`            | channelId, channelType, reason, chunksDelivered, totalChunks, durationMs, origin, timestamp                             |
    | `delivery:acked`              | entryId, channelId, channelType, messageId, durationMs, timestamp                                                       |
    | `delivery:chunk_sent`         | channelId, channelType, chunkIndex, totalChunks, charCount, ok, retried, timestamp                                      |
    | `delivery:complete`           | channelId, channelType, totalChunks, deliveredChunks, failedChunks, totalChars, durationMs, origin, strategy, timestamp |
    | `delivery:enqueued`           | entryId, channelId, channelType, origin, timestamp                                                                      |
    | `delivery:failed`             | entryId, channelId, channelType, error, reason, timestamp                                                               |
    | `delivery:hook_cancelled`     | channelId, channelType, reason, origin, timestamp                                                                       |
    | `delivery:nacked`             | entryId, channelId, channelType, error, attemptCount, nextRetryAt, timestamp                                            |
    | `delivery:queue_drained`      | entriesAttempted, entriesDelivered, entriesFailed, durationMs, timestamp                                                |
    | `elevated:model_routed`       | sessionKey, senderTrustLevel, modelRoute, agentId, timestamp                                                            |
    | `followup:depth_exceeded`     | sessionKey, chainId, maxDepth, timestamp                                                                                |
    | `followup:enqueued`           | sessionKey, channelType, reason, chainId, chainDepth, timestamp                                                         |
    | `grouphistory:injected`       | sessionKey, channelType, messageCount, charCount, timestamp                                                             |
    | `priority:aged_promotion`     | sessionKey, fromLane, toLane, waitTimeMs, timestamp                                                                     |
    | `priority:lane_assigned`      | sessionKey, channelType, lane, reason, timestamp                                                                        |
    | `queue:coalesced`             | sessionKey, channelType, messageCount, timestamp                                                                        |
    | `queue:dequeued`              | sessionKey, channelType, waitTimeMs, timestamp                                                                          |
    | `queue:enqueued`              | sessionKey, channelType, queueDepth, mode, timestamp                                                                    |
    | `queue:overflow`              | sessionKey, channelType, policy, droppedCount, timestamp                                                                |
    | `retry:attempted`             | channelId, chatId, attempt, maxAttempts, delayMs, error, timestamp                                                      |
    | `retry:exhausted`             | channelId, chatId, totalAttempts, finalError, timestamp                                                                 |
    | `retry:markdown_fallback`     | channelId, chatId, originalParseMode, timestamp                                                                         |
    | `sender:blocked`              | channelType, senderId, channelId, timestamp                                                                             |
    | `sendpolicy:allowed`          | channelId, channelType, chatType, reason, timestamp                                                                     |
    | `sendpolicy:denied`           | channelId, channelType, chatType, reason, timestamp                                                                     |
    | `sendpolicy:override_changed` | sessionKey, override, changedBy, timestamp                                                                              |
    | `steer:followup_queued`       | sessionKey, channelType, agentId, reason, timestamp                                                                     |
    | `steer:injected`              | sessionKey, channelType, agentId, timestamp                                                                             |
    | `steer:rejected`              | sessionKey, channelType, agentId, reason, timestamp                                                                     |
    | `streaming:block_sent`        | channelId, chatId, blockIndex, totalBlocks, charCount, timestamp                                                        |
    | `typing:proxy_start`          | runId, channelType, channelId, parentSessionKey, agentId, threadId, timestamp                                           |
    | `typing:proxy_stop`           | runId, channelType, channelId, reason, durationMs, timestamp                                                            |
    | `typing:started`              | channelId, chatId, mode, timestamp                                                                                      |
    | `typing:stopped`              | channelId, chatId, durationMs, timestamp                                                                                |

    `channel:reaction_received` fires when someone adds a reaction to one of the agent's **outbound** messages on Discord, Slack, or Telegram (emitted by the channel-manager via the optional `adapter.onReaction` fanout). The payload is content-free — ids, the emoji, and a timestamp only, never a message body or a sender display name. The daemon consumes it as a corroborating [Verified Learning](/reference/config-yaml#learning-outcome-agents-learningoutcome) signal: it resolves the `messageId` to the originating trajectory (and ignores the event if it does not map to an agent-authored outbound message) and observes a `reaction`-source outcome. It is distinct from the *outbound* ack-reaction lifecycle (`ack:reaction_sent`, `reaction:phase_changed`, `reaction:terminal`, and friends), which tracks reactions the agent itself posts as progress indicators.
  </Accordion>

  <Accordion title="InfraEvents">
    Approval gates, config patches, plugin lifecycle, hook execution, auth token rotation, diagnostics, media file extraction, scheduler (cron, heartbeat, tasks), process metrics, observability admin, agent hot-add/remove, MCP server connection lifecycle, notifications, background task lifecycle, system lifecycle, secret management, security warnings, and lifecycle reaction events.

    | Event Name                                | Key Payload Fields                                                                                                                                                                                                                              |
    | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agent:hot_added`                         | agentId, timestamp                                                                                                                                                                                                                              |
    | `agent:hot_removed`                       | agentId, timestamp                                                                                                                                                                                                                              |
    | `approval:requested`                      | requestId, toolName, action, params, agentId, sessionKey, trustLevel, createdAt, timeoutMs, channelType                                                                                                                                         |
    | `approval:resolved`                       | requestId, approved, approvedBy, reason, resolvedAt                                                                                                                                                                                             |
    | `auth:token_rotated`                      | provider, profileName, expiresAtMs, timestamp                                                                                                                                                                                                   |
    | `background_task:cancelled`               | agentId, taskId, toolName, timestamp                                                                                                                                                                                                            |
    | `background_task:completed`               | agentId, taskId, toolName, durationMs, origin, timestamp                                                                                                                                                                                        |
    | `background_task:failed`                  | agentId, taskId, toolName, error, durationMs, origin, timestamp                                                                                                                                                                                 |
    | `background_task:notified`                | agentId, taskId, toolName, sessionKey, notified, reason, traceId, timestamp, trajectoryRecorded                                                                                                                                                 |
    | `background_task:promoted`                | agentId, taskId, toolName, timestamp                                                                                                                                                                                                            |
    | `background_task:reentered`               | taskId, agentId, sessionKey, hopCount, traceId, timestamp                                                                                                                                                                                       |
    | `config:patched`                          | section, key, patchedBy, timestamp                                                                                                                                                                                                              |
    | `diagnostic:billing_snapshot`             | providers, totalCost, timestamp                                                                                                                                                                                                                 |
    | `diagnostic:channel_health`               | channels, timestamp                                                                                                                                                                                                                             |
    | `diagnostic:message_processed`            | messageId, channelId, channelType, agentId, sessionKey, receivedAt, executionDurationMs, deliveryDurationMs, totalDurationMs, tokensUsed, cost, status, finishReason, failureStage, errorKind, timestamp                                        |
    | `diagnostic:webhook_delivered`            | webhookId, source, event, statusCode, success, durationMs, failureReason, timestamp                                                                                                                                                             |
    | `hook:executed`                           | hookName, pluginId, durationMs, success, error, timestamp                                                                                                                                                                                       |
    | `mcp:server:connected`                    | serverName, transport, toolCount, durationMs, timestamp                                                                                                                                                                                         |
    | `mcp:server:connect_failed`               | serverName, transport, reason, timestamp                                                                                                                                                                                                        |
    | `mcp:server:disconnected`                 | serverName, reason, timestamp                                                                                                                                                                                                                   |
    | `mcp:server:reconnect_failed`             | serverName, attempts, lastError, timestamp                                                                                                                                                                                                      |
    | `mcp:server:reconnected`                  | serverName, attempt, toolCount, durationMs, timestamp                                                                                                                                                                                           |
    | `mcp:server:reconnecting`                 | serverName, attempt, maxAttempts, nextDelayMs, timestamp                                                                                                                                                                                        |
    | `mcp:server:tools_changed`                | serverName, previousToolCount, currentToolCount, addedTools, removedTools, timestamp                                                                                                                                                            |
    | `media:file_extracted`                    | fileName, mimeType, chars, truncated, durationMs, timestamp                                                                                                                                                                                     |
    | `media:file_persisted`                    | relativePath, mimeType, sizeBytes, mediaKind, agentId, timestamp                                                                                                                                                                                |
    | `notification:delivered`                  | agentId, channelType, channelId, messageId, durationMs, timestamp                                                                                                                                                                               |
    | `notification:enqueued`                   | agentId, priority, channelType, channelId, origin, timestamp                                                                                                                                                                                    |
    | `notification:suppressed`                 | agentId, reason, priority, timestamp                                                                                                                                                                                                            |
    | `observability:metrics`                   | rssBytes, heapUsedBytes, heapTotalBytes, externalBytes, eventLoopDelayMs, activeHandles, uptimeSeconds, timestamp                                                                                                                               |
    | `observability:reset`                     | admin, table, rowsDeleted, timestamp                                                                                                                                                                                                            |
    | `plugin:deactivated`                      | pluginId, reason, timestamp                                                                                                                                                                                                                     |
    | `plugin:registered`                       | pluginId, pluginName, hookCount, timestamp                                                                                                                                                                                                      |
    | `reaction:cleanup`                        | messageId, channelType, channelId, chatId, removedEmoji, timestamp                                                                                                                                                                              |
    | `reaction:phase_changed`                  | messageId, channelType, channelId, chatId, phase, emoji, previousPhase, timestamp                                                                                                                                                               |
    | `reaction:stall_detected`                 | messageId, channelType, channelId, chatId, phase, severity, stallMs, timestamp                                                                                                                                                                  |
    | `reaction:terminal`                       | messageId, channelType, channelId, chatId, phase, emoji, timestamp                                                                                                                                                                              |
    | `scheduler:cron_execution_started`        | executionId, bootId, jobId, agentId, scheduledForMs, trigger, workKind, rootRunId, startedAtMs                                                                                                                                                  |
    | `scheduler:cron_execution_terminal`       | executionId, bootId, jobId, agentId, scheduledForMs, trigger, workKind, terminalAtMs, durationMs, outcomeKind, executionStatus, deliveryStatus, continuationStatus, queueDisposition, deliveredChunks, failedChunks, ambiguousChunks, errorKind |
    | `scheduler:cron_model_drift`              | executionId, previousExecutionId, jobId, agentId, previousModelResolved, modelResolved, previousModelResolutionSource, modelResolutionSource, timestamp, workKind, action                                                                       |
    | `scheduler:cron_ownership_reconciliation` | agentId, durationMs, timestamp, status, recoveredBeforeStart, ownerLostAfterStart, settledFromTerminal, retainedCurrentBoot, errorCode, errorKind                                                                                               |
    | `scheduler:cron_store_reset`              | agentId, operationId, target, beforeDigests, afterDigests, reactivated, timestamp                                                                                                                                                               |
    | `scheduler:heartbeat_alert`               | agentId, consecutiveErrors, classification, reason, backoffMs, timestamp                                                                                                                                                                        |
    | `scheduler:heartbeat_wake_admitted`       | correlationId, target, lane, retainedReason, disposition, timestamp                                                                                                                                                                             |
    | `scheduler:heartbeat_wake_deferred`       | correlationId, target, lane, reason, nextEligibleAtMs, errorKind, timestamp                                                                                                                                                                     |
    | `scheduler:heartbeat_wake_terminal`       | correlationId, target, lane, retainedReason, status, cancellationReason, eventEntryCount, durationMs, errorKind, timestamp                                                                                                                      |
    | `scheduler:job_suspended`                 | jobId, jobName, agentId, consecutiveErrors, lastError, timestamp, deliveryTarget                                                                                                                                                                |
    | `scheduler:wake_gate`                     | jobId, agentId, wake, durationMs, toolCalls, estTurnsSaved, failedOpen, timestamp                                                                                                                                                               |
    | `secret:accessed`                         | secretName, agentId, outcome, timestamp                                                                                                                                                                                                         |
    | `secret:modified`                         | secretName, action, timestamp                                                                                                                                                                                                                   |
    | `security:warn`                           | category, agentId, message, timestamp                                                                                                                                                                                                           |
    | `system:error`                            | error, source                                                                                                                                                                                                                                   |
    | `system:shutdown`                         | reason, graceful                                                                                                                                                                                                                                |
  </Accordion>

  <Accordion title="TerminalEvents (4 events)">
    Terminal session lifecycle, spawn failures, keystroke forwarding, and session pool eviction events.

    | Event Name                 | Key Payload Fields                                            |
    | -------------------------- | ------------------------------------------------------------- |
    | `terminal:session_state`   | Terminal session state change (created, active, idle, closed) |
    | `terminal:spawn_failed`    | Terminal session spawn failed                                 |
    | `terminal:keystroke`       | Keystroke sent to a terminal session                          |
    | `terminal:session_evicted` | Terminal session evicted from the session pool                |

    **Source:** `packages/core/src/event-bus/events-terminal.ts`
  </Accordion>
</AccordionGroup>

<Tip>
  All event payloads are TypeScript interfaces. Import `EventMap` from `@comis/core` and use your IDE's type inference to explore the exact payload shape for any event.
</Tip>

## Subscribing to Events

Plugins subscribe to events via lifecycle hooks, not directly on the event bus. The hook system provides priority ordering and result merging.

```typescript theme={}
import type { PluginPort, PluginRegistryApi } from "@comis/core";
import { ok, type Result } from "@comis/shared";

const metricsPlugin: PluginPort = {
  id: "metrics-collector",
  name: "Metrics Collector",
  register(registry: PluginRegistryApi): Result<void, Error> {
    // Count tool calls (void hook -- fire and forget)
    registry.registerHook("after_tool_call", (event) => {
      metrics.increment("tool_calls", { tool: event.toolName });
    });
    return ok(undefined);
  },
};
```

<Info>
  Plugins subscribe to events via lifecycle hooks (see [Plugins](/developer-guide/plugins)), not directly on the event bus. The hook system provides priority ordering and result merging. Direct `eventBus.on()` is available for internal subsystem wiring in the composition root.
</Info>

## Related

<CardGroup cols={2}>
  <Card title="Architecture" icon="hexagon" href="/developer-guide/architecture">
    TypedEventBus in the hexagonal architecture
  </Card>

  <Card title="Plugins" icon="puzzle-piece" href="/developer-guide/plugins">
    Hook into events via the plugin system
  </Card>

  <Card title="Custom Adapters" icon="plug" href="/developer-guide/custom-adapters">
    Adapters emit channel events
  </Card>

  <Card title="Packages" icon="cubes" href="/developer-guide/packages">
    Which package owns which events
  </Card>
</CardGroup>
