# マルチテナントとサーバーの展開

マルチユーザー サーバー モードとは、複数の人間、テナント、ワークスペース、または統合アカウントにサービスを提供するバックエンド コードから Copilot SDK を実行することを意味します。 このセットアップでは、アプリケーションは要求のルーティングと承認を所有しますが、SDK とランタイムはセッションごとの状態、セッションごとの認証、および明示的なツール登録を提供するため、あるユーザーのセッションが別のユーザーのツールまたは ID を継承しません。

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

**次の場合に最適です。** 同時ユーザーを処理する SaaS 製品、パートナー統合、内部プラットフォーム、バックエンド サービス。

## このガイドは、次の場合に使用します。

ビルドする場合は、次のガイドを使用します。

* Copilotを利用するエージェントを埋め込むマルチユーザー SaaS 製品
* Copilot Studio または Fabric スタイルのパターンのような、パートナー連携のバックエンド
* 同時実行ユーザー、ワークスペース、テナント、または要求を処理するすべてのサーバー
* 複数の SDK クライアントが 1 つの Copilot ランタイム プロセスに接続する共有ランタイム

このガイドは [スケーリングとマルチテナント](/ja/copilot/how-tos/copilot-sdk/setup/scaling) の姉妹です。 トポロジ、負荷分散、およびストレージ パターンについては、このガイドを使用してください。 SDK レベルのオプションとランタイム分離の選択肢については、このガイドを使用してください。

## 主要な SDK オプション

| オプション                           | 次の目的に使用します:                               | メモ                              |
| ------------------------------- | ----------------------------------------- | ------------------------------- |
| `mode: "empty"`                 | アンビエント OS ツールと CLI の既定値を無効にする             | マルチユーザーまたは共有のシナリオに必要です。         |
| `sessionIdleTimeoutSeconds`     | アイドル状態のセッションのクリーンアップ                      | 実行時間の長いプロセスのサーバー側タイムアウトを設定します。  |
| `baseDirectory`                 | ランタイム インスタンスごとの `COPILOT_HOME` の分離        | 既存のランタイムに接続するときに無視されます。         |
| `sessionFs`                     | ローカル ディスクからセッション ファイル システム ストレージをルーティングする | セッションごとのファイルシステム プロバイダーとペアにします。 |
| `RuntimeConnection.forUri(url)` | 既に実行されているランタイムの共有                         | 言語名は異なります。以下のサンプルを参照してください。     |
| セッションごと `gitHubToken`           | 認証のスコープをリクエスト元のユーザーに限定する                  | これは、単一の共有ユーザー トークンよりも優先されます。    |

### `mode: "empty"`

`mode: "empty"` は、オプションの Copilot CLI の動作を既定で無効にします。 マルチユーザー サーバー モードでは、セッションがアクセスできるツール、MCP サーバー、スキル、ワークスペース パスをアプリケーションが明示的に決定する必要があるため、これは安全なベースラインです。

共有サーバーには既定の `mode: "copilot-cli"` を使用しないでください。 このモードは、CLI に似たコーディング エージェントを対象としており、アンビエント ホスト ファイルシステムの機能を公開できます。

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";

// baseDirectory and sessionIdleTimeoutSeconds apply when the SDK spawns the
// runtime. With RuntimeConnection.forUri(...) configure COPILOT_HOME and the
// idle timeout on the runtime process itself.
const client = new CopilotClient({
    mode: "empty",
    connection: RuntimeConnection.forUri(process.env.COPILOT_RUNTIME_URL!),
});

const session = await client.createSession({
    sessionId: `user-${user.id}-${crypto.randomUUID()}`,
    model: "gpt-5.4",
    availableTools: ["custom:lookupOrder", "custom:createTicket"],
    gitHubToken: user.githubToken,
});
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot import CopilotClient, RuntimeConnection
from copilot.session import 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,
)
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
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,
})
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```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,
});
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
// setCopilotHome and setSessionIdleTimeoutSeconds are ignored when
// setCliUrl is used; configure those on the runtime process instead.
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();
```

</div>

<div class="ghd-codetab" data-lang="rust" data-label="Rust"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Rust</div>

```rust
use std::path::PathBuf;
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-{}-{request_id}", user.id))
        .with_model("gpt-5.4")
        .with_available_tools(["custom:lookupOrder", "custom:createTicket"])
        .with_github_token(user.github_token),
).await?;
```

</div>

</div>

### `sessionIdleTimeoutSeconds`

非アクティブなセッションが自動的にクリーンアップされるように、サーバーに `sessionIdleTimeoutSeconds` を設定します。 これにより、実行時間の長いプロセスでのゾンビ セッションが防止され、メモリとファイル システムの負荷が軽減されます。

| Language   | パブリック オプション                              |
| ---------- | ---------------------------------------- |
| TypeScript | `sessionIdleTimeoutSeconds`              |
| Python     | `session_idle_timeout_seconds`           |
| Go         | `SessionIdleTimeoutSeconds`              |
| .NET       | `SessionIdleTimeoutSeconds`              |
| Java       | `setSessionIdleTimeoutSeconds(...)`      |
| Rust       | `with_session_idle_timeout_seconds(...)` |

製品の会話の有効期間に一致する値を使用します。 チャット バックエンドの場合、通常は 15 分から 30 分が適しています。 ワークフロー エージェントの場合は、ワークフローの完了時にタイムアウト時間が長くなり、明示的に削除されます。

### `baseDirectory`

`baseDirectory` は、ランタイム インスタンスの `COPILOT_HOME` を設定します。 これを使用して、プロセス、ポッド、ワーカー、またはテナントの境界ごとにランタイム状態、資格情報、セッション データを分離します。

```typescript
const client = new CopilotClient({
    mode: "empty",
    baseDirectory: `/var/lib/my-app/copilot/runtime-${process.env.HOSTNAME}`,
    sessionIdleTimeoutSeconds: 900,
});
```

ランタイムは、`COPILOT_HOME`を含め、構成された`session-state/{sessionId}`の下にセッション状態を格納します。 アプリで複数のランタイム インスタンスを実行する場合は、意図的に共有ストレージを使用しない限り、各インスタンスに個別のディレクトリを指定します。

SDK が `RuntimeConnection.forUri(url)` を使用してすでに実行中のランタイムに接続する場合、`baseDirectory` は SDK クライアントによって無視されます。 代わりに、ランタイム プロセスで `COPILOT_HOME` を構成します。

### `sessionFs`

`sessionFs` はカスタム セッション ファイルシステム プロバイダーを登録するため、セッション スコープのファイル I/O をランタイムのローカル ディスクではなくアプリケーション ストレージ経由でルーティングできます。 ローカル ディスクが一時的な場合、セッション状態がオブジェクト ストレージに存在する必要がある場合、またはプラットフォームでテナント対応のストレージ パスを適用する必要がある場合に使用します。

```typescript
const client = new CopilotClient({
    mode: "empty",
    sessionFs: {
        initialCwd: "/workspace",
        sessionStatePath: "/session-state",
        conventions: "posix",
    },
});
```

プロバイダー コールバックを公開する言語の場合は、クライアント レベルで `sessionFs` を構成し、セッションの作成時または再開時にセッションごとのファイルシステム ハンドラーを提供します。 永続化の概念とストレージのトレードオフについては、 [セッションの再開と永続化](/ja/copilot/how-tos/copilot-sdk/features/session-persistence) を参照してください。

検証済みのパブリック SDK サーフェス:

| Language                                | クライアント レベルの構成          | セッションごとのプロバイダー                  |
| --------------------------------------- | ---------------------- | ------------------------------- |
| TypeScript                              | `sessionFs`            |                                 |
| `createSessionFsAdapter` / プロバイダーコールバック |                        |                                 |
| Python                                  | `session_fs`           | `create_session_fs_handler`     |
| Go                                      | `SessionFS`            | `CreateSessionFSProvider`       |
| .NET                                    | `SessionFs`            | `CreateSessionFsProvider`       |
| Rust                                    | `with_session_fs(...)` | `with_session_fs_provider(...)` |

Javaは現在、検証済みのパブリック `sessionFs` オプションを公開していないため、このガイドでは Java `sessionFs` サンプルは表示されません。

### `RuntimeConnection.forUri(url)`

複数の SDK クライアントが既に実行されている 1 つのランタイムを共有する必要がある場合は、外部ランタイム接続を使用します。 これは、ランタイム プロセスが要求ハンドラーとは別に管理されるバックエンド サービスで一般的です。

| Language   | 外部ランタイム接続                                              |
| ---------- | ------------------------------------------------------ |
| TypeScript | `RuntimeConnection.forUri(url)`                        |
| Python     | `RuntimeConnection.for_uri(url)`                       |
| Go         | `copilot.URIConnection{URL: url}`                      |
| .NET       | `RuntimeConnection.ForUri(url)`                        |
| Java       | `setCliUrl(url)`                                       |
| Rust       | `Transport::External { host, port, connection_token }` |

外部ランタイムは、独自のプロセス レベルの認証とストレージを管理します。 ユーザー固有の認証が必要な場合は、セッションごとのトークンを `createSession` または `resumeSession` に渡します。

### セッションごと `gitHubToken`

各セッションで `gitHubToken` を設定して、GitHub 認証の対象を要求元のユーザーに限定します。 これは、ランタイム プロセスを認証するクライアント レベルのトークンとは異なります。

```typescript
const session = await client.createSession({
    sessionId: `user-${user.id}-support`,
    model: "gpt-5.4",
    availableTools: ["custom:*"],
    gitHubToken: user.githubToken,
});
```

コンテンツの除外、モデル ルーティング、クォータ チェック、およびユーザー固有のCopilot アクセスには、セッションごとのトークンを使用します。 製品でサービス アカウント セマンティクスを意図的に使用しない限り、ユーザー間で 1 つのサービス トークンを共有しないようにします。

## 統合ID

ブランド化されたエージェントを構築するパートナーは、Mission Control 要求の統合 ID を設定できます。 ランタイムは、`GITHUB_COPILOT_INTEGRATION_ID` を読み取り、すべての Mission Control 要求で `Copilot-Integration-Id` HTTP ヘッダーとしてスタンプします。

```bash
GITHUB_COPILOT_INTEGRATION_ID=my-product-agent copilot --headless --port 4321
```

既定の統合 ID は `copilot-developer-cli`。 属性やルーティングには、 `my-product-agent` などの安定した値を使用します。 統合 ID は現在、環境変数によってのみ構成されています。これは、ファースト クラスの SDK オプションではありません。

SDK がランタイムを生成する場合は、クライアント環境オプションを使用して環境変数を渡します。
`RuntimeConnection.forUri(url)`で接続する場合は、ランタイム プロセス自体に環境変数を設定します。

## セッションレベルの分離保証

セッション レベルの分離とは、ランタイムがグローバル共有状態ではなく、ユーザー固有のモデルと状態の情報をセッションのスコープに保持することを意味します。

| Surface                                               | 分離動作                                         |
| ----------------------------------------------------- | -------------------------------------------- |
| モデル リスト キャッシュ                                         | セッションごと。 モデル検索では、セッションのモデル リスト キャッシュが使用されます。 |
| セッションの状態                                              |                                              |
| `COPILOT_HOME/session-state/{sessionId}`配下のセッションIDごと。 |                                              |
| GitHub ID 情報                                          |                                              |
| `gitHubToken` がセッションに設定されている場合は、セッションごと。              |                                              |
| Tools                                                 |                                              |
| `mode: "empty"`では明示的、`mode: "copilot-cli"`では暗黙的です。    |                                              |
| ホスト ファイルシステム                                          | ホスト ツールが使用可能な場合は、ランタイム プロセスによって共有されます。       |

`mode: "empty"` は、共有ランタイム パターンを実行可能にする機能です。アプリケーションが登録または許可しない限り、アンビエント OS ツールは公開されません。
`mode: "copilot-cli"`では、OS ファイルシステム アクセスはホスト プロセスを通じて共有されるため、マルチユーザー サーバー モードにはそのモードを使用しないでください。

セッションの状態は、`COPILOT_HOME/session-state/{sessionId}`経由でルーティングしない限り、`sessionFs`に格納されます。 独自のテナントまたはユーザーの境界を含む一意のセッション ID を使用し、セッションを再開または削除する前にアクセス制御を適用します。

## パターンの比較

| Pattern                           | 次の場合に使用します。                                                         | Trade-offs                                                                                                         |
| --------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| パターン 1: ユーザーごとに分離された CLI          | ユーザーごとに最も強力な分離境界または個別のプロセス資格情報が必要です。                                | 強力な分離。より高いリソース コスト。 「[スケーリングとマルチテナント](/ja/copilot/how-tos/copilot-sdk/setup/scaling)」を参照してください。                    |
| パターン 2: CLI との共有: `mode: "empty"` | アプリがツール、認証、およびセッション ID を制御している間、1 つのランタイムで多くのユーザーにサービスを提供する必要があります。 | 効率的;には、慎重なツール登録、セッションごとのトークン、およびアプリケーション レベルのアクセス チェックが必要です。                                                       |
| パターン 3: ハイブリッド                    | コンピューティング負荷の高い作業をクラウド セッションにルーティングし、軽い作業をローカル セッションにルーティングします。      | 柔軟;には、ワークロード ルーティングとポリシー処理が必要です。 「[クラウド セッション](/ja/copilot/how-tos/copilot-sdk/features/cloud-sessions)」を参照してください。 |

### パターン 2: CLI との共有: `mode: "empty"`

このパターンでは、すべてのユーザーがバックエンド経由で 1 つのランタイム プールに接続します。 アプリケーションはユーザー認証を実行し、セッション ID を選択し、セッションでユーザーのGitHub トークンを渡し、明示的なツール許可リストを提供します。

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-multi-tenancy-diagram-0.png)

次の規則を使用します。

* 常にクライアントまたはランタイムを `mode: "empty"`で起動します。
* 一意のセッション ID を使用し、所有権メタデータをアプリケーション データベースに格納します。
* セッション ID を参照する `resumeSession`、 `deleteSession`、または UI アクションの前に所有権を確認します。
* 要求をユーザーとして実行する必要がある場合は、セッションごとに `gitHubToken` を渡します。
* セッションに必要なツールのみを登録し、 `custom:*` や `mcp:search_docs`などのソース修飾許可リストを優先します。
* `sessionIdleTimeoutSeconds`設定し、完了したワークフロー セッションを明示的に削除します。

## よくある落とし穴

* `mode: "empty"`を忘れること。 既定の `copilot-cli` モードでは、CLI スタイルの動作が公開され、アンビエント ツールを使用してホスト ファイル システムが公開される場合があります。
* `sessionIdleTimeoutSeconds`を設定していません。 実行時間の長いサーバーは、クリーンアップしないとアイドル 状態のセッションを蓄積する可能性があります。
* セッションごとのトークンを渡すのではなく、ユーザー間で 1 つの `gitHubToken` を共有します。
* バックエンドの所有権を確認せずに、クライアントが提供するセッション ID を信頼する。
* 既存のランタイムに接続するクライアントで `baseDirectory` を設定し、ランタイム ストレージを移動することを想定しています。 代わりにランタイム プロセスを構成します。
* 各ツールがユーザーに適しているかどうかを確認することなく、 `builtin:*` などの広範なツール パターンを許可します。

## 参照

* [スケーリングとマルチテナント](/ja/copilot/how-tos/copilot-sdk/setup/scaling): デプロイ トポロジ、ストレージ パターン、分離の比較
* [バックエンド サービスのセットアップ](/ja/copilot/how-tos/copilot-sdk/setup/backend-services): ヘッドレス サーバー モードでランタイムを実行する
* [BYOK (独自のキーを持ち込む)](/ja/copilot/how-tos/copilot-sdk/auth/byok): 自分のモデルプロバイダーの認証情報を使用する
* [クラウド セッション](/ja/copilot/how-tos/copilot-sdk/features/cloud-sessions): 選択した作業をクラウド セッションにルーティングする
* [セッションの再開と永続化](/ja/copilot/how-tos/copilot-sdk/features/session-persistence): 再開可能なセッション状態の管理
* [機能](/ja/copilot/how-tos/copilot-sdk/features): ツール、イベント、フック、および高度な SDK 機能