Sessions Provider Architecture
Overview
The Sessions Provider architecture introduces an extensible provider model for managing agent sessions in the Agent Sessions window. Instead of hardcoding session types and backends, multiple providers register with a central registry (ISessionsProvidersService), and the management service (ISessionsManagementService) aggregates sessions from all providers and routes actions to the correct one.
This design allows new compute environments (remote agent hosts, cloud backends, third-party agents) to plug in without modifying core session management code.
Architectural Flow
Core Interfaces
ISession — Universal Session Facade
File: src/vs/sessions/services/sessions/common/session.ts
The common session interface exposed by all providers. It is a self-contained facade — consumers should not reach back to underlying services to resolve additional data. All mutable properties are observables for reactive UI binding. A session groups one or more chats together; ISession fields are propagated from the primary (first) chat.
| Property | Type | Description |
|---|---|---|
sessionId | string | Globally unique ID in the format providerId:localId |
resource | URI | Resource URI identifying this session |
providerId | string | ID of the owning provider |
sessionType | string | Session type ID (e.g., 'copilot-cli', 'copilot-cloud') |
icon | ThemeIcon | Display icon |
createdAt | Date | Creation timestamp |
workspace | IObservable<ISessionWorkspace | undefined> | Workspace info (repositories, label, icon) |
title | IObservable<string> | Display title (auto-titled or renamed) |
updatedAt | IObservable<Date> | Last update timestamp |
status | IObservable<SessionStatus> | Current status (Untitled, InProgress, NeedsInput, Completed, Error) |
changes | IObservable<readonly IChatSessionFileChange[]> | File changes produced by the session |
modelId | IObservable<string | undefined> | Selected model identifier |
mode | IObservable<{id, kind} | undefined> | Selected mode identifier and kind |
loading | IObservable<boolean> | Whether the session is initializing |
isArchived | IObservable<boolean> | Archive state |
isRead | IObservable<boolean> | Read/unread state |
description | IObservable<IMarkdownString | undefined> | Status description (e.g., current agent action), supports markdown |
lastTurnEnd | IObservable<Date | undefined> | When the last agent turn ended |
gitHubInfo | IObservable<IGitHubInfo | undefined> | GitHub owner/repo/PR info associated with the session |
chats | IObservable<readonly IChat[]> | The chats belonging to this session group |
mainChat | IChat | The main (first) chat of this session |
Supporting Types
ISessionWorkspace — Workspace information for a session:
label: string— Display label (e.g., "my-app", "org/repo")icon: ThemeIcon— Workspace iconrepositories: ISessionRepository[]— One or more repositoriesrequiresWorkspaceTrust: boolean— Whether workspace trust is required to operate
Workspace label and session grouping: The sessions list groups sessions by workspace.label. Sessions whose workspace is undefined or whose label is empty appear under "Unknown". For the CopilotChatSessionsProvider, the workspace label is derived from session metadata via getRepositoryName() (in agentSessionsViewer.ts), which checks these metadata keys in priority order: remoteAgentHost, owner+name, repositoryNwo, repository, repositoryUrl, repositoryPath, worktreePath, workingDirectoryPath, then badge. See the ChatSessionItem Metadata Contract section below for full details.
ISessionRepository — A repository within a workspace:
uri: URI— Source repository URI (file://orgithub-remote-file://)workingDirectory: URI | undefined— Worktree or checkout pathdetail: string | undefined— Provider-chosen display detail (e.g., branch name)baseBranchName: string | undefined— Name of the base branchbaseBranchProtected: boolean | undefined— Whether the base branch is protected
IGitHubInfo — GitHub information associated with a session:
owner: string— GitHub repository ownerrepo: string— GitHub repository namepullRequest?: { number: number; uri: URI; icon?: ThemeIcon }— Associated pull request
IChat — A single chat within a session:
resource: URI— Resource URI identifying this chatcreatedAt: Date— When the chat was createdtitle: IObservable<string>— Chat display titleupdatedAt: IObservable<Date>— When the chat was last updatedstatus: IObservable<SessionStatus>— Current chat statuschanges: IObservable<readonly IChatSessionFileChange[]>— File changesmodelId: IObservable<string | undefined>— Selected modelmode: IObservable<{ id: string; kind: string } | undefined>— Selected modeisArchived: IObservable<boolean>— Archive stateisRead: IObservable<boolean>— Read/unread statedescription: IObservable<IMarkdownString | undefined>— Status descriptionlastTurnEnd: IObservable<Date | undefined>— When the last agent turn ended
SessionStatus — Enum: Untitled, InProgress, NeedsInput, Completed, Error
ISessionsProvider — Provider Contract
File: src/vs/sessions/services/sessions/common/sessionsProvider.ts
A sessions provider encapsulates a compute environment. It owns workspace discovery, session creation, session listing, and picker contributions. One provider can serve multiple session types, and multiple provider instances can serve the same session type (e.g., one per remote agent host).
Identity
| Property | Type | Description |
|---|---|---|
id | string | Unique provider instance ID (e.g., 'default-copilot', 'agenthost-hostA') |
label | string | Display label |
icon | ThemeIcon | Provider icon |
sessionTypes | readonly ISessionType[] | Session types this provider supports |
onDidChangeSessionTypes? | Event<void> | Optional; fires when session types change dynamically (e.g., a remote host advertises a new agent) |
Workspace Discovery
| Member | Description |
|---|---|
browseActions: readonly ISessionWorkspaceBrowseAction[] | Actions shown in the workspace picker (e.g., "Folders", "Repositories") |
resolveWorkspace(repositoryUri: URI): ISessionWorkspace | Resolve a URI to a session workspace with label and icon |
Session Listing
| Member | Description |
|---|---|
getSessions(): ISession[] | Returns all sessions owned by this provider |
onDidChangeSessions: Event<ISessionChangeEvent> | Fires when sessions are added, removed, or changed |
onDidReplaceSession?: Event<{ from: ISession; to: ISession }> | Optional; fires when a temporary (untitled) session is atomically replaced by a committed session after the first turn |
Session Lifecycle
| Method | Description |
|---|---|
createNewSession(workspace) | Create a new session for a given workspace |
setSessionType(sessionId, type) | Change the session type; returns the updated ISession |
getSessionTypes(sessionId) | Get available session types for a session |
renameChat(sessionId, chatUri, title) | Rename a chat within a session |
setModel(sessionId, modelId) | Set the model |
archiveSession(sessionId) | Archive a session |
unarchiveSession(sessionId) | Unarchive a session |
deleteSession(sessionId) | Delete a session |
deleteChat(sessionId, chatUri) | Delete a single chat from a session |
setRead(sessionId, read) | Mark read/unread |
Send
| Method | Description |
|---|---|
sendAndCreateChat(sessionId, options) | Send a request, creating a new chat in the session; returns the updated ISession |
Supporting Types
ISessionType — A platform-level session type identifying an agent backend:
id: string— Unique identifier (e.g.,'copilot-cli','copilot-cloud')label: string— Display labelicon: ThemeIcon— Icon
ISessionWorkspaceBrowseAction — A browse action shown in the workspace picker:
label: string,icon: ThemeIcon,providerId: stringrun(): Promise<ISessionWorkspace | undefined>— Opens the browse dialog
ISessionChangeEvent — Change event:
added: readonly ISession[]removed: readonly ISession[]changed: readonly ISession[]
ISendRequestOptions — Send request options:
query: string— Query textattachedContext?: IChatRequestVariableEntry[]— Optional attached context entries
ISessionsProvidersService — Central Registry
File: src/vs/sessions/services/sessions/browser/sessionsProvidersService.ts
Central service that manages the provider registry. Providers register and unregister here; the service fires change events to notify consumers.
Provider Registry
| Member | Description |
|---|---|
registerProvider(provider): IDisposable | Register a provider; returns disposable to unregister |
getProviders(): ISessionsProvider[] | Get all registered providers |
getProvider<T>(providerId): T | undefined | Get a specific provider by ID |
onDidChangeProviders: Event<ISessionsProvidersChangeEvent> | Fires with { added, removed } arrays when providers are registered or unregistered |
Note: The providers service is a pure registry. It does not aggregate sessions or route actions — that responsibility belongs to ISessionsManagementService.
ISessionsProvidersChangeEvent:
added: readonly ISessionsProvider[]removed: readonly ISessionsProvider[]
ISessionsManagementService — High-Level Orchestration
File: src/vs/sessions/services/sessions/common/sessionsManagement.ts (interface) / src/vs/sessions/services/sessions/browser/sessionsManagementService.ts (implementation)
Coordinates active session tracking, provider selection, and user workflows. Sits above the providers service and adds UI-facing concerns like context keys, session aggregation, and action routing.
Sessions
| Member | Description |
|---|---|
getSessions(): ISession[] | Get all sessions from all registered providers |
getSession(resource: URI): ISession | undefined | Look up a session by its resource URI |
getSessionTypes(session): ISessionType[] | Get session types available for a session |
getAllSessionTypes(): ISessionType[] | Get all session types from all registered providers |
onDidChangeSessionTypes: Event<void> | Fires when available session types change |
onDidChangeSessions: Event<ISessionsChangeEvent> | Fires when sessions change across any provider |
Active Session
| Member | Description |
|---|---|
activeSession: IObservable<IActiveSession | undefined> | The currently active session (extends ISession with activeChat: IObservable<IChat>) |
activeProviderId: IObservable<string | undefined> | Auto-selects when one provider exists, persists to storage |
setActiveProvider(providerId) | Set the active sessions provider by ID |
openSession(sessionResource, options?) | Select an existing session as active |
openChat(session, chatUri) | Open a specific chat within a session |
openNewSessionView() | Switch to the new-session view |
Session Creation & Send
| Method | Description |
|---|---|
createNewSession(providerId, workspace) | Create a new session, delegates to the correct provider |
unsetNewSession() | Unset the current new session |
sendAndCreateChat(session, options) | Send a request, creating a new chat in the session |
setSessionType(session, type) | Update the session type for a new session |
Context Keys Managed
The management service binds and updates these context keys:
| Context Key | Type | Description |
|---|---|---|
isNewChatSession | boolean | true when viewing the new-session form |
activeSessionProviderId | string | Provider ID of the active session |
activeSessionType | string | Session type of the active session |
isActiveSessionBackgroundProvider | boolean | Whether the active session uses the background agent provider |
isActiveSessionArchived | boolean | Whether the active session is archived |
activeSessionSupportsMultiChat | boolean | Whether the active session supports multiple chats |
Multi-Chat Sessions
A session can contain multiple chats (conversations), controlled by the session's capabilities.supportsMultipleChats property. When enabled, users can start additional conversations within the same session, sharing its workspace context.
Session–Chat Relationship
ISession groups one or more IChat instances:
Property derivation from chats:
title,changes,modelId,mode,loading,isArchived,description,gitHubInfo— all come frommainChatupdatedAt— latestupdatedAtacross all chatsstatus— aggregated:NeedsInput>InProgress> other statusesisRead—trueonly when all chats are readlastTurnEnd— latestlastTurnEndacross all chats
Context Keys
When the active session supports multi-chat (capabilities.supportsMultipleChats is true), the context key activeSessionSupportsMultiChat is set to true, enabling multi-chat UI elements (e.g., "New Chat" button).
Active Chat Tracking
IActiveSession extends ISession with an activeChat observable:
Initialized to the first chat when a session becomes active
Updated when
openChat(session, chatUri)is called to switch between chatsAutomatically updated to the latest chat when
sendAndCreateChatcreates a new one
Multi-Chat Provider Methods
| Method | Description |
|---|---|
sendAndCreateChat(sessionId, options) | Creates a new chat in the session and sends the request. For new sessions this is the first chat; for existing sessions it adds another chat to the group. |
deleteChat(sessionId, chatUri) | Deletes an individual chat; if only one chat remains, deletes the entire session |
renameChat(sessionId, chatUri, title) | Renames a specific chat by its URI |
openChat(session, chatUri) | Switches the active chat within the session (management service) |
Data Flow: Session Lifecycle
Creating a New Session
Session Change Propagation
ChatSessionItem Metadata Contract
File (type definition): src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts File (metadata consumer): src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts (getRepositoryName()) File (adapter consumer): src/vs/sessions/contrib/copilotChatSessions/browser/copilotChatSessionsProvider.ts (AgentSessionAdapter._buildWorkspace())
Extensions that implement a ChatSessionItemController or ChatSessionContentProvider create ChatSessionItem objects. These items have a metadata property (a Record<string, unknown>) that the sessions list uses to determine workspace grouping, repository information, and display labels.
Why This Matters
The sessions list groups sessions by workspace folder. AgentSessionAdapter._buildWorkspace() reads item.metadata to construct an ISessionWorkspace — specifically its label, which determines which section the session appears under. If metadata is missing or lacks a recognizable key, the session appears under "Unknown".
Required Metadata
Every ChatSessionItem created by a controller — whether from newChatSessionItemHandler, forkHandler, or _createChatSessionItem during refresh — must set item.metadata with at least one of the keys listed below so the session groups correctly.
For local sessions, the minimum is:
Metadata Key Priority
getRepositoryName() checks metadata keys in this priority order to derive the workspace label. The first match wins:
| Priority | Key(s) | Used By | Example |
|---|---|---|---|
| 1 | remoteAgentHost + workingDirectoryPath | Remote agent host sessions | "myproject [dev-box]" |
| 2 | owner + name | Cloud sessions | "my-repo" |
| 3 | repositoryNwo | Sessions with owner/repo format | "repo" from "owner/repo" |
| 4 | repository | Git remote URL or owner/repo string | Parsed repo name |
| 5 | repositoryUrl | Full GitHub URL | Parsed repo name |
| 6 | repositoryPath | Repository directory path | Basename of path |
| 7 | worktreePath | Git worktree path | Basename of path |
| 8 | workingDirectoryPath | Working directory (fallback) | Basename of path |
| 9 | (badge fallback) | session.badge value | Parsed from badge string |
Common Patterns
Local sessions (Copilot CLI, Claude): Set workingDirectoryPath to the workspace folder's absolute path. This is the most common case and the minimum required for correct grouping.
Cloud sessions: Set owner and name for GitHub repository identification.
Remote agent host sessions: Set remoteAgentHost and workingDirectoryPath for composite labels.
Checklist for New Controller Implementations
When implementing a ChatSessionItemController, ensure metadata is set in all code paths that create ChatSessionItem objects:
newChatSessionItemHandler— Called when the user sends the first message from the welcome chat. Resolve the workspace folder fromcontext.inputState(look for the folder option group) and setitem.metadata.forkHandler— Called when forking an existing session. Copy or resolve metadata from the parent session.Refresh/sync handlers — When rebuilding items from persisted sessions (e.g.,
_refreshItems), set metadata from the session's stored state.
Missing metadata in any of these paths causes the session to appear under "Unknown" in the sessions list.
Context Keys
Session Provider Context Keys (managed by SessionsManagementService)
| Context Key | Type | Description |
|---|---|---|
isNewChatSession | boolean | true when viewing the new-session form (no established session selected) |
activeSessionProviderId | string | Provider ID of the active session (e.g., 'default-copilot') |
activeSessionType | string | Session type of the active session (e.g., 'copilot-cli', 'copilot-cloud') |
isActiveSessionBackgroundProvider | boolean | Whether the active session uses the background agent provider |
isActiveSessionArchived | boolean | Whether the active session is archived (marked as done) |
activeSessionSupportsMultiChat | boolean | Whether the active session's provider supports multiple chats per session |
Other Session Context Keys (defined in common/contextkeys.ts)
| Context Key | Type | Description |
|---|---|---|
activeSessionHasGitRepository | boolean | Whether the active session has an associated git repository |
chatSessionProviderId | string | The provider ID of a session in context menu overlays |
Adding a New Provider
To add a new sessions provider:
Implement
ISessionsProviderwith a uniqueid, supportedsessionTypes,capabilities, andbrowseActionsCreate session data classes implementing
ISessionwith observable properties for the new session typeRegister via a workbench contribution at
WorkbenchPhase.AfterRestored:Session IDs must use the format
${providerId}:${localId}so the management service can route actions correctlyFire
onDidChangeSessionswhen sessions are added, removed, or updatedFire
onDidReplaceSessionwhen a temporary session is atomically replaced by a committed oneThe provider's
browseActionswill automatically appear in the workspace pickerThe provider's
sessionTypeswill be available for session type selection