Skip to content

System events and UI projection

System events and UI projection

This document explains how system-level session events become model context, timeline entries, telemetry, and UI updates in the extracted Copilot CLI bundle. In the analyzed app.js, events such as system.message, system.notification, session.info, session.warning, session.error, pending_messages.modified, and session.custom_notification are not equivalent. Some are model-visible, some are timeline-only, some are ephemeral UI state, and some are telemetry-only.

Because app.js is bundled/minified, symbol names are unstable. Line references below are searchable anchors in the extracted bundle and will shift across releases.

Source anchors

AreaAnchor strings / minified symbolsApprox. app.js lineWhat it shows
System prompt eventsystem.message, upsertSystemContextMessage4361, 4471, 4475System/developer messages update model-visible system context.
System notification eventsystem.notification, instruction_discovered4361, 4471, 4475, 4481Runtime notifications can become chat-like user context, with instruction discovery filtered.
Info/warning/error eventssession.info, session.warning, session.error4361, 4396Timeline display messages with category and optional URL.
Notification API bridgelevel, info, warning, error, emitEphemeral4396A notification helper maps severity to session info/warning/error events.
Pending queue updatepending_messages.modified4361, 4471, 4479Empty ephemeral event tells clients prompt queue state changed.
Custom notificationssession.custom_notification, payload, mcp task event callback4361, 4481Opaque source-defined notifications, including MCP task events.
Tool/capability statesession.tools_updated, session.context_changed, session.mode_changed4361, 4471, 4475UI/state update events that are not ordinary chat messages.
Telemetry projectionSYSTEM_NOTIFICATION, github.copilot.system.notification.*5742System notifications are projected into OpenTelemetry attributes/events.
Timeline entriesadd-timeline-entry, timeline, onSystemNotification1739, 4644, 4783Command handlers return timeline entries; renderer maps event-like records to UI/Markdown.
Ephemeral event helperemitEphemeral4207+Streaming, queue, tool, and status updates can be emitted without durable conversation semantics.

Event categories

flowchart TD
Event[Session event] --> ModelVisible{Model-visible?}
ModelVisible -->|yes| SystemContext[system.message -> system/developer context]
ModelVisible -->|sometimes| NotificationContext[system.notification -> user-like context]
Event --> Timeline{Timeline display?}
Timeline --> Info[session.info]
Timeline --> Warning[session.warning]
Timeline --> Error[session.error]
Timeline --> AddEntry[add-timeline-entry command results]
Event --> Ephemeral{Ephemeral UI state?}
Ephemeral --> Queue[pending_messages.modified]
Ephemeral --> Tools[session.tools_updated]
Ephemeral --> Custom[session.custom_notification]
Event --> Telemetry[OpenTelemetry / session telemetry]

The important design point is that session events have multiple projections. The same event stream can drive model replay, terminal UI, ACP/JSON-RPC clients, remote control, telemetry, and persistence.

system.message

system.message is the clearest model-visible system event. Its schema includes:

FieldMeaning
contentSystem/developer prompt text sent as model input.
roleEither system or developer.
nameOptional name identifier for a developer/system message.

During event processing, system.message calls upsertSystemContextMessage(...). That method maintains a system context message list and updates currentSystemMessage when the role is system and content is string text.

This means system.message is not just UI copy. It mutates the model context used for future calls.

system.notification

system.notification is runtime-generated notification text, typically wrapped in <system_notification> XML tags. Its schema includes:

FieldMeaning
contentNotification text.
kindStructured metadata identifying what triggered it.

The event processor treats most system.notification events as user-like model context by pushing a { role: "user", content } chat message. There is one important exception: if the kind is instruction_discovered, it is not pushed into _chatMessages during replay.

That exception avoids turning every dynamic instruction discovery notice into persistent user prompt content while still allowing the UI/telemetry to show that an instruction file was discovered.

Instruction discovery notifications

When on-demand instructions are enabled, file access can discover additional instruction sources. The runtime emits:

system.notification({
content: "Discovered instruction: <path>",
kind: {
type: "instruction_discovered",
sourcePath,
triggerFile,
triggerTool,
description
}
})

The notification is visible as a runtime event and telemetry signal, but the chat-message replay path filters it out from model-visible user content. The actual instruction content is handled through the dynamic instruction loader, not by relying on the notification text as the instruction.

session.info, session.warning, and session.error

These events are timeline/status messages rather than prompt messages.

EventTypical purpose
session.infoInformational timeline message: timing, context window, MCP, snapshot, configuration, auth, model, notification.
session.warningWarning timeline message: subscription, policy, MCP, resume continuity, notification.
session.errorError timeline message: auth, authorization, quota, rate limit, context limit, query, notification, IDE, etc.

The event schemas include category fields (infoType, warningType, errorType), human-readable message, and optional url for user action.

A notification helper maps severity levels:

Input levelEmitted event
info or missingsession.info
warningsession.warning
errorsession.error

Each can be emitted as durable or ephemeral depending on the notification request.

Ephemeral event semantics

Many UI events are emitted through emitEphemeral(...). Ephemeral events are useful for live clients but do not always carry durable conversation meaning.

Examples include:

EventMeaning
assistant.message_deltaStreaming assistant text chunk.
assistant.reasoning_deltaStreaming reasoning/summary chunk.
assistant.streaming_deltaStreaming byte/size update.
pending_messages.modifiedPrompt queue changed.
tool.execution_progressTool/MCP progress update.
session.tools_updatedTool definitions changed.
session.extensions_loadedExtension state changed.
session.custom_notificationSource-defined notification payload.

Ephemeral does not mean unimportant. It means the event is primarily a live UI/protocol state update rather than a stable model message.

pending_messages.modified

This event has an empty payload. Its only meaning is that the pending prompt/message queue changed.

It is emitted when:

  • immediate prompts are injected;
  • queued messages are shifted for processing;
  • a prompt is enqueued while processing is active;
  • queue processing is stopped/cleared by a command handler.

Clients use it to refresh queue indicators, pending prompt counts, or status displays.

session.custom_notification

session.custom_notification carries opaque custom notification data with a source-defined payload. The schema includes version/source/subject/payload-like fields rather than a fixed event-specific shape.

MCP task event callbacks emit this event after converting task events into custom notification payloads. This gives external subsystems a way to notify the TUI/ACP clients without adding a new first-class session event for every source.

State-update events

Some events update UI state or telemetry but are not chat messages:

EventRole
session.context_changedWorking directory/Git context changed.
session.mode_changedAgent mode changed, e.g. interactive/plan/autopilot.
session.tools_updatedAvailable tools changed for the selected model/session.
session.skills_loadedSkill catalog changed.
session.custom_agents_updatedCustom agent catalog changed.
session.mcp_servers_loadedMCP server list/status changed.
session.mcp_server_status_changedOne MCP server status changed.
session.extensions_loadedSDK extension list/status changed.

The event processor recognizes these events and avoids treating them as unknown errors, but they do not become model-visible chat messages by default.

Timeline entries from command results

Slash command handlers often return objects like:

{ kind: "add-timeline-entry", entry: { type: "info" | "error" | "copilot" | ..., text } }

These are not the same as session events in the persisted event schema, but the TUI projection layer renders them into the visible timeline. Examples appear in LSP commands, plugin commands, MCP commands, reindexing, and many diagnostics commands.

The renderer also has timeline entry handlers such as onSystemNotification, onToolCallRequested, onToolCallCompleted, and onCompaction, which convert internal timeline records into terminal/Markdown output.

Telemetry projection

OpenTelemetry projection has a separate mapping. The scan found constants such as:

  • github.copilot.system.notification;
  • github.copilot.system.notification.type;
  • github.copilot.system.notification.agent_id;
  • github.copilot.system.notification.agent_type;
  • github.copilot.system.notification.status;
  • github.copilot.system.notification.shell_id;
  • github.copilot.system.notification.exit_code.

This means system notifications can be represented in telemetry even when they are filtered from chat replay or displayed only as timeline state.

Model-visible versus UI-visible

Event/resultModel-visible?UI-visible?Notes
system.messageYesUsually not as ordinary chatUpdates system/developer context.
system.notificationUsually yesYesExcept instruction_discovered is filtered from chat replay.
session.infoNoYesTimeline/status display.
session.warningNoYesTimeline/status display.
session.errorNo direct prompt roleYesAlso telemetry and control-flow effects.
pending_messages.modifiedNoYesEphemeral queue state.
session.custom_notificationNo by defaultYesOpaque event for clients.
add-timeline-entry command resultNo by itselfYesCommand handler UI projection.

Relationship to other docs

  • session-support-implementation.md explains event-sourced session persistence and replay.
  • built-in-tool-execution-pipeline.md explains tool lifecycle events.
  • hooks-lifecycle-automation.md explains hook events and notification hooks.
  • remote-control-implementation.md explains which events are exported to remote clients.
  • observability-update-shutdown.md explains telemetry/logging projections.

Created and maintained by Yingting Huang.