agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
212 lines (163 loc) • 16.4 kB
Markdown
# DSL → Claude Agent SDK Options ブリッジ仕様
> **Status**: Proposal
> **Updated**: 2026-06-05
> **対象**: `src/adapters/claude-agent-sdk.ts`(および他 SDK アダプタのブリッジ層)
> **基準 SDK**: `@anthropic-ai/claude-agent-sdk` v0.2.141(型定義 `sdk.d.ts` 実測)
---
## 1. 問題意識
現行アダプタは、DSL から組み立てたエージェント定義を **`systemPrompt` 文字列に全部書き込み**、
SDK のネイティブ連携チャネル(`agents` / `skills`)を使っていない。
```ts
// 現状 src/adapters/claude-agent-sdk.ts buildOptions(抜粋)
opts.systemPrompt = systemPrompt; // ← agent 定義を文字列で丸投げ
if (this.guardrailHooks) opts.hooks = buildClaudeHooks(...); // guardrail は hooks 連携済み
opts.tools = readonly ? ["Read","Glob","Grep"] : [..., "Edit","Write","Bash"];
opts.permissionMode = this.permissionMode;
// agents / skills / settingSources は未設定
```
これでは:
- **skills(SKILL.md)が SDK に渡らない** → progressive disclosure(必要時ロード)が働かない。
- **`agents` 未登録** → LLM がサブエージェントを Agent/Task tool で呼べない。
**LLM にエージェントのルーティング(役割選択 / fanout)をさせるなら `agents` 登録は必須**。
これが無いと「候補から LLM が選ぶ」モデル自体が成立しない。
ガードレイルは `hooks` で連携済みなので、欠落の核心は **skills** と **agents** の2つ。
---
## 2. SDK の連携チャネル(v0.2.141 実型定義)
| 関心事 | SDK オプション | 意味 |
|--------|---------------|------|
| エージェント定義 | `Options.agents: Record<string, AgentDefinition>` | サブエージェント登録。Agent/Task tool 経由で LLM が呼ぶ |
| 〃(単一カスタム) | `Options.systemPrompt: string \| string[] \| {preset}` | メインスレッドのカスタム system prompt |
| Skills | `Options.skills: string[] \| 'all'` / `AgentDefinition.skills: string[]` | SKILL.md を有効化(progressive disclosure) |
| ガードレイル(実行制御) | `Options.hooks: Partial<Record<HookEvent, ...>>` | PreToolUse 等でツール実行を block/deny |
| 権限(能力境界) | `Options.allowedTools` / `disallowedTools` / `permissionMode` / `canUseTool` | ツール許可・拒否・確認 |
| 設定ロード | `Options.settingSources: ('user'\|'project'\|'local')[]` | `.claude/settings.json`・skills・agents をディスクからロード |
| 拡張束 | `Options.plugins: SdkPluginConfig[]` | commands + skills + hooks をまとめる |
| MCP | `Options.mcpServers` | MCP サーバ |
### 2.1 `AgentDefinition` のフィールド(`sdk.d.ts:38`)
```ts
type AgentDefinition = {
description: string; // いつこのエージェントを使うか(LLM ルーティングの判断材料)
prompt: string; // このエージェントの system prompt
tools?: string[]; // 許可ツール(省略時は親から継承)
disallowedTools?: string[];
model?: string; // 'sonnet'|'opus'|'haiku' or full id or 'inherit'
skills?: string[]; // このエージェントにプリロードする skill 名
mcpServers?: AgentMcpServerSpec[];
permissionMode?: PermissionMode;
maxTurns?: number;
effort?: 'low'|'medium'|'high'|'xhigh'|'max' | number;
memory?: 'user'|'project'|'local';
initialPrompt?: string;
background?: boolean;
};
```
### 2.2 Skills の重要な性質(`sdk.d.ts:1670-1692`)
- skills は **context filter**。名前+説明だけが文脈に入り、本体 SKILL.md は **Skill tool で必要時にロード**される。
- 「ファイルはディスク上にあり Read/Bash で到達可能。サンドボックスではない」。
- **したがって SKILL.md 本文を `systemPrompt` に貼ってはいけない**(progressive disclosure の意味が消え、トークンを浪費する)。`skills: ['x','y']` か `'all'` で有効化する。
- メインセッションは `Options.skills`、サブエージェントは `AgentDefinition.skills`。
---
## 3. ルーティングモデル(LLM ルーティング一択 = `agents` 登録必須)
**決定: LLM 内部ルーティング(モデル B)一択。`Options.agents` 登録は必須要件とする。**
役割選択・ドメイン特定・fanout 対象の選択は**自然言語理解を要する**(context-map と同じ問題で、
実行前に決定できない)。したがって選択は LLM に委ねるしかなく、SDK の subagent 機構に候補
エージェントを登録して **LLM が `description` を見て Agent/Task tool で委譲**する以外に成立しない。
### 仕様
- 候補エージェント群を `Options.agents: Record<id, AgentDefinition>` に登録する(**必須**)。
- 各 `AgentDefinition` に `description`(ルーティング判断材料)/ `prompt` / `tools` / `model` /
`skills` / `permissionMode` を**個別**に持たせる。
- メインセッションの `systemPrompt` は「オーケストレータ/ルータの枠組み」を担い、各専門エージェントの
定義は `agents[id]` 側に置く(**`systemPrompt` に全エージェント定義を丸投げしない**)。
### 却下: 外部ルーティング(単発 `query()` に systemPrompt 丸投げ)
runtime が事前にエージェントを 1 つ選んで `systemPrompt` に入れる方式は**却下**。
事前選択は自然言語タスクを読まないと不可能(前段の議論で確定)。`systemPrompt` 丸投げの現状実装は
このモデル専用であり、LLM ルーティングには対応できていない=**作り直し対象**。
### backend 含意(重要)
`agents` 登録必須=**ネイティブ subagent 機構を持つ backend でないと完全には成立しない**。
| backend | subagent 機構 | モデル B 対応 |
|---------|--------------|--------------|
| Claude `@anthropic-ai/claude-agent-sdk` | `Options.agents` | ✅ ネイティブ |
| OpenAI `@openai/agents` | `Agent.handoffs: Agent[]` | ✅ ネイティブ(handoff へ写像) |
| ~~Gemini `@google/genai`(生API)~~ | — | **不採用**(ADK に置換) |
| **Gemini ADK** `google/adk` | `sub_agents` + description 委譲 | ✅ ネイティブ(OpenAI 相当)= Google backend 採用 |
| Gemini CLI | subagents(markdown 定義) | △ ローカル定義依存(採用外) |
| ~~Cursor `@cursor/sdk`~~ | — | **サポート廃止**(依存 audit 不通過+subagent 機構なし) |
→ 「全て SDK 経由でコンポーネント化」を厳密に満たすのは **Claude / OpenAI / Gemini-ADK**。
生 Gemini は runtime が subagent ルーティングを自前実装、Gemini CLI / Cursor は IDE・ローカル定義
束縛として別扱い。全 backend の詳細は **§7 Backend Capability Matrix** 参照。
---
## 4. DSL → SDK Options マッピング(決定版)
ブリッジ層は `systemPrompt` 文字列だけでなく、**`Options` オブジェクト全体**を組み立てる。
| DSL / artifact | → SDK | 備考 |
|----------------|-------|------|
| agent 定義(role_name/purpose/responsibilities/constraints/rules) | `agents[id].prompt`(Claude)/ `Agent.instructions`(OpenAI handoff) | レンダリングは既存 `buildTaskPrompt` を流用。**`systemPrompt` 丸投げはしない** |
| agent の `description` 相当(purpose 要約) | `agents[id].description` | **LLM ルーティングの判断材料**。必須 |
| 候補エージェント集合(selector で絞った群) | `Options.agents: Record<id, AgentDefinition>`(Claude)/ `Agent.handoffs`(OpenAI) | **必須**。これが LLM ルーティングの前提 |
| `can_execute_tools` | `agents[id].tools` | 現状の readonly 粗判定を DSL 由来へ |
| `mode: read-only/read-write` | `permissionMode` / tools 絞り込み | |
| `model_class` + `agent-runtime.config.model_mapping` | `agents[id].model` / `Options.model` | |
| cursor-skills(SKILL.md / 固定プロンプト手順) | **in-code 合成**: skill = subagent(`AgentDefinition` prompt=本体, description=用途)/ または skill = TS ハンドラ tool(`createSdkMcpServer`+`tool()` / function / ADK function) | ネイティブ `skills`(ディスク discovery)は**不使用**。in-code 注入で `settingSources:[]` 隔離を維持。手順塊→subagent、決定的処理→tool |
| guardrails + policy | `hooks`(既存)+ `disallowedTools` / `permissionMode` | scope を hook matcher / tools 絞りに反映 |
| ルータ/オーケストレータ枠組み | `Options.systemPrompt` | 各エージェント定義ではなく「委譲の枠組み」のみ |
| 隔離(ローカルファイルを読ませない) | `settingSources: []`(Claude) | コンポーネント化要件。**明示必須**(既定は `.claude/` をロード) |
---
## 5. 最優先の実装ギャップ
| 優先 | ギャップ | 対応 |
|------|---------|------|
| **HIGH** | `agents` 未登録(=LLM ルーティング不能) | 候補エージェントを `AgentDefinition` 群に変換し `Options.agents` に登録。`description` を必ず埋める。`systemPrompt` 丸投げを廃止。OpenAI は `handoffs`、Gemini は runtime 側ルーティング合成 |
| **HIGH** | skills 未連携 | SKILL.md をディスク配置し、DSL の skill 参照を `Options.skills` / `AgentDefinition.skills` にマップ。system prompt への本文インラインを廃止 |
| **HIGH** | 隔離されていない(ローカル `.claude/` 依存の可能性) | Claude は `settingSources: []` を明示。Cursor は IDE 束縛=非隔離 backend として扱う |
| **MED** | tools 権限が粗い(readonly 二択) | `can_execute_tools` 由来の per-agent `tools` に置換 |
| LOW | plugins / mcpServers | 必要に応じて `AgentDefinition.mcpServers` / `Options.plugins` にマップ |
---
## 6. 現行実装の所在と変更点
- `src/adapters/claude-agent-sdk.ts` `buildOptions()`:
- 追加(必須): `opts.agents`、`opts.skills`、`opts.settingSources: []`。
- 変更: `opts.tools` を DSL 由来の per-agent ツールへ。`opts.systemPrompt` は「ルータ枠組み」のみに縮小(エージェント定義の丸投げをやめる)。
- 既存維持: `opts.hooks`(guardrail)、`permissionMode`。
- `src/adapters/openai-agents-sdk.ts`: 候補エージェントを `Agent` 群に変換し `handoffs` に登録(subagent 委譲)。
- `src/adapters/gemini-sdk.ts`(生 `@google/genai`): **不採用・廃止**。Google backend は ADK に置換。
- `src/adapters/cursor-sdk.ts`: **サポート廃止・撤去**(依存 audit 不通過+subagent 機構なし)。`@cursor/sdk` peer dep も削除。
- **ADK アダプタ(新規)**: 候補エージェントを ADK の `sub_agents` 階層へ変換し description 委譲に乗せる。
runtime は TypeScript のため、ADK-TS を使うか、ADK(Python) を A2A / サブプロセス経由で呼ぶかは要確定(§8)。
- ブリッジ(DSL → Options 変換)は adapter の外(runtime のブリッジ層)に置き、adapter は受け取った
Options 片を各 SDK に渡すだけにする。backend capability matrix(§3)で分岐。
- skills 本文は `systemPrompt` から除去(progressive disclosure に委ねる)。
---
## 7. Backend Capability Matrix(全 SDK)
ルーティングは LLM 内部ルーティング(§3)一択のため、**ネイティブ subagent 機構の有無**と
**ローカルファイル依存(隔離可否)**が backend 選定の決め手になる。
凡例: ✅ ネイティブ in-code / △ 可能だが制約あり / ❌ 機構なし(runtime 合成が必要) / 🔒 ローカル依存
| backend | 種別 | subagent ルーティング | tools | skills 相当 | guardrail | ローカル依存 / 隔離 | SDK 経由コンポーネント化 |
|---------|------|----------------------|-------|------------|-----------|---------------------|--------------------------|
| **Claude** `@anthropic-ai/claude-agent-sdk` | agent SDK | ✅ `Options.agents`(Agent/Task tool) | ✅ `allowedTools`/`disallowedTools`/per-agent `tools` | ✅ `skills`(progressive disclosure) | ✅ `hooks` + `permissionMode`/`canUseTool` | 既定で `.claude/` ロード 🔒 → **`settingSources:[]` で隔離可** | ◎(`settingSources:[]` 前提で完全 in-code) |
| **OpenAI** `@openai/agents` | agent SDK | ✅ `Agent.handoffs: Agent[]` | ✅ `tools`(function tools) | ❌(handoff+tools で代替) | ✅ SDK `guardrails` + `mcpServers` | なし(完全 in-code) | ◎ 最もクリーン |
| ~~**Gemini (raw)** `@google/genai`~~ | 生成 API | ❌ subagent なし | — | ❌ | ❌ | — | **不採用(ADK に置換)**。primitive 不足でモデル B を満たせない |
| **Gemini (ADK)** `google/adk`(py / ts / go / java) | agent FW | ✅ `sub_agents` 階層 + description 委譲 + Workflow/Task/A2A | ✅ tools | ❌(tools/sub_agents で代替) | △ callback / plugin / policy | なし(**code-first in-code**) | ◎(OpenAI 相当)= **Google backend はこれを採用** |
| **Gemini CLI** `google-gemini/gemini-cli` | CLI エージェント | ✅ subagents(`@agent` 委譲 + description 自動ルーティング) | ✅ tools(wildcard / MCP isolation) | △(CLI 機能依存) | ✅ Policy Engine | **強い** 🔒(`~/.gemini/agents` の markdown 定義) | △ CLI backend。ローカル定義依存(Cursor と同類) |
| ~~**Cursor** `@cursor/sdk`~~ | エージェント SDK | ❌ in-code 機構なし(単一 Agent のみ) | ✅ inline MCP | △ | ✅ hook callbacks | 既定隔離 | **サポート廃止**。①依存 audit を通らない ②候補 subagent を in-code 登録できずモデル B 不適 |
### 判断(確定)
- **採用 backend は Claude(要 `settingSources:[]`)/ OpenAI / Gemini-ADK の 3 つ**。いずれも
ネイティブ subagent + in-code 注入でモデル B・隔離要件を満たす。
- **生 `@google/genai` アダプタは不採用・廃止**。primitive 不足でモデル B を満たせず、runtime 自前合成は
ADK で代替できるため保持価値がない。Google backend は **ADK 一本**。
- **Gemini CLI** はローカル markdown 定義依存(`~/.gemini/agents`)→ 非隔離。採用外。
- **Cursor は サポート廃止**。理由は ①`@cursor/sdk` が依存関係 audit を通らない、
②候補 subagent を in-code 登録する機構が SDK に無くモデル B 不適、の 2 点。
`src/adapters/cursor-sdk.ts` と `@cursor/sdk`(optional peer dep)を撤去する。
- → ブリッジは採用 3 backend に対し、共通の最小契約(候補 AgentDefinition 群)を各機構
(Claude `agents` / OpenAI `handoffs` / ADK `sub_agents`)へ写像する。
---
## 8. 残課題(未決)
- 軽量 skill(同一 context に instructions を軽くロードしたいだけのもの)を subagent ではなく
「instruction テキストを返す tool」で表すかの線引き基準。
- ADK の組み込み方式: **ADK-TS を使う**か、**ADK(Python) を A2A / サブプロセス経由**で呼ぶか
(runtime は TypeScript。ADK-TS の成熟度を確認のうえ確定)。
- 共通抽象(candidate AgentDefinition 群)→ Claude `agents` / OpenAI `handoffs` / ADK `sub_agents`
への写像インターフェース型定義。
> **確定事項**:
> - ルーティングは **LLM 内部ルーティング(モデル B)一択**。候補登録(`agents`/`handoffs`/`sub_agents`)は必須。
> - 外部ルーティング(systemPrompt 丸投げ)は不採用。
> - **採用 backend = Claude / OpenAI / Gemini-ADK**。生 `@google/genai` アダプタは廃止。
> - **Cursor サポート廃止**(依存 audit 不通過+in-code subagent 機構なし)。`cursor-sdk.ts` と `@cursor/sdk` を撤去。Gemini CLI は採用外(非隔離)。
> - **skills は in-code 合成**(subagent or TS ハンドラ tool)。ネイティブ `skills` ディスク discovery は不使用 → `settingSources:[]` 隔離を維持。skill と agent は同じ subagent 登録機構に統合。