How Conversations Are Managed as Sessions in the Copilot SDK

The Copilot SDK encapsulates every conversation with the CLI as a CopilotSession object, which manages state, RPC communication, event handling, and persistence across the dialogue lifecycle.

The github/copilot-sdk repository treats each interaction with GitHub Copilot as a stateful session rather than a stateless request. A CopilotSession serves as the central coordination point for message flow, tool execution, permission management, and UI interactions. Understanding how these sessions are architected is essential for building robust integrations that leverage the SDK's full capabilities.

Understanding Copilot SDK Session Architecture

Session Lifecycle and Instantiation

Sessions are instantiated through CopilotClient.createSession or resumed via CopilotClient.resumeSession. According to the source code in nodejs/src/session.ts (lines 61-73), the CopilotSession constructor is internal and receives a unique sessionId, a JSON-RPC MessageConnection, an optional workspacePath, and optional MCP authentication handlers.

When you create a new session, the SDK establishes a scoped communication channel:

const client = await CopilotClient.create();
const session = await client.createSession({ model: "gpt-4" });

The session immediately becomes the owner of all subsequent interactions, maintaining isolated state from other concurrent sessions.

State Storage and Memory Management

Sessions maintain in-memory state through structures including eventHandlers, toolHandlers, and openCanvasInstances (as implemented in nodejs/src/session.ts, lines 93-102). When infinite sessions are enabled, the SDK persists conversation state to disk using the provided workspacePath to store checkpoints, plan files, and artifact files.

This dual-layer storage allows sessions to survive process restarts. The in-memory structures handle live conversation flow, while the workspace directory enables long-running dialogues that can be resumed days later.

RPC Communication and Message Flow

Session-Scoped RPC Methods

Each session exposes a typed RPC façade through the rpc getter. As defined in nodejs/src/session.ts (lines 81-86), this lazily creates a session-scoped RPC client via createSessionRpc that exposes methods under the session namespace, including session.send and session.getMessages.

This design ensures that RPC calls are automatically routed to the correct conversation context without manual session ID management.

Sending Messages and Waiting for Responses

The SDK provides two primary methods for message transmission:

  • send: Forwards a prompt to the server via session.send (fire-and-forget)
  • sendAndWait: Builds on send and listens for assistant.message and session.idle events, returning the final assistant message or timing out

According to nodejs/src/session.ts (lines 44-62 and 86-106), sendAndWait handles the complexity of streaming responses, aggregating partial messages until the session signals idleness.

const result = await session.sendAndWait({ 
  prompt: "Explain the Singleton pattern." 
});
console.log(result?.data.content);

Event Handling and Broadcasting

Event Subscription Patterns

Sessions expose an on method for registering event listeners. As implemented in nodejs/src/session.ts (lines 85-112), you can subscribe to specific event types (on(eventType, handler)) or register wildcard listeners (on(handler)) that receive all events.

This pattern enables reactive architectures where UI updates, logging, or side effects respond to conversation state changes:

const unsubscribe = session.on("assistant.message", (event) => {
  console.log("Assistant:", event.data.content);
});

Broadcast Event Routing

Internally, _dispatchEvent routes events to registered handlers. For broadcast requests—including tool calls, permission requests, MCP auth, commands, and elicitation—the session uses _handleBroadcastEvent to match incoming requests with registered handlers and manage asynchronous responses.

Tool Execution and Permission Management

Registering Custom Tools

Custom tools are registered via registerTools, which accepts an array of tool definitions with names, descriptions, and handlers. When the runtime emits external_tool.requested, the session's _handleBroadcastEvent forwards the call to the matching ToolHandler, captures the result, and replies through rpc.tools.handlePendingToolCall (as shown in nodejs/src/session.ts, lines 84-108).

session.registerTools([
  {
    name: "weather.fetch",
    description: "Fetch current weather for a city.",
    handler: async (args) => {
      const { city } = args as { city: string };
      const resp = await fetch(`https://api.weather.com/v3/${city}`);
      const data = await resp.json();
      return `Temperature: ${data.temp}°C`;
    },
  },
]);

Handling Permission Requests

Permission requests (permission.requested) are routed to user-provided PermissionHandler callbacks. The session manages the request lifecycle, sending results back via rpc.permissions.handlePendingPermissionRequest (lines 108-123).

session.registerPermissionHandler(async (request) => {
  if (request.action === "file.write" && request.path.endsWith(".txt")) {
    return { kind: "allowed" };
  }
  return { kind: "denied", reason: "Only .txt files are allowed" };
});

MCP Authentication Flows

For Model Context Protocol (MCP) integrations, the session handles OAuth requirements (mcp.oauth_required) through registered McpAuthHandler instances, responding via rpc.mcp.oauth.handlePendingRequest (lines 120-138).

Session Persistence and Resume Capabilities

Infinite Sessions and Workspace Storage

When infinite sessions are enabled, the workspacePath parameter triggers persistent storage of conversation checkpoints and artifacts. This allows the session to maintain context across application restarts, storing plan files and intermediate states on disk while keeping the active conversation state in memory.

Graceful Disconnection and Cleanup

The disconnect() method (lines 124-135) sends session.destroy to the server while preserving on-disk data for future resumption. This triggers _markDisconnected, which clears all in-memory handlers and event listeners without deleting persisted workspace data.

To fully remove a session, including its persisted state, use CopilotClient.deleteSession:

await session.disconnect();  // Keep for resume
await client.deleteSession(sessionId);  // Permanent removal

Resuming a session restores the workspace and re-populates open canvases:

const resumed = await client.resumeSession("abc123");
await resumed.sendAndWait({ prompt: "Continue where we left off." });

Cross-Language Session Consistency

The Copilot SDK maintains architectural parity across language bindings. Each implementation follows the same session management patterns:

All bindings support session-scoped RPC, event dispatch, tool/permission handling, and persistence, ensuring consistent behavior regardless of implementation language.

Summary

  • CopilotSession objects encapsulate all state for a single conversation, providing scoped RPC methods and event handling.
  • Sessions support both in-memory operation and persistent storage via workspace directories when infinite sessions are enabled.
  • The RPC façade (rpc getter) exposes type-safe methods like send and sendAndWait that handle message streaming and completion detection.
  • Event handling uses a subscription model with support for both specific event types and wildcard listeners, routing tool calls and permissions through _handleBroadcastEvent.
  • Tool execution and permission management are handled via registered callbacks that communicate results back to the runtime through session-scoped RPC.
  • Sessions can be resumed using resumeSession with their sessionId, restoring workspace state and conversation context across process lifecycles.

Frequently Asked Questions

What is the difference between disconnect() and deleteSession() in the Copilot SDK?

disconnect() gracefully terminates the active session connection while preserving any persisted workspace data for later resumption. It sends session.destroy to the server and clears in-memory handlers via _markDisconnected. In contrast, deleteSession() (called on CopilotClient) permanently removes both the session reference and all associated on-disk artifacts, making the session ID invalid for future resumeSession calls.

How does the Copilot SDK handle long-running conversations that exceed memory limits?

The SDK supports infinite sessions that persist conversation state to disk using the workspacePath parameter. When enabled, the session stores checkpoints, plan files, and artifacts in the workspace directory, allowing the conversation to be resumed later with CopilotClient.resumeSession. This architecture prevents memory bloat by offloading historical context to disk while maintaining active state in memory.

Can multiple sessions exist simultaneously in the same process?

Yes, the Copilot SDK supports multiple concurrent sessions. Each session maintains isolated state through its own CopilotSession instance with unique sessionId values and separate RPC connections. Sessions do not share event handlers, tool registrations, or permission handlers, ensuring that conversations remain isolated even when operating within the same CopilotClient instance.

What events indicate that a conversation turn has completed?

The SDK uses the session.idle event to signal conversation completion, which sendAndWait listens for alongside assistant.message events. When the assistant finishes generating content and the session enters an idle state, sendAndWait resolves with the final aggregated message. You can also subscribe to these events directly using session.on("session.idle", handler) for custom flow control.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →