DeepSeek Harness: サービス分離とスコープ

最終更新:2026-08-31

マルチエージェント、マルチセッション環境では、共有サービスは恩恵でもあり呪いでもあります——グローバルサービスは便利だが競合しやすく、分離されたサービスは安全だが通信コストが増えます。Cordis のスコープシステムは両者のバランスを取ります:デフォルトで共有、必要に応じて分離。

💡 ヒント:スコープの核心原則は「デフォルトで共有、必要に応じて分離」——ほとんどのサービスはグローバル共有可能;分離が必要なサービスのみ isolate 設定が必要。非分離が基本、分離が例外。

📋 前提知識19-service.md の完了、Service 基底クラスを理解していること

1. 学習内容

サービス分離とスコープ


2. グローバルスコープ

(1) デフォルト動作

デフォルトではすべての Service はグローバル——DSH インスタンス全体でインスタンスは1つだけ:

TYPESCRIPT
export default class CacheService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'cache')  // グローバルに一意
  }
}

(2) グローバルサービスの特性

特性 説明
シングルトン プロセスにつき1インスタンスのみ
共有 すべてのセッションとリクエストが同じ状態を共有
分離なし セッション A のデータ変更はセッション B からも見える

(3) 適用シナリオ

▶ サンプル 4:

TYPESCRIPT
// ❌ グローバルキャッシュはセッション間のデータ漏洩を引き起こす
export default class CacheService extends Service {
  private data = new Map<string, any>()
  
  set(key: string, value: any) {
    this.data.set(key, value)
  }
}

// セッション A:ctx.cache.set('temp', 'secret-data')
// セッション B:ctx.cache.get('temp') → 'secret-data' (漏洩!)

3. セッションスコープ

(1) 概念

セッションスコープは各セッションに独立したサービスインスタンスを作成:

TYPESCRIPT
export default class SessionCacheService extends Service {
  static scope = 'session'

  constructor(ctx: Context) {
    super(ctx, 'session-cache')
  }
}

▶ サンプル 2:

100%
graph LR
    S1[Session A 作成] --> C1[CacheService A インスタンス]
    S2[Session B 作成] --> C2[CacheService B インスタンス]
    S1 --> D1[Session A 破棄 → CacheService A 破棄]
    S2 --> D2[Session B 破棄 → CacheService B 破棄]

(3) スコープ宣言

TYPESCRIPT
export default class SessionCacheService extends Service {
  static scope = 'session'
  // または
  // static scope = Symbol('session')
}

(4) 適用シナリオ

(5) セッション間分離効果

TEXT 📖 参照専用
セッション A:
  ctx.sessionCache.set('key', 'value-A')

セッション B:
  ctx.sessionCache.get('key') → undefined  (分離されている)

セッション A:
  ctx.sessionCache.get('key') → 'value-A'  (セッション内ではアクセス可能)

4. リクエストスコープ

(1) 概念

リクエストスコープは各ツール呼び出しや LLM リクエストに独立したインスタンスを作成:

TYPESCRIPT
export default class RequestContextService extends Service {
  static scope = 'request'

  constructor(ctx: Context) {
    super(ctx, 'request-context')
  }
}

(2) リクエストスコープのライフサイクル

100%
graph LR
    R1[リクエスト 1 開始] --> S1[サービスインスタンス 1]
    R2[リクエスト 2 開始] --> S2[サービスインスタンス 2]
    R1 --> E1[リクエスト 1 完了 → インスタンス 1 破棄]
    R2 --> E2[リクエスト 2 完了 → インスタンス 2 破棄]

(3) 適用シナリオ

(4) 3つのスコープレベルの比較

次元 グローバル セッション リクエスト
インスタンス 1 セッションごとに1 リクエストごとに1
ライフサイクル プロセス生存期間 セッション生存期間 リクエスト生存期間
状態共有 グローバル共有 セッション内 リクエスト内のみ
メモリ
最適用途 設定/接続プール セッションキャッシュ/履歴 権限/タイミング

5. isolate Realm 設定

(1) Realm 概念

Realm は Cordis の分離ドメイン——同一 DSH インスタンス内に独立したプラグイン空間を作成:

YAML
# cordis.yml
realms:
  agent-a:
    isolate:['cache', 'tools']
    plugins:
      my-tool-a:
        $insert:./plugins/tool-a
  
  agent-b:
    isolate:['cache', 'tools']
    plugins:
      my-tool-b:
        $insert:./plugins/tool-b

(2) isolate フィールド

isolate リストはどのサービスが Realm 内で独立インスタンスを持つかを指定:

YAML
realms:
  my-realm:
    isolate:
      - cache       # cache サービスが独立インスタンスを持つ
      - tools       # tools サービスが独立インスタンスを持つ
      # リストにないサービスはグローバル共有のまま

(3) Realm 内のサービスインスタンス

TEXT 📖 参照専用
グローバル:llm(共有)
realm-a:cache(独立)、tools(独立)
realm-b:cache(独立)、tools(独立)

realm-a 内プラグイン → ctx.cache = realm-a の cache
realm-b 内プラグイン → ctx.cache = realm-b の cache
互いに影響しない

(4) Realm のユースケース

シナリオ 説明
マルチエージェント 異なる Agent が異なるツールセットとキャッシュを持つ
マルチテナント 異なるテナントのサービスが相互に分離
テスト テスト Realm がプロダクション Realm に影響しない
A/B テスト 2つの Realm が異なるサービス実装を使用

(5) Realm 間通信

Realm はデフォルトで分離されていますが、グローバルサービスを通じて通信可能:

TYPESCRIPT
// グローバルサービス(isolate の影響を受けない)
export default class EventBusService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'event-bus')  // isolate リストにない → グローバル共有
  }

  emit(event: string, data: any) { /* ... */ }
  on(event: string, handler: Function) { /* ... */ }
}

// realm-a 内プラグイン
ctx.eventBus.emit('data-updated', { source: 'realm-a' })

// realm-b 内プラグイン
ctx.eventBus.on('data-updated', (data) => {
  ctx.logger.info(`received from ${data.source}`)
})

6. スコープと Agent プリセット

(1) Agent プリセット概念

Agent プリセットはツールセット、モデルパラメータ、スコープ設定を含む事前定義された Agent 設定:

YAML
presets:
  coder:
    model:deepseek-coder
    tools:[file_edit, shell, search]
    mode:standard
    
  reviewer:
    model:deepseek-chat
    tools:[file_edit, search]
    mode:minimal
    isolate:[cache]

(2) プリセットとスコープの関係

各プリセットは isolate リストを指定でき、その Agent のために独立したサービスインスタンスを作成:

TEXT 📖 参照専用
coder Agent:  共有キャッシュ、独立ツール
reviewer Agent:独立キャッシュ、共有ツール

(3) マルチエージェントシナリオ

YAML
# cordis.yml
agents:
  coder:
    preset:coder
    isolate:[tools]
    
  reviewer:
    preset:reviewer
    isolate:[tools, cache]

(4) エージェント間コラボレーション

100%
graph TB
    subgraph Global
        LLM[llm Service]
        EVENT[event-bus Service]
    end
    subgraph Agent-Coder
        CT[tools Service]
        CC[cache Service]
    end
    subgraph Agent-Reviewer
        RT[tools Service]
        RC[cache Service]
    end
    CT --> LLM
    RT --> LLM
    CT --> EVENT
    RT --> EVENT

7. マルチエージェントサービス分離

(1) 分離戦略の選択

TEXT 📖 参照専用
完全共有:   すべてのサービスをグローバル → シンプルだが競合の可能性
完全分離:   すべてのサービスを独立 → 安全だがリソース浪費
混合分離:   コアサービス共有 + ビジネスサービス分離 → バランス型

推奨される混合分離:

サービスタイプ 分離戦略 理由
llm 共有 API 呼び出しの再利用が可能
sessions 共有 統一セッション管理
tools 分離 各 Agent が異なるツールセットを持つ
cache 分離 各 Agent が独立したキャッシュを持つ
fs 共有 ファイルシステムは1つのみ

▶ サンプル 2:

YAML
agents:
  frontend-dev:
    preset:coder
    isolate:[tools, cache]
    tools:
      - file_edit
      - shell
      - search
    config:
      cache:
        maxSize:100
  
  backend-dev:
    preset:coder
    isolate:[tools, cache]
    tools:
      - file_edit
      - shell
      - search
      - database
    config:
      cache:
        maxSize:200

(3) リソース消費推定

分離レベル メモリ CPU 接続
グローバル共有 1x 1x 1x
2エージェント分離 ~2x ~1.5x ~2x
5エージェント分離 ~5x ~3x ~5x

(4) 分離リーク検出

TYPESCRIPT
// サービスインスタンス数を監視
ctx.on('service/created', (name, instance) => {
  ctx.logger.info(`service created:${name}, total instances:${countInstances(name)}`)
})

// サービスのインスタンス数がエージェント数を大幅に超える場合、リークの可能性

❓ よくある質問

Q scope を宣言しない場合、サービスはデフォルトでグローバルですか?
A はい。static scope のない Service はデフォルトでグローバルシングルトンです。
Q セッションスコープの Service はセッション終了時にクリーンアップされますか?
A はい。セッションが破棄されると、その Service インスタンスは ctx.effect で登録されたリソースを含め自動的にクリーンアップされます。
Q Realm の isolate リストは動的に変更できますか?
A いいえ。Realm 設定は起動時に決定され、変更には再起動が必要です。
Q 1つの Service は複数のスコープに同時に存在できますか?
A いいえ。Service はグローバル、セッションレベル、リクエストレベルのいずれかです。スコープ間のデータ共有が必要な場合は、グローバルイベントバスを使用してください。
Q リクエストスコープのパフォーマンスオーバーヘッドは大きいですか?
A リクエストごとの Service 作成・破棄にはオーバーヘッドがあります。本当に必要な場合のみリクエストスコープを使用し、そうでなければグローバルまたはセッションスコープを使用してください。
Q スコープ問題のデバッグ方法は?
A Service コンストラクタでインスタンス ID を出力:typescript constructor(ctx:Context) { super(ctx, 'cache') console.log(`cache instance created:${this.id}, scope:${this.scope}`) }

📖 まとめ


📝 練習問題

1. ⭐ 基礎:セッションスコープの SessionStateService を書き、各セッションが独立したキー値ストレージを維持するようにしてください。2つのセッションを起動し、データ分離を確認すること。

2. ⭐⭐ 応用:cache と tools サービスを分離する Realm を設定してください。Realm 内外から cache にアクセスし、異なるインスタンスが得られることを確認。

3. ⭐⭐⭐ チャレンジ:デュアルエージェントシステムを設計してください:coder と reviewer。Coder は file_edit/shell ツール、Reviewer は file_edit/search ツールのみ。両者は llm と sessions を共有するが、cache とツールセットは独立して分離。確認:coder の shell ツールは reviewer から見えず、キャッシュも互いに影響しないこと。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%