Skip to content

Actions

Actions are the sole mutation mechanism for subscribable state. They form a discriminated union keyed by type. Every action is wrapped in an ActionEnvelope for sequencing and origin tracking.

Action Envelope

typescript
ActionEnvelope {
  action: Action
  serverSeq: number                                     // monotonic, assigned by server
  origin: { clientId: string, clientSeq: number } | undefined  // undefined = server-originated
  rejectionReason?: string                              // present when the server rejected the action
}
  • serverSeq — Monotonically increasing sequence number assigned by the server, used for ordering and replay.
  • origin — Identifies who produced this action. undefined means the server itself (e.g. from an agent backend). Otherwise identifies the client that dispatched it.
  • rejectionReason — When present, indicates the server rejected the action. The client should revert its optimistic prediction. Contains a human-readable explanation (e.g. "no active turn to cancel", "unknown permission request ID").

Root Actions

These mutate the root state. All root actions are server-only — clients observe them but cannot produce them.

TypePayloadWhen
root/agentsChangedAgentInfo[]Available agent backends or their models changed

Session Actions

All scoped to a session URI. Some are server-only (produced by the agent backend), others can be dispatched directly by clients.

When a client dispatches an action, the server applies it to the state and also reacts to it as a side effect (e.g. session/turnStarted triggers agent processing, session/turnCancelled aborts it). This avoids a separate command→action translation layer for the common interactive cases.

Lifecycle

TypeClient-dispatchable?When
session/readyNoSession backend initialized successfully
session/creationFailedNoSession backend failed to initialize

Turn Lifecycle

TypeClient-dispatchable?When
session/turnStartedYesUser sent a message; server starts processing
session/deltaNoStreaming text chunk appended to a response part by partId
session/responsePartNoNew response part created (markdown, reasoning, content ref)
session/turnCompleteNoTurn finished (assistant idle)
session/turnCancelledYesTurn was aborted; server stops processing
session/errorNoError during turn processing

Tool Calls

TypeClient-dispatchable?When
session/toolStartNoTool execution began
session/toolCompleteNoTool execution finished

FUTURE WORK

A session/toolUpdate action for streaming incremental tool output (e.g. terminal output during a shell command) is planned for a future protocol version. :::\n\n### Metadata & Informational

TypeClient-dispatchable?When
session/titleChangedYesSession title updated (auto-generated or client rename)
session/usageNoToken usage report
session/reasoningNoReasoning/thinking text appended to a reasoning part by partId
session/modelChangedYesModel changed for this session
session/isReadChangedYesClient marked session as read or unread
session/isArchivedChangedYesClient archived or unarchived session
session/activityChangedNoServer updated the session's current activity description

Pending Messages

TypeClient-dispatchable?When
session/pendingMessageSetYesA steering or queued message was set (upsert)
session/pendingMessageRemovedYesA pending message was cancelled (by client) or consumed (by server)
session/queuedMessagesReorderedYesQueued messages were reordered

The pendingMessageSet and pendingMessageRemoved actions carry a kind discriminant ('steering' or 'queued'). See the State Model — Pending Messages for semantics.

Customizations

TypeClient-dispatchable?When
session/customizationsChangedNoServer updated the session's customization list (full replacement)
session/customizationToggledYesClient toggled a customization on or off by URI

See the Customizations guide for the full flow.

Client-Dispatched Actions

Clients interact with the server by dispatching actions as fire-and-forget notifications:

jsonc
// Client → Server
{
  "jsonrpc": "2.0",
  "method": "dispatchAction",
  "params": {
    "clientSeq": 1,
    "action": { "type": "session/turnStarted", "session": "copilot:/<uuid>", ... }
  }
}

The client applies the action optimistically to its local state before sending. When the server echoes it back in an ActionEnvelope, the client reconciles (see Write-Ahead Reconciliation).

ActionServer-side effect
session/turnStartedBegins agent processing for the new turn
session/toolCallConfirmedApproves or denies a pending tool call; unblocks or cancels tool execution
session/turnCancelledAborts the in-progress turn
session/titleChangedUpdates the session title (rename)
session/modelChangedChanges the model for subsequent turns
session/pendingMessageSetStores a steering or queued message (upsert); if queued and idle, auto-starts a turn
session/pendingMessageRemovedCancels a pending message before it is consumed
session/queuedMessagesReorderedReorders queued messages; unknown IDs ignored, unmentioned messages kept at end
session/customizationToggledToggles a customization on or off by URI
session/isReadChangedMarks the session as read or unread
session/isArchivedChangedArchives or unarchives the session
session/activityChangedUpdates the session's current activity description

Reducers

State is mutated by pure reducer functions:

typescript
rootReducer(state: RootState, action: RootAction): RootState
sessionReducer(state: SessionState, action: SessionAction): SessionState

Reducers are pure — no side effects, no I/O. The same reducer code runs on both server and client, which is what makes write-ahead possible. Server-side effects (e.g. forwarding a message to the agent SDK) are handled by a separate dispatch layer, not in the reducer.

The reducer switch on action type is exhaustive — the compiler errors if a case is missing. This guarantees that every action type is handled.

Next Steps

Released under the MIT License.