Thanks to visit codestin.com
Credit goes to docs.github.com

Skip to main content

다중 테넌트 및 서버 배포

상태, 인증 및 도구에 대한 세션별 격리를 사용하여 다중 사용자 서버 배포에서 Copilot SDK를 실행합니다.

최적 대상: 동시 사용자를 처리하는 SaaS 제품, 파트너 통합, 내부 플랫폼 및 백 엔드 서비스.

이 가이드를 사용하는 경우

빌드할 때 다음 가이드를 사용합니다.

  • Copilot 지원 에이전트를 포함하는 다중 사용자 SaaS 제품
  • Copilot Studio 또는 Fabric 스타일 패턴과 같은 파트너 통합을 위한 백 엔드
  • 동시 사용자, 작업 영역, 테넌트 또는 요청을 처리하는 모든 서버
  • 여러 SDK 클라이언트가 하나의 Copilot 런타임 프로세스에 연결하는 공유 런타임

이 가이드는 확장성 및 멀티 테넌시의 자매입니다. 토폴로지, 부하 분산 및 스토리지 패턴에 이 가이드를 사용합니다. SDK 수준 옵션 및 런타임 격리 선택에 대해서는 이 가이드를 사용합니다.

주요 SDK 옵션

옵션다음 용도로 사용Notes
mode: "empty"앰비언트 OS 도구 및 CLI 기본값을 사용하지 않도록 설정다중 사용자 또는 공유 시나리오에 필요합니다.
sessionIdleTimeoutSeconds유휴 세션 정리장기 실행 프로세스에 대한 서버 쪽 시간 제한을 설정합니다.
baseDirectory런타임 인스턴스별로 COPILOT_HOME 격리기존 런타임에 연결할 때 무시됩니다.
sessionFs세션 파일 시스템 스토리지를 로컬 디스크에서 다른 저장소로 라우팅세션별 파일 시스템 공급자와 페어링합니다.
RuntimeConnection.forUri(url)이미 실행 중인 런타임 공유언어 이름은 다양합니다. 아래 샘플을 참조하세요.
세션당 gitHubToken요청하는 사용자에 대한 인증 범위 지정단일 공유 사용자 토큰보다 이 방법을 선호합니다.

mode: "empty"

mode: "empty" 기본적으로 선택적 Copilot CLI 동작을 사용하지 않도록 설정합니다. 다중 사용자 서버 모드에서는 애플리케이션이 세션에서 액세스할 수 있는 도구, MCP 서버, 기술 및 작업 영역 경로를 명시적으로 결정해야 하기 때문에 안전한 기준입니다.

공유 서버에는 기본값 mode: "copilot-cli" 을 사용하지 마세요. 이 모드는 CLI와 유사한 코딩 에이전트를 위한 것이며 앰비언트 호스트 파일 시스템 기능을 노출할 수 있습니다.

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-4.1",
    availableTools: ["custom:lookupOrder", "custom:createTicket"],
    gitHubToken: user.githubToken,
});
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-4.1",
    available_tools=["custom:lookupOrder", "custom:createTicket"],
    github_token=user.github_token,
    on_permission_request=PermissionHandler.approve_all,
)
Go
package main

import (
    "context"
    "fmt"

    copilot "github.com/github/copilot-sdk/go"
)

type appUser struct {
    ID          string
    GitHubToken string
}

func main() {
    ctx := context.Background()
    runtimeInstanceID := "instance-1"
    runtimeURL := "http://127.0.0.1:8080"
    requestID := "req-1"
    user := appUser{ID: "alice", GitHubToken: "YOUR_GITHUB_TOKEN"}

    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-4.1",
        AvailableTools: []string{"custom:lookupOrder", "custom:createTicket"},
        GitHubToken:    user.GitHubToken,
    })
    _ = session
    _ = err
}
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-4.1",
    AvailableTools: []string{"custom:lookupOrder", "custom:createTicket"},
    GitHubToken:    user.GitHubToken,
})
.NET
using GitHub.Copilot;

var runtimeInstanceId = "instance-1";
var runtimeUrl = "http://127.0.0.1:8080";
var requestId = "req-1";
var user = new { Id = "alice", GitHubToken = "YOUR_GITHUB_TOKEN" };

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-4.1",
    AvailableTools = ["custom:lookupOrder", "custom:createTicket"],
    GitHubToken = user.GitHubToken,
});
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-4.1",
    AvailableTools = ["custom:lookupOrder", "custom:createTicket"],
    GitHubToken = user.GitHubToken,
});
Java
import java.util.List;
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.CopilotClientOptions;
import com.github.copilot.rpc.CopilotClientMode;
import com.github.copilot.rpc.SessionConfig;

public class MultiTenancyExample {
    record User(String id, String gitHubToken) {}

    public static void main(String[] args) throws Exception {
        String runtimeUrl = "http://localhost:4321";
        String requestId = "req-1";
        User user = new User("u1", "ghu_token");

        // 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-4.1")
            .setAvailableTools(List.of("custom:lookupOrder", "custom:createTicket"))
            .setGitHubToken(user.gitHubToken())
        ).get();
    }
}
// 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-4.1")
    .setAvailableTools(List.of("custom:lookupOrder", "custom:createTicket"))
    .setGitHubToken(user.gitHubToken())
).get();
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-4.1")
        .with_available_tools(["custom:lookupOrder", "custom:createTicket"])
        .with_github_token(user.github_token),
).await?;

sessionIdleTimeoutSeconds

비활성 세션이 자동으로 정리되도록 서버에서 설정합니다 sessionIdleTimeoutSeconds . 이렇게 하면 장기 실행 프로세스에서 좀비 세션을 방지하고 메모리 및 파일 시스템 압력을 줄일 수 있습니다.

언어공용 옵션
TypeScriptsessionIdleTimeoutSeconds
Pythonsession_idle_timeout_seconds
GoSessionIdleTimeoutSeconds
.NETSessionIdleTimeoutSeconds
javasetSessionIdleTimeoutSeconds(...)
러스트with_session_idle_timeout_seconds(...)

제품의 대화 수명과 일치하는 값을 사용합니다. 채팅 백 엔드의 경우 15~30분은 일반적으로 좋은 시작점입니다. 워크플로 에이전트의 경우 워크플로가 완료되면 더 긴 시간 제한 및 명시적 삭제를 사용합니다.

baseDirectory

baseDirectory은 런타임 인스턴스에 대해 COPILOT_HOME을 설정합니다. 프로세스, Pod, 작업자 또는 테넌트 경계당 런타임 상태, 자격 증명 및 세션 데이터를 격리하는 데 사용합니다.

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가 런타임의 로컬 디스크 대신 애플리케이션 스토리지를 통해 라우팅될 수 있습니다. 로컬 디스크가 사용 후 삭제되거나, 세션 상태가 개체 스토리지에 있어야 하는 경우 또는 플랫폼이 테넌트 인식 스토리지 경로를 적용해야 하는 경우에 사용합니다.

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

공급자 콜백을 노출하는 언어의 경우 클라이언트 수준에서 구성 sessionFs 하고 세션을 만들거나 다시 열 때 세션별 파일 시스템 처리기를 제공합니다. 지속성 개념 및 스토리지 장차는 세션 다시 시작 및 지속성 을 참조하세요.

확인된 공용 SDK 표면:

언어클라이언트 수준 구성세션별 제공자
TypeScriptsessionFs
createSessionFsAdapter / 공급자 콜백
Pythonsession_fscreate_session_fs_handler
GoSessionFSCreateSessionFSProvider
.NETSessionFsCreateSessionFsProvider
러스트with_session_fs(...)with_session_fs_provider(...)

Java 현재 확인된 공용 sessionFs 옵션을 노출하지 않으므로 이 가이드에서는 Java sessionFs 샘플을 표시하지 않습니다.

RuntimeConnection.forUri(url)

여러 SDK 클라이언트가 이미 실행 중인 런타임을 공유해야 하는 경우 외부 런타임 연결을 사용합니다. 이는 런타임 프로세스가 요청 처리기와 별도로 관리되는 백 엔드 서비스에서 일반적입니다.

언어외부 런타임 연결
TypeScriptRuntimeConnection.forUri(url)
PythonRuntimeConnection.for_uri(url)
Gocopilot.URIConnection{URL: url}
.NETRuntimeConnection.ForUri(url)
javasetCliUrl(url)
러스트Transport::External { host, port, connection_token }

외부 런타임은 자체 프로세스 수준 인증 및 스토리지를 관리합니다. 사용자별 인증이 필요한 경우 createSession 또는 resumeSession에 세션별 토큰을 전달합니다.

세션당 gitHubToken

각 세션에서 gitHubToken 설정하여 요청 사용자에게 GitHub 인증 범위를 지정합니다. 이는 런타임 프로세스를 인증하는 클라이언트 수준 토큰과 다릅니다.

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

콘텐츠 제외, 모델 라우팅, 할당량 검사 및 사용자별 Copilot 액세스에 세션별 토큰을 사용합니다. 제품에서 의도적으로 서비스 계정 의미 체계를 사용하지 않는 한 사용자 간에 하나의 서비스 토큰을 공유하지 않습니다.

통합 ID

브랜드 에이전트를 빌드하는 파트너는 Mission Control 요청에 대한 통합 ID를 설정할 수 있습니다. 런타임은 GITHUB_COPILOT_INTEGRATION_ID를 읽고 모든 Mission Control 요청의 Copilot-Integration-Id HTTP 헤더에 이를 설정합니다.

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

기본 통합 ID는 .입니다 copilot-developer-cli. 특성 및 라우팅과 같은 my-product-agent 안정적인 값을 사용합니다. 통합 ID는 현재 환경 변수에 의해서만 구성됩니다. 일류 SDK 옵션이 아닙니다.

SDK가 런타임을 생성하는 경우 클라이언트 환경 옵션을 통해 환경 변수를 전달합니다. RuntimeConnection.forUri(url)로 연결하는 경우 런타임 프로세스 자체에 환경 변수를 설정합니다.

세션 수준 격리 보장

세션 수준 격리는 런타임이 전역 공유 상태가 아닌 세션으로 범위가 지정된 사용자별 모델 및 상태 정보를 유지한다는 것을 의미합니다.

표면격리 동작
모델 목록 캐시세션당. 모델 조회는 세션의 모델 목록 캐시를 사용합니다.
세션 상태아래 COPILOT_HOME/session-state/{sessionId}의 세션 ID당
GitHub 신원
gitHubToken이(가) 세션에 설정된 경우 세션당.
도구명시적 in mode: "empty"; 암시적 in mode: "copilot-cli".
호스트 파일 시스템호스트 도구를 사용할 수 있는 경우 런타임 프로세스에서 공유합니다.

mode: "empty" 는 공유 런타임 패턴을 실행 가능하게 만드는 요소입니다. 애플리케이션이 등록하거나 허용하지 않는 한 앰비언트 OS 도구가 노출되지 않습니다. OS mode: "copilot-cli"파일 시스템 액세스는 호스트 프로세스를 통해 공유되므로 다중 사용자 서버 모드에 해당 모드를 사용하지 마세요.

세션 상태는 sessionFs를 통해 라우팅하지 않는 한 COPILOT_HOME/session-state/{sessionId} 아래에 저장됩니다. 고유한 테넌트 또는 사용자 경계를 포함하는 고유한 세션 ID를 사용하고 세션을 다시 시작하거나 삭제하기 전에 액세스 제어를 적용합니다.

패턴 비교

패턴사용 시기Trade-offs
패턴 1: 사용자당 격리된 CLI사용자당 가장 강력한 격리 경계 또는 별도의 프로세스 자격 증명이 필요합니다.강력한 격리; 리소스 비용이 더 높습니다.
확장성 및 멀티 테넌시을(를) 참조하세요.
패턴 2: mode: "empty" 사용 공유 CLI앱이 도구, 인증 및 세션 ID를 제어하는 동안 하나의 런타임이 많은 사용자에게 서비스를 제공하려고 합니다.효율적이지만 신중한 도구 등록, 세션별 토큰, 그리고 애플리케이션 수준의 액세스 검사가 필요합니다.
패턴 3: 하이브리드컴퓨팅이 많은 작업을 클라우드 세션으로 라우팅하고 가벼운 작업을 로컬 세션으로 라우팅합니다.유연하지만 워크로드 라우팅 및 정책 처리가 필요합니다.
클라우드 세션을(를) 참조하세요.

패턴 2: mode: "empty"가 있는 공유 CLI

이 패턴에서 모든 사용자는 백 엔드를 통해 하나의 런타임 풀에 연결합니다. 애플리케이션은 사용자 인증을 수행하고, 세션 ID를 선택하고, 세션에서 사용자의 GitHub 토큰을 전달하고, 명시적 도구 허용 목록을 제공합니다.

다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.

다음 규칙을 사용합니다.

  • 항상 mode: "empty"에서 클라이언트 또는 런타임을 시작하세요.
  • 고유한 세션 ID를 사용하고 애플리케이션 데이터베이스에 소유권 메타데이터를 저장합니다.
  • 세션 ID를 참조하는 resumeSession, deleteSession 또는 모든 UI 작업을 수행하기 전에 소유권을 확인하세요.
  • 요청이 사용자로 실행되어야 하는 경우 세션당 전달 gitHubToken 합니다.
  • 세션에 필요한 도구만 등록하고 원본으로 한정된 허용 목록(예: custom:* 또는 mcp:search_docs.)을 선호합니다.
  • sessionIdleTimeoutSeconds을 설정하고 완료된 워크플로 세션을 명시적으로 삭제합니다.

일반적인 문제

  • 잊어버림 mode: "empty". 기본 copilot-cli 모드는 CLI 스타일 동작을 노출하며 앰비언트 도구를 통해 호스트 파일 시스템을 노출할 수 있습니다.
  • 설정하지 않음 sessionIdleTimeoutSeconds. 장기 실행 서버는 정리하지 않으면 유휴 세션을 누적할 수 있습니다.
  • 세션별 토큰을 전달하는 대신, 하나의 gitHubToken를 여러 사용자 간에 공유
  • 백 엔드에서 소유권을 확인하지 않고 클라이언트에서 제공하는 세션 ID를 신뢰합니다.
  • 기존 런타임에 연결하는 클라이언트에서 설정하고 baseDirectory 런타임 스토리지를 이동할 것으로 예상합니다. 대신 런타임 프로세스를 구성합니다.
  • 각 도구가 사용자에게 적합한지 여부를 검토하지 않고와 같은 builtin:* 광범위한 도구 패턴을 허용합니다.

참고하십시오