How to Set Up Multi-Tenant Deployments with Session Isolation Patterns in the Copilot SDK
To deploy the Copilot SDK in a multi-tenant environment, configure the client with mode: "empty", generate unique session IDs that embed tenant identifiers, and provide per-session GitHub tokens to ensure complete isolation of state, authentication, and filesystem access between users.
The Copilot SDK enables SaaS platforms to embed AI assistance for multiple tenants from a single deployment. Implementing multi-tenant deployments with session isolation patterns in the Copilot SDK requires careful configuration of runtime modes, session identifiers, and authentication scopes to prevent cross-tenant data leakage. This guide covers the architectural mechanisms and implementation patterns defined in the GitHub Copilot SDK source code.
Architectural Isolation Mechanisms
The SDK provides specific runtime options that guarantee per-session isolation of state, tools, and filesystem access. These mechanisms are implemented in nodejs/src/client.ts and documented in /docs/setup/multi-tenancy.md.
Runtime Mode Configuration
Set mode: "empty" when initializing the client to disable the default CLI-style ambient tools. This prevents the host filesystem from being shared across sessions, which is required for multi-tenant security. According to the source documentation, the default copilot-cli mode exposes the host filesystem and must not be used in shared environments.
Session Identification Strategy
Generate unique session IDs that embed tenant or user identifiers to ensure state isolation. The SDK stores session data under COPILOT_HOME/session-state/{sessionId}, scoping model list caches, conversation history, and tool registrations to individual users. Use a format like user-${user.id}-${crypto.randomUUID()} to prevent collisions and ensure traceability.
Per-Session Authentication and Tools
Pass a unique gitHubToken for each session to scope all GitHub-related operations—including repository access and quota checks—to the individual user. Additionally, configure availableTools as an explicit allow-list (e.g., ["custom:lookupOrder", "custom:createTicket"]) rather than using wildcards like builtin:* to prevent unauthorized tool access across tenants.
Filesystem and State Management
Configure baseDirectory to isolate COPILOT_HOME per runtime instance, using paths like /var/lib/my-app/copilot/runtime-${process.env.HOSTNAME}. For tenant-aware storage, implement the sessionFs configuration with provider callbacks to route file I/O to object storage or per-tenant disks instead of the runtime's local filesystem. Set sessionIdleTimeoutSeconds: 900 (15 minutes) to automatically clean up idle sessions and prevent zombie state accumulation.
Deployment Patterns for Multi-Tenancy
When architecting your deployment, choose from three isolation patterns based on your security and resource requirements:
Isolated CLI per user provides the strongest isolation by running separate process credentials for each tenant. This pattern prevents any cross-tenant contamination but incurs higher resource costs and complexity.
Shared CLI with mode: "empty" allows one runtime to serve many tenants while your application layer controls tools, authentication, and session IDs. This is the most efficient pattern for high-density SaaS deployments but requires strict validation of per-session tokens and session ownership.
Hybrid combines cloud sessions for heavy computational work with local sessions for lightweight operations. This offers flexibility but requires custom routing logic to direct requests to the appropriate runtime environment.
Implementation Examples by Language
The following examples demonstrate the core pattern across supported languages: initialize the client with mode: "empty", provide a unique sessionId, pass a per-session gitHubToken, and configure isolation settings.
TypeScript
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";
const client = new CopilotClient({
mode: "empty", // isolate ambient tools
connection: RuntimeConnection.forUri(
process.env.COPILOT_RUNTIME_URL!
),
sessionIdleTimeoutSeconds: 900, // 15 min idle timeout
baseDirectory: `/var/lib/my-app/copilot/runtime-${process.env.HOSTNAME}`,
});
const session = await client.createSession({
sessionId: `user-${user.id}-${crypto.randomUUID()}`,
model: "gpt-5.4",
availableTools: ["custom:lookupOrder", "custom:createTicket"],
gitHubToken: user.githubToken, // per-session auth
});
Python
from copilot import CopilotClient, RuntimeConnection, PermissionHandler
client = CopilotClient(
mode="empty",
base_directory=f"/var/lib/my-app/copilot/{runtime_instance_id}",
session_idle_timeout_seconds=900,
connection=RuntimeConnection.for_uri(runtime_url),
)
await client.start()
session = await client.create_session(
session_id=f"user-{user.id}-{request_id}",
model="gpt-5.4",
available_tools=["custom:lookupOrder", "custom:createTicket"],
github_token=user.github_token,
on_permission_request=PermissionHandler.approve_all,
)
Go
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
)
client := copilot.NewClient(&copilot.ClientOptions{
Mode: copilot.ModeEmpty,
BaseDirectory: fmt.Sprintf("/var/lib/my-app/copilot/%s", runtimeInstanceID),
SessionIdleTimeoutSeconds: 900,
Connection: copilot.URIConnection{URL: runtimeURL},
})
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
SessionID: fmt.Sprintf("user-%s-%s", user.ID, requestID),
Model: "gpt-5.4",
AvailableTools: []string{"custom:lookupOrder", "custom:createTicket"},
GitHubToken: user.GitHubToken,
})
.NET (C#)
var client = new CopilotClient(new CopilotClientOptions {
Mode = CopilotClientMode.Empty,
BaseDirectory = $"/var/lib/my-app/copilot/{runtimeInstanceId}",
SessionIdleTimeoutSeconds = 900,
Connection = RuntimeConnection.ForUri(runtimeUrl),
});
await using var session = await client.CreateSessionAsync(new SessionConfig {
SessionId = $"user-{user.Id}-{requestId}",
Model = "gpt-5.4",
AvailableTools = ["custom:lookupOrder", "custom:createTicket"],
GitHubToken = user.GitHubToken,
});
Java
var client = new CopilotClient(new CopilotClientOptions()
.setMode(CopilotClientMode.EMPTY)
.setCliUrl(runtimeUrl)
);
var session = client.createSession(new SessionConfig()
.setSessionId("user-" + user.id() + "-" + requestId)
.setModel("gpt-5.4")
.setAvailableTools(List.of("custom:lookupOrder", "custom:createTicket"))
.setGitHubToken(user.gitHubToken())
).get();
Rust
use github_copilot_sdk::{Client, ClientOptions, Transport};
use github_copilot_sdk::mode::ClientMode;
use github_copilot_sdk::types::SessionConfig;
let client = Client::start(
ClientOptions::new()
.with_mode(ClientMode::Empty)
.with_base_directory(PathBuf::from(
format!("/var/lib/my-app/copilot/{}", runtime_instance_id)
))
.with_session_idle_timeout_seconds(900)
.with_transport(Transport::External {
host: runtime_host.to_string(),
port: runtime_port,
connection_token: None,
})
).await?;
let session = client.create_session(
SessionConfig::default()
.with_session_id(format!("user-{}-{}", user.id, request_id))
.with_model("gpt-5.4")
.with_available_tools(["custom:lookupOrder", "custom:createTicket"])
.with_github_token(user.github_token),
).await?;
Critical Configuration Pitfalls
Avoid these common mistakes when implementing session isolation:
- Never deploy with the default
copilot-climode in multi-tenant environments, as this exposes the host filesystem to all sessions. - Always validate ownership of session IDs before resuming or deleting sessions to prevent unauthorized access to tenant data.
- Do not use shared service tokens for
gitHubToken; always scope tokens to the individual user session. - Avoid overly broad tool patterns like
builtin:*inavailableTools; explicitly list only the tools required for each tenant context. - Ensure
sessionIdleTimeoutSecondsis set to prevent resource exhaustion from accumulated idle sessions.
Summary
- Configure
mode: "empty"innodejs/src/client.tsto disable ambient tools and filesystem sharing - Generate unique
sessionIdvalues containing tenant identifiers to isolate state inCOPILOT_HOME/session-state/{sessionId} - Provide per-session
gitHubTokenvalues to scope GitHub operations and quota checks to individual users - Set
sessionIdleTimeoutSeconds: 900to automatically prune idle sessions and prevent resource leaks - Implement
sessionFsandbaseDirectoryconfigurations to enforce tenant-aware filesystem isolation
Frequently Asked Questions
What runtime mode is required for multi-tenant Copilot SDK deployments?
You must set mode: "empty" when initializing the client, as implemented in nodejs/src/client.ts and documented in /docs/setup/multi-tenancy.md#mode-empty. This disables the default CLI-style ambient tools and prevents the host filesystem from being shared across sessions, which is critical for preventing cross-tenant data leakage.
How should session IDs be structured to ensure tenant isolation?
Generate unique session IDs that embed both the tenant identifier and a random UUID, such as user-${user.id}-${crypto.randomUUID()}. The SDK stores session state under COPILOT_HOME/session-state/{sessionId}, ensuring that conversation history, model caches, and tool registrations remain scoped to individual users as defined in nodejs/src/generated/session-events.ts.
Can multiple tenants share the same GitHub token in a Copilot SDK deployment?
No, you must provide a per-session gitHubToken scoped to the individual user rather than using a shared service token. This ensures that GitHub operations—including repository access and quota checks—are isolated to the specific tenant, as documented in /docs/setup/multi-tenancy.md#per-session-githubtoken and enforced in nodejs/src/generated/rpc.ts.
How do you handle filesystem isolation when tenants require persistent storage?
Configure the sessionFs option with tenant-aware provider callbacks to route file I/O to object storage or per-tenant disks instead of the runtime's local disk. Combine this with baseDirectory set to a unique path per runtime instance to ensure complete filesystem isolation between tenants, as described in /docs/setup/multi-tenancy.md#sessionfs.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →