# How to Set Up Multi-Tenant Deployments with Session Isolation Patterns in the Copilot SDK

> Learn how to set up multi-tenant deployments with session isolation patterns in the Copilot SDK. Ensure secure, isolated access for each tenant with unique session IDs and GitHub tokens.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts) and documented in [`/docs/setup/multi-tenancy.md`](https://github.com/github/copilot-sdk/blob/main//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

```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

```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

```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#)

```csharp
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

```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

```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-cli` mode 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:*` in `availableTools`; explicitly list only the tools required for each tenant context.
- Ensure `sessionIdleTimeoutSeconds` is set to prevent resource exhaustion from accumulated idle sessions.

## Summary

- Configure `mode: "empty"` in [`nodejs/src/client.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts) to disable ambient tools and filesystem sharing
- Generate unique `sessionId` values containing tenant identifiers to isolate state in `COPILOT_HOME/session-state/{sessionId}`
- Provide per-session `gitHubToken` values to scope GitHub operations and quota checks to individual users
- Set `sessionIdleTimeoutSeconds: 900` to automatically prune idle sessions and prevent resource leaks
- Implement `sessionFs` and `baseDirectory` configurations 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`](https://github.com/github/copilot-sdk/blob/main/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`](https://github.com/github/copilot-sdk/blob/main/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`](https://github.com/github/copilot-sdk/blob/main/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`.