backend-integrator について
backend-integratorは、MassGenにLLMバックエンドを追加・監査するためのチェックリストと実装パターンです。コードベースでの実装、登録、認証、トークン管理、ツールとMCPの処理を扱います。
主な機能
- 01バックエンドの基底クラス選択
- 02ストリーミングと状態管理の実装
- 03バックエンドと機能の登録
- 04認証とトークン料金の処理
- 05カスタムツールとMCPサーバーの統合
活用シーン
- 01MassGenへのLLMプロバイダー追加
- 02既存バックエンド統合の監査
- 03バックエンド機能に伴うファイルと変更の計画
MCP 対応
APIバックエンドが設定済みのMCPサーバーを扱う方法と、CLI・SDKラッパーが基盤システムへMCPサーバー設定を渡す方法を説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ massgen/ massgen --skill backend-integratorインストール後、Claude Code で/backend-integratorを入力して読み込まれたか確認します。
詳細
バックエンドインテグレーター
このスキルでは、MassGen に新しい LLM バックエンドを統合するための完全なチェックリストとパターンを提供します。完全な統合では、コードベース全体で約 15 個のファイルを変更します。
このスキルを使用するタイミング
- 新しい LLM プロバイダー/バックエンドを追加するとき
- 既存のバックエンドに統合漏れがないか監査するとき
- バックエンドの機能を拡張する際に変更が必要なファイルを把握するとき
統合アーキテクチャ
Backend Type Decision:
Stateless + OpenAI-compatible API → subclass ChatCompletionsBackend
Stateless + custom API → subclass CustomToolAndMCPBackend
Stateless + Response API format → subclass ResponseBackend
Stateful CLI wrapper (like Codex, Gemini CLI) → subclass LLMBackend directly
Stateful SDK wrapper (like Claude Code, Copilot) → subclass LLMBackend directly
完全なチェックリスト
フェーズ 1:コア実装(3 ファイル)
1.1 バックエンドクラス
ファイル:massgen/backend/<name>.py
基底クラスを選択します:
LLMBackend— 最低限の機能です。すべて自分で実装しますCustomToolAndMCPBackend— MCP とカスタムツールのサポートを追加します(最も一般的です)ChatCompletionsBackend— OpenAI 互換 API 向けです(上記のクラスを継承します)ResponseBackend— OpenAI Response API 形式向けです
必須メソッド:
async def stream_with_tools(self, messages, tools, **kwargs) -> AsyncGenerator[StreamChunk, None]:
"""Main streaming method. Yield StreamChunks."""
def get_provider_name(self) -> str:
"""Return provider name string (e.g., 'OpenAI', 'Codex')."""
def get_filesystem_support(self) -> FilesystemSupport:
"""Return NONE, NATIVE, or MCP."""
yield する StreamChunk の種類:
| 種類 | 用途 | 主なフィールド |
|---|---|---|
"content" | テキスト出力 | content="..." |
"tool_calls" | ツールの呼び出し | tool_calls=[{id, name, arguments}] |
"reasoning" | 思考/推論の差分 | reasoning_delta="..." |
"reasoning_done" | 推論の完了 | reasoning_text="..." |
"reasoning_summary" | 推論の要約の差分 | reasoning_summary_delta="..." |
"reasoning_summary_done" | 推論の要約の完了 | reasoning_summary_text="..." |
"complete_message" | アシスタントの完全なメッセージ | complete_message={...} |
"complete_response" | API の生レスポンス | response={...} |
"done" | ストリームの完了 | usage={prompt_tokens, completion_tokens, total_tokens} |
"error" | エラーの発生 | error="..." |
"agent_status" | ステータスの更新 | status="...", detail="..." |
"backend_status" | バックエンドレベルのステータス | status="...", detail="..." |
"compression_status" | 圧縮イベント | status="...", detail="..." |
"hook_execution" | フックの実行 | hook_info={...}, tool_call_id="..." |
すべてのチャンクに共通するフィールド:source(エージェント/オーケストレーター ID)、display(ブール値、デフォルトは True)。
トークン追跡 — 次のいずれかを呼び出します:
self._update_token_usage_from_api_response(usage_dict, model) # If API returns usage
self._estimate_token_usage(messages, response_text, model) # Fallback
タイミング — stream_with_tools 内で呼び出します:
self.start_api_call_timing(self.model) # Before API call
self.record_first_token() # On first content chunk
self.end_api_call_timing(success=True/False) # After completion
ステートフルなバックエンド(CLI/SDK ラッパー)の場合は、次も実装します:
def is_stateful(self) -> bool: return True
async def clear_history(self) -> None: ...
async def reset_state(self) -> None: ...
圧縮のサポート — StreamingBufferMixin を継承し、次を呼び出します:
self._clear_streaming_buffer(**kwargs) # Start of stream
self._finalize_streaming_buffer(agent_id=id) # End of stream
1.2 フォーマッター(必要な場合)
ファイル:massgen/formatter/<name>_formatter.py
API が標準以外のメッセージ/ツール形式(OpenAI chat completions 形式以外)を使用する場合にのみ必要です。FormatterBase をサブクラス化し、format_messages()、format_tools()、format_mcp_tools() を実装します。
既存のフォーマッター:
_claude_formatter.py— Anthropic Messages API_gemini_formatter.py— Gemini API_chat_completions_formatter.py— OpenAI/汎用(互換性のある API で再利用します)_response_formatter.py— OpenAI Response API 形式
1.3 API パラメーター処理(必要な場合)
ファイル:massgen/api_params_handler/<name>_api_params_handler.py
バックエンドが HTTP API を呼び出し、API に渡す前に YAML 設定パラメーターをフィルタリング/変換する必要がある場合にのみ必要です。APIParamsHandlerBase をサブクラス化します。
CLI/SDK ラッパー(Codex、Claude Code)では通常、コマンドを直接構築するため、これは必要ありません。
フェーズ 2:登録(4 ファイル)
2.1 バックエンド init.py
ファイル:massgen/backend/__init__.py
from .your_backend import YourBackend
# Add to __all__
2.2 CLI バックエンドのマッピング
ファイル:massgen/cli.py
create_backend() 関数に追加します:
elif backend_type == "your_backend":
api_key = kwargs.get("api_key") or os.getenv("YOUR_API_KEY")
if not api_key:
raise ConfigurationError(
_api_key_error_message("YourBackend", "YOUR_API_KEY", config_path)
)
return YourBackend(api_key=api_key, **kwargs)
API キーが不要な CLI ベースのバックエンドでは、キーの確認を省略します。
2.3 機能レジストリ
ファイル:massgen/backend/capabilities.py
BACKEND_CAPABILITIES にエントリーを追加します:
"your_backend": BackendCapabilities(
backend_type="your_backend",
provider_name="YourProvider",
supported_capabilities={"mcp", "web_search", ...},
builtin_tools=["web_search"], # Provider-native tools
filesystem_support="mcp", # "none", "mcp", or "native"
models=["model-a", "model-b"], # Newest first
default_model="model-a",
env_var="YOUR_API_KEY", # Or None
notes="...",
model_release_dates={"model-a": "2025-06"},
base_url="https://api.example.com/v1", # If applicable
)
2.4 設定バリデーター(必要な場合)
ファイル:massgen/config_validator.py
特別なルール(必須パラメーター、組み合わせ不可の設定など)がある場合は、_validate_backend() にバックエンド固有の検証を追加します。
フェーズ 3:トークン管理(1 ファイル)
3.1 料金
ファイル:massgen/token_manager/token_manager.py
まず LiteLLM を確認してください — モデルが LiteLLM データベースに存在しない場合に限り、PROVIDER_PRICING に追加します。プロバイダー名は get_provider_name() と完全に一致している必要があります(大文字と小文字を区別します)。
フェーズ 4:除外するパラメーター(新しい YAML パラメーターを追加する場合は 2 ファイル)
4.1 基底クラスでの除外
ファイル:massgen/backend/base.py -> get_base_excluded_config_params()
4.2 API パラメーターハンドラーでの除外
ファイル:massgen/api_params_handler/_api_params_handler_base.py -> get_base_excluded_params()
両方の設定を同期させてください。プロバイダー API に渡してはならない、新しいフレームワークレベルの YAML パラメーターを追加します。
フェーズ 5:認証
5.1 API キーバックエンド(標準)
ほとんどのバックエンドでは API キーを使用します。capabilities.py で env_var を設定し、cli.py にキーのチェックを追加します:
# cli.py
api_key = kwargs.get("api_key") or os.getenv("YOUR_API_KEY")
if not api_key:
raise ConfigurationError(...)
return YourBackend(api_key=api_key, **kwargs)
5.2 OAuth/サブスクリプション認証(CLI/SDK ラッパー)
Codex や Claude Code のように OAuth に対応するバックエンドでは、複数段階の認証カスケードを実装します:
def __init__(self, api_key=None, **kwargs):
# Tier 1: Explicit API key
self.api_key = api_key or os.getenv("YOUR_API_KEY")
# Tier 2: Check cached OAuth tokens
self.use_oauth = not bool(self.api_key)
self.auth_file = Path.home() / ".your_cli" / "auth.json"
async def _ensure_authenticated(self):
if self.api_key:
os.environ["YOUR_API_KEY"] = self.api_key
return
if self._has_cached_credentials():
return
# Tier 3: Initiate OAuth flow
await self._initiate_oauth_flow()
async def _initiate_oauth_flow(self, use_device_flow=False):
"""Wrap the CLI's login command."""
cmd = [self._cli_path, "login"]
if use_device_flow:
cmd.append("--device-auth") # For headless/SSH
proc = await asyncio.create_subprocess_exec(*cmd, ...)
cli.py では:API キーのチェックをスキップし、認証をバックエンドに委任します:
elif backend_type == "your_backend":
# Auth handled by backend (API key or OAuth)
return YourBackend(**kwargs)
capabilities.py では:API キー名を env_var に設定します(任意であり、OAuth には必須ではありません):
env_var="YOUR_API_KEY", # Optional - can use OAuth instead
notes="Works with subscription auth (via `your-cli login`) or YOUR_API_KEY."
注:OAuth は、プロバイダーが推奨するサポート対象 SDK またはプログラムからのコマンドライン利用を通じてのみ提供します。各バックエンドで OAuth を正しくサポートできるよう、OAuth の利用方法として認められている手段を確認する必要があります(例:API を偽装した Claude Code ではなく、Claude Agent SDK を使用します)。
5.3 認証なし(ローカル推論)
ローカルサーバー(LM Studio、vLLM、SGLang)では、capabilities で env_var=None を設定します。cli.py でキーのチェックは行いません。
参考実装:
codex.py— ブラウザーとデバイスコードフローを使った完全な OAuthclaude_code.py— 3 段階:CLAUDE_CODE_API_KEY->ANTHROPIC_API_KEY-> サブスクリプションログインlmstudio/vllm/sglang— 認証なし(env_var=None)
フェーズ 6:カスタムツールと MCP
6.1 標準パス(API バックエンド)
CustomToolAndMCPBackend を継承する場合、MCP とカスタムツールは自動的に機能します:
- YAML の
mcp_servers設定から MCP サーバーを読み込みます custom_tools/custom_tools_path設定からカスタムツールを読み込みますcwdが設定されている場合、FilesystemManagerによってファイルシステム MCP が注入されますToolManagerがツールの実行を処理します
6.2 マルチモーダルツール(すべてのバックエンドタイプ)
enable_multimodal_tools: true が設定されている場合、バックエンドは read_media および generate_media のカスタムツールを登録する必要があります。massgen/backend/base.py の共有ヘルパーを使用します:
from .base import get_multimodal_tool_definitions
# In __init__:
enable_multimodal = self.config.get("enable_multimodal_tools", False) or kwargs.get("enable_multimodal_tools", False)
if enable_multimodal:
custom_tools.extend(get_multimodal_tool_definitions())
- API バックエンド(
CustomToolAndMCPBackendのサブクラス):自動的に処理されます。基底クラスがget_multimodal_tool_definitions()を呼び出し、_register_custom_tools()で登録します - CLI/SDK バックエンド(Codex、Claude Code などの
LLMBackendのサブクラス):__init__で明示的に実行し、MCP サーバーとしてラップする必要があります(§6.4 を参照)
重要:必ず get_multimodal_tool_definitions() を使用してください。ツール辞書を直接記述してはいけません。これにより、定義を一か所にまとめられます。
6.3 CLI/SDK ラッパー — MCP サーバー
これらのバックエンドでは、CLI/SDK 独自の MCP システムを設定する必要があります:
Codex:ワークスペース内のプロジェクトスコープの .codex/config.toml を(-C ディレクトリに)書き込みます。Codex はこれを自動的に読み込みます。MassGen の mcp_servers リストを TOML 形式に変換します:
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
Claude Code:mcp_servers 辞書を SDK オプションに直接渡します。サーバーのライフサイクルは SDK が処理します:
options = {"mcp_servers": {"filesystem": {"command": "npx", "args": [...]}}}
6.4 CLI/SDK ラッパー — カスタムツール
LLM は外部プロセス内で実行されるため、カスタムツールには特別な対応が必要です。
推奨:MCP サーバーとしてラップする(claude_code.py の方式):
ToolManagerを使ってカスタムツールを読み込む(スキーマ+実行関数)- 各ツールを公開する MCP サーバーを作成する
- MCP サーバーを CLI/SDK に登録する
- LLM が MCP ツールを呼び出すと、ラッパーが
ToolManagerを通じて実行する
# claude_code.py pattern (simplified)
def _create_sdk_mcp_server_from_custom_tools(self):
tool_schemas = self._custom_tool_manager.fetch_tool_schemas()
mcp_tools = []
for schema in tool_schemas:
name = schema["function"]["name"]
async def wrapper(args, tool_name=name):
return await self._execute_massgen_custom_tool(tool_name, args)
mcp_tools.append(tool(name=name, ...)(wrapper))
return create_sdk_mcp_server(name="massgen_custom_tools", tools=mcp_tools)
SDK の MCP に対応していない CLI ラッパーの場合(Codex など):
共通ユーティリティ massgen/mcp_tools/custom_tools_server.py を使用します。このユーティリティは、stdio トランスポート経由で ToolManager のツールをラップする、スタンドアロンの fastmcp サーバーを作成します。
from ..mcp_tools.custom_tools_server import build_server_config, write_tool_specs
def _setup_custom_tools_mcp(self, custom_tools):
# 1. Write tool specs JSON to workspace
specs_path = Path(self.cwd) / ".codex" / "custom_tool_specs.json"
write_tool_specs(custom_tools, specs_path)
# 2. Get MCP server config (fastmcp run ... --tool-specs ...)
server_config = build_server_config(
tool_specs_path=specs_path,
allowed_paths=[self.cwd],
agent_id="my_backend",
)
# 3. Add to mcp_servers list (written to workspace config before launch)
self.mcp_servers.append(server_config)
サーバーは CLI によってサブプロセスとして起動され、stdio 経由で接続します。reset_state() で specs ファイルをクリーンアップしてください。
参照:codex.py ではこのパターンを使用しています。custom_tools_server.py には、すぐに使える MCP サーバーの dict を返す build_server_config() も用意されています。
フォールバック:システムプロンプトへの埋め込み
- システムプロンプト内で、ツールを JSON スキーマとして記述する
- 構造化出力(JSON ブロック)を解析してツール呼び出しを取得する
- MCP によるラップが実現できない場合にのみ使用する
6.5 カスタムツールの YAML 設定
backend:
type: your_backend
custom_tools:
- path: "massgen/tool/_basic" # Directory with TOOL.md
function: "two_num_tool"
- path: "path/to/tool.py" # Single file
function: ["func_a", "func_b"]
preset_args: [{"timeout": 30}, {}]
custom_tools_path: "massgen/tool/" # Auto-discover from directory
auto_discover_custom_tools: true # Discover from registry
MassGen ワークフローツール(new_answer、vote など):これらは実行可能なツールではなく、調整レベルのツールです。必ずシステムプロンプト経由で埋め込んでください。詳しくは §6.7 を参照してください。
6.7 ワークフローツールの統合(vote、new_answer など)
ワークフローツールはネイティブな関数呼び出しツールではありません。システムプロンプトに埋め込まれ、モデルのテキスト出力から解析されます。このパターンは Claude Code と Codex のバックエンドで共通です。
massgen/backend/base.py の共通ヘルパー:
from .base import build_workflow_instructions, parse_workflow_tool_calls, extract_structured_response
# build_workflow_instructions(tools) → str
# Filters tools to workflow tools, returns instruction text with usage examples.
# Returns "" if no workflow tools present.
# parse_workflow_tool_calls(text) → List[Dict]
# Extracts JSON tool calls from text output.
# Returns standard format: {id, type, function: {name, arguments}}
# extract_structured_response(text) → Optional[Dict]
# Low-level: extracts {"tool_name": "...", "arguments": {...}} from text.
# Tries: ```json blocks → regex → brace-matching → line-by-line.
API バックエンドの場合:ワークフローツールは通常の関数ツールとして渡されるため、埋め込みは不要です。
CLI/SDK バックエンドの場合(Codex、Claude Code):モデルはネイティブな関数ツール定義を受け取れないため、次のようにします。
- 指示を構築する:
build_workflow_instructions(tools)を呼び出して指示テキストを取得する - システムプロンプトに埋め込む:バックエンドがシステムプロンプトに使用する仕組みに追加する
- テキスト出力を蓄積する:ストリーミング中のすべての
contentチャンクを追跡する - ストリーミング後に解析する:
parse_workflow_tool_calls(accumulated_text)を呼び出してツール呼び出しを抽出する - 完了チャンクとして yield する:
yield StreamChunk(type="done", tool_calls=workflow_tool_calls)
Codex 固有:指示はワークスペースのルートにある AGENTS.md に記述します(Codex はこれを自動的に読み込みます)。また、Codex は CLI 経由で単一のユーザープロンプトしか受け取らないため、オーケストレーターのシステムメッセージもメッセージから抽出して含めます。こうしないと、オーケストレーターのシステムメッセージが失われてしまいます。この方法は、開発者向け指示の方法が機能しなかったため採用されました。
Claude Code 固有:_build_system_prompt_with_workflow_tools() がツール呼び出しのセクションも追加して指示を構築し、それを system_prompt として SDK に渡します。
一般的なワークフローツールの解析(例:Claude Code)が機能するのか、MCP ツール(例:Codex)を使用する必要があるのかは、未解決の問題です。
6.7.1 MCP ベースとテキストベースのワークフローツール
CLI/SDK バックエンドで MCP ベースとテキストベースのワークフローツールを選ぶ際は、次の点を考慮してください。
MCP ベースの方法(試行済みですが、常に信頼できるとは限りません):
- ワークフローツールを MCP サーバーとして登録する
- SDK/CLI の MCP システムに渡す
- 注意点:MCP ツールの命名規則は SDK によって異なります。Claude Code では
mcp__{server_name}__がプレフィックスとして付与されます。# If server is "massgen_workflow_tools", tool names become: # "new_answer" → "mcp__massgen_workflow_tools__new_answer" # "vote" → "mcp__massgen_workflow_tools__vote" - MCP ワークフローツールを使用する場合は、適切なプレフィックスを指定して
build_workflow_mcp_instructions()を使用します。instructions = build_workflow_mcp_instructions( tools, mcp_prefix="mcp__massgen_workflow_tools__" )
テキストベースの方法(現在の Claude Code のフォールバック):
- ワークフローツールの指示をシステムプロンプトに埋め込む
parse_workflow_tool_calls()を使用して、モデルのテキスト出力から JSON ツール呼び出しを解析する- より信頼性は高いものの、堅牢な解析が必要です
現在の状況:MCP ベースの方法は信頼性に欠けていたため、Claude Code ではテキストベースのワークフローツール(JSON 解析)を使用しています。Codex も AGENTS.md の指示を通じてテキストベースの方法を使用しています。
6.6 プロバイダー固有のツール:維持するか、置き換えるか
CLI/SDK ベースのエージェント(Codex、Claude Code)には、独自の組み込みツール(ファイル編集、シェル実行、ウェブ検索、サブエージェントなど)が備わっています。統合する際は、どのツールを維持し、どのツールを MassGen の同等機能で置き換えるかを決めてください。これらの判断は NativeToolBackendMixin の get_tool_category_overrides() に記録してください。詳細は §6.8 と Phase 10.2 を参照してください。
一般的なルール:MassGen が置き換える具体的な理由を持たない限り、プロバイダーのネイティブツールを優先してください。ネイティブツールはプロバイダーのモデルに最適化されており、余分な MCP オーバーヘッドを避けられます。
MassGen に利点がある場合は置き換える:
- サブエージェント/タスクツール:MassGen のサブエージェントで置き換えます。プロバイダーのサブエージェントは MassGen の連携(投票、合意形成、インテリジェンス共有)に参加できません。
- ファイルシステムツール:プロバイダーが提供する機能を超えて、MassGen でパスの権限適用やワークスペースの分離が必要な場合は、置き換えてもかまいません。
プロバイダーのネイティブツールを維持する:
- ファイルの読み取り/書き込み/編集:プロバイダーのツールはモデルに最適化されています。
- シェル/コマンドの実行:プロバイダーのサンドボックスはすでに設定されています。
- ウェブ検索:通常、プロバイダーとの連携はシームレスです。
実装:プロバイダーのツールフィルタリング機能を使って、特定のツールを無効にします。
# Codex: via .codex/config.toml
[mcp_servers.some_server]
disabled_tools = ["task", "sub_agent"]
# Claude Code: via SDK disallowed_tools param
all_params["disallowed_tools"] = [
"Read", "Write", "Edit", "MultiEdit", # Use MassGen's MCP filesystem
"Bash", "BashOutput", "KillShell", # Use MassGen's execute_command
"LS", "Grep", "Glob", # Use MassGen's filesystem tools
"TodoWrite", # MassGen has its own task tracking
"NotebookEdit", "NotebookRead",
"ExitPlanMode",
# Security restrictions
"Bash(rm*)", "Bash(sudo*)", "Bash(su*)", "Bash(chmod*)", "Bash(chown*)",
]
# Conditionally keep web tools:
if not enable_web_search:
disallowed_tools.extend(["WebSearch", "WebFetch"])
主なパターン:
- Claude Code SDK は Bash の制限に glob パターンをサポートしています:
"Bash(rm*)"、"Bash(sudo*)" if "disallowed_tools" not in all_paramsを確認し、YAML 設定からユーザーが上書きできるようにします。- Codex では、
.codex/config.tomlの MCP サーバーごとにdisabled_toolsを指定します。組み込みツールには無効化する仕組みがないため、代わりに--full-autoを使って自動承認します。
capabilities.py に、バックエンドが提供するネイティブツールを記録してください。
builtin_tools=["file_edit", "shell", "web_search", "sub_agent"],
notes="Native tools: file ops, shell, web search, sub-agents. Override sub-agents with MassGen's."
6.8 NativeToolBackendMixin
ファイル:massgen/backend/native_tool_mixin.py
組み込みツールを持つバックエンド(CLI/SDK ラッパー)では、NativeToolBackendMixin を使ってツールのフィルタリングとフックの統合を標準化します。
from .native_tool_mixin import NativeToolBackendMixin
class YourBackend(NativeToolBackendMixin, LLMBackend):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__init_native_tool_mixin__()
# Optional: initialize hook adapter
self._init_native_hook_adapter(
"massgen.mcp_tools.native_hook_adapters.YourBackendAdapter"
)
def get_disallowed_tools(self, config):
"""Return native tools to disable (MassGen has equivalents)."""
return ["NativeTool1", "NativeTool2"]
Mixin が提供するもの:
| メソッド | 目的 |
|---|---|
get_disallowed_tools(config) | 抽象メソッド — 無効にするネイティブツールを宣言します |
get_tool_category_overrides() | 抽象メソッド — スキップ/置き換える MCP カテゴリを宣言します |
supports_native_hooks() | フックアダプターが利用可能か確認します |
get_native_hook_adapter() | アダプターのインスタンスを取得します |
set_native_hooks_config(config) | MassGen のフックをネイティブ形式で設定します |
_init_native_hook_adapter(path) | インポートパスを指定してアダプターを初期化します |
参考実装:
claude_code.py— MassGen MCP を優先し、ほとんどのネイティブツール(Read、Write、Bash など)を無効にします。codex.py— すべてのネイティブツールを維持し、代わりに MassGen 側で同等の MCP の追加をスキップします。
Phase 7:テスト(2 ファイル以上)
7.1 機能テスト
capabilities.py に追加すると、massgen/tests/test_backend_capabilities.py で自動的にテストされます。
uv run pytest massgen/tests/test_backend_capabilities.py -v
7.2 統合テスト
作成先:massgen/tests/test_<name>_integration.py、またはクロスバックエンドのテストスクリプトに追加します。
クロスバックエンドテストのパターン:
BACKEND_CONFIGS = {
"claude": {"type": "claude", "model": "claude-haiku-4-5-20251001"},
"openai": {"type": "openai", "model": "gpt-4o-mini"},
"your_backend": {"type": "your_backend", "model": "model-a"},
}
7.3 フック発火テスト(CLI/SDK バックエンド)⚠️ 必須
スクリプト:scripts/test_hook_backends.py
新しい CLI/SDK バックエンドでは、すべて必須です。 このスクリプトは、フックのデータ構造が正しいことだけでなく、実際のストリーミング中にフックのパイプライン全体が実際に発火することを検証します。
2 つのモードがあります。
ユニットモード(API 呼び出しなし — CI で実行):
uv run python scripts/test_hook_backends.py --backend your_backend
検証内容:バックエンドが GeneralHookManager を保持すること、MidStreamInjectionHook が注入コンテンツを返すこと、HighPriorityTaskReminderHook が発火すること、結合されたフックが正しく集約されること。
E2E モード(実際の API 呼び出し):
uv run python scripts/test_hook_backends.py --backend your_backend --e2e
uv run python scripts/test_hook_backends.py --backend your_backend --e2e --verbose
完全なライブフローを検証します。PreToolUse フックが発火 → ツールが実行 → PostToolUse フックが発火 → 注入コンテンツがモデルにフィードバックされ、認識されます。
バックエンドの追加:スクリプト内の BACKEND_CONFIGS にエントリーを追加します。
"your_backend": {
"type": "your_backend",
"model": "your-model",
"description": "Your Backend description",
"api_style": "openai", # or "anthropic", "gemini"
},
現在の対象:claude、openai、gemini(ネイティブ SDK)、openrouter、grok
未対応:codex、claude_code、copilot、gemini_cli — これらはファイルベースの IPC または SDK ネイティブのフックを使用するため、個別の E2E フック検証が必要です。
重要な理由:ユニットテストでは、フックのデータ構造が正しいことを検証します。このスクリプトでは、実際のストリーミングターン中にフックが実際に発火することを検証します。バックエンドはすべてのユニットテストに合格しても、ライブ実行中にフックを黙って破棄してしまう可能性があります。
7.4 サンドボックス/パス権限テスト(CLI/SDK バックエンド)⚠️ 必須
スクリプト:scripts/test_native_tools_sandbox.py
組み込みのファイル/シェルツールを備えた新しい CLI/SDK バックエンドでは、すべて必須です。 このスクリプトは、固有のシークレットを含む実際のファイルシステムに対して実際のエージェントを実行し、権限の境界が実際に適用されていることを検証します。適用するコードが存在することだけを確認するものではありません。
# Test your new backend
uv run python scripts/test_native_tools_sandbox.py --backend your_backend
# Use LLM judge to detect subtle leakage
uv run python scripts/test_native_tools_sandbox.py --backend your_backend --llm-judge
このスクリプトは、権限の全マトリックスを検証します。
| ゾーン | 想定される読み取り | 想定される書き込み |
|---|---|---|
| ワークスペース(cwd) | ✅ 許可 | ✅ 許可 |
| 書き込み可能なコンテキストパス | ✅ 許可 | ✅ 許可 |
| 読み取り専用のコンテキストパス | ✅ 許可 | ❌ ブロック |
| すべてのコンテキスト外 | バックエンドによる | ❌ ブロック |
| 親ディレクトリ | バックエンドによる | ❌ ブロック |
/tmp | ✅ 許可 | バックエンドによる |
固有のシークレット(各ゾーンのファイルに書き込まれる UUID)を使用して、操作が「失敗」した場合でも不正な読み取りを検出します。モデルの応答にシークレット文字列が現れた場合、エラーメッセージに関係なく情報漏えいが発生しています。
バックエンドの追加:スクリプト内の BACKEND_CONFIGS にエントリーを追加します。
"your_backend": {
"module": "massgen.backend.your_backend",
"class": "YourBackend",
"model": "your-model",
"blocks_reads_outside": True, # Does your enforcement block reads?
"blocks_tmp_writes": True, # Does your enforcement block /tmp writes?
},
現在の対象:claude_code、codex
未対応:copilot、gemini_cli — これらのバックエンドが本番環境で使用される際に追加する必要があります。
重要な理由:権限コールバックとフックのユニットテストでは、適用ロジックを個別に検証します。このスクリプトでは、制限されたパスへのライブエージェントのアクセスを、適用処理が実際に阻止することを検証します。バックエンドのフックロジックが正しくても、フックが適切な実行経路に組み込まれていなければ、不正なアクセスを許してしまう可能性があります。
7.5 設定の検証
uv run python scripts/validate_all_configs.py
フェーズ 8:設定例(2 ファイル以上)
8.1 単一エージェント
作成:massgen/configs/providers/<name>/single_<name>.yaml
8.2 マルチエージェント/ツール
作成:massgen/configs/providers/<name>/<name>_with_tools.yaml
フェーズ 9:ドキュメント(3 ファイル以上)
9.1 YAML スキーマ
ファイル:docs/source/reference/yaml_schema.rst — バックエンド固有のパラメーターを記述します
9.2 バックエンドの表
uv run python docs/scripts/generate_backend_tables.py
9.3 ユーザーガイド
ファイル:docs/source/user_guide/backends.rst — 新しいバックエンドのセクションを追加します
フェーズ 10:システムプロンプトに関する考慮事項
システムプロンプトは、massgen/system_message_builder.py が massgen/system_prompt_sections.py の優先度ベースのセクションを使って組み立てます。バックエンドの機能と設定フラグに応じて、異なるセクションが条件付きで含められます。
10.1 セクションのカテゴリ
常に含まれます(すべてのバックエンド):
| セクション | 目的 |
|---|---|
AgentIdentitySection | エージェントが何者か |
EvaluationSection | vote/new_answer の調整プリミティブ |
CoreBehaviorsSection | アクションの優先度、並列実行 |
OutputFirstVerificationSection | 品質改善の反復ループ |
SkillsSection | コマンド実行による openskills read |
MemorySection | 意思決定の記録 |
WorkspaceStructureSection | ワークスペースのパス |
ProjectInstructionsSection | CLAUDE.md/AGENTS.md |
SubagentSection | MassGen のサブエージェントへの委任 |
BroadcastCommunicationSection | エージェント間のコミュニケーション |
PostEvaluationSection | submit/restart |
FileSearchSection | 汎用 CLI ツールである rg/sg |
TaskContextSection | CONTEXT.md の作成 |
NoveltyPressureSection | エージェント間での新規性/多様性の促進 |
ChangedocSection | 変更のドキュメント化 |
設定フラグに応じて含まれます:
| セクション | 条件 |
|---|---|
TaskPlanningSection | enable_task_planning=True |
EvolvingSkillsSection | auto_discover_custom_tools=True AND enable_task_planning=True |
PlanningModeSection | planning_mode_enabled=True |
CodeBasedToolsSection | enable_code_based_tools=True(CodeAct パラダイム) |
CommandExecutionSection | enable_mcp_command_line=True |
DecompositionSection | 分解モードが有効 |
MultimodalToolsSection | enable_multimodal_tools=True |
モデル固有:
| セクション | 条件 |
|---|---|
GPT5GuidanceSection | GPT-5 モデルのみ |
GrokGuidanceSection | Grok モデルのみ |
ネイティブバックエンド向けに調整:
| セクション | 調整内容 |
|---|---|
FilesystemOperationsSection | has_native_tools=True → 汎用的なツール表現 |
FilesystemBestPracticesSection | 比較ツールの表現を調整 |
10.2 tool_category_overrides
ファイル:massgen/backend/native_tool_mixin.py
NativeToolBackendMixin の抽象メソッド get_tool_category_overrides() は、バックエンドが MassGen のどのツールカテゴリをネイティブに処理するかを宣言します。各ネイティブバックエンドはこのメソッドを実装します:
@abstractmethod
def get_tool_category_overrides(self) -> Dict[str, str]:
# "skip" = backend has native equivalent
# "override" = attach MassGen's version, disable native
# (absent) = attach MCP normally
カテゴリ:filesystem、command_execution、file_search、web_search、planning、subagents
現在は system_message_builder.py で以下の用途に使用されています:
filesystem: "skip"の場合、ファイルシステム関連セクションにhas_native_tools=Trueを渡す- 各バックエンドがネイティブに処理するツールカテゴリを記載する
注:MCP サーバーの挿入フィルタリング(どの MCP サーバーを接続するか)は、各 CLI/SDK バックエンド(codex.py、claude_code.py)が個別に処理します。get_tool_category_overrides() メソッドはドキュメントとして機能し、システムプロンプトのセクションの動作を制御します。ミックスインを使用しないバックエンド(API ベース)は、system_message_builder.py のフォールバックにより、デフォルトで {} を返します。
10.3 パス権限の統合
filesystem: "skip" の場合、バックエンドのネイティブツールがファイル操作を処理します。ただし、PathPermissionManager(PPM)による MassGen のきめ細かなパスごとの権限が適用されないことがあります。各バックエンドでは、この問題に異なる方法で対応しています。
2 層の防御(権限コールバックに対応する SDK バックエンドに推奨):
- レイヤー 1 — 権限コールバック(大まかなゲート):SDK の権限リクエストからパスを抽出し、PPM に照らして検証します。抽出可能なパスがない場合はフェイルオープンとし、レイヤー 2 に委ねます。
- レイヤー 2 — PreToolUse フック(きめ細かな制御):オーケストレーターは、Claude Code 以外のネイティブバックエンドに対して
PathPermissionManagerHookをPRE_TOOL_USEとして自動登録します。toolName/toolArgsをすべて利用できるため、正確に検証できます。
バックエンドごとの対応方法:
| バックエンド | PPM の適用方法 |
|---|---|
| Claude Code | 独自の add_dirs サンドボックスを使用 — オーケストレーターによる PPM フックの適用はスキップ |
| Copilot | 2 層の防御(権限コールバック + PPM PreToolUse フック)。Docker モードでは、コンテナーによる隔離に加え、多層防御として PPM を使用 |
| Codex | ネイティブフックに非対応 — Docker/ワークスペースによる隔離に依存 |
| API バックエンド | ToolManager によって PPM を自動的に適用 |
参考:2 層パターンについては copilot.py(_build_permission_callback)と orchestrator.py(_setup_native_hooks_for_agent)を参照してください。PPM の内部実装については、massgen/filesystem_manager/_path_permission_manager.py を参照してください。
フェーズ 11:フック
MassGen は、ツール実行やその他のライフサイクルイベントをインターセプトするフックをサポートしています。フックは透過的に実行されます(システムプロンプトには記載されません)。
一部のバックエンド(Codex など)はフックに対応していない場合があります。この場合、フックは MassGen のツール実行フレームワーク内で可能な範囲に限り適用されます。
11.1 フックの種類と組み込みフック
フックの種類(massgen/mcp_tools/hooks.py の HookType enum):
| HookType | トリガー |
|---|---|
PRE_TOOL_USE | ツール実行前 |
POST_TOOL_USE | ツール実行後 |
組み込みフッククラス(PatternHook のサブクラス — オーケストレーターによって登録されます):
| フッククラス | 種類 | 目的 |
|---|---|---|
PathPermissionManagerHook | PreToolUse | パスごとの読み取り/書き込み権限を適用します(_path_permission_manager.py 内) |
RoundTimeoutPreHook | PreToolUse | ラウンドの制限時間を適用します |
HumanInputHook | PreToolUse | 人間の入力をエージェントの処理フローに注入します |
MidStreamInjectionHook | PostToolUse | 調整メッセージ(投票のリマインダーなど)を注入します |
SubagentCompleteHook | PostToolUse | サブエージェントの完了を通知します |
BackgroundToolCompleteHook | PostToolUse | バックグラウンドツールの完了を通知します |
HighPriorityTaskReminderHook | PostToolUse | 現在のタスクをエージェントに再通知します |
MediaCallLedgerHook | PostToolUse | メディア生成呼び出しを追跡します |
RoundTimeoutPostHook | PostToolUse | ラウンドの制限時間を適用します |
11.2 フックのアーキテクチャ
GeneralHookManager (massgen/mcp_tools/hooks/)
├── Registers global and per-agent hooks
├── Executes hooks in order for MCP-based backends
└── For native backends → NativeHookAdapter
├── ClaudeCodeNativeHookAdapter
│ └── Converts to Claude Agent SDK HookMatcher format
└── (future) CodexNativeHookAdapter
11.3 ネイティブフックアダプター — 実装方法
ネイティブのツール実行(CLI/SDK ラッパー)を備えたバックエンドでは、MassGen のフックをバックエンドのフックシステムに橋渡しする必要があります。これは NativeHookAdapter(massgen/mcp_tools/native_hook_adapters/base.py にある基底クラス)を介して行います。
ステップ 1:アダプターのサブクラスを作成します。
# massgen/mcp_tools/native_hook_adapters/your_backend_adapter.py
from .base import NativeHookAdapter
class YourBackendNativeHookAdapter(NativeHookAdapter):
def supports_hook_type(self, hook_type):
return hook_type in (HookType.PRE_TOOL_USE, HookType.POST_TOOL_USE)
def convert_hook_to_native(self, hook, hook_type, context_factory=None):
"""Wrap MassGen hook as your backend's native hook format."""
async def native_hook(tool_name, tool_args, context):
event = self.create_hook_event_from_native(
{"tool_name": tool_name, "tool_input": tool_args},
hook_type, context_factory() if context_factory else {},
)
result = await hook.execute(event)
return self.convert_hook_result_to_native(result, hook_type)
return {"pattern": hook.matcher, "handler": native_hook}
def build_native_hooks_config(self, hook_manager, agent_id=None, context_factory=None):
"""Convert all registered hooks to native config."""
config = {"PreToolUse": [], "PostToolUse": []}
for hook_type, hooks in hook_manager.get_hooks(agent_id).items():
for hook in hooks:
native = self.convert_hook_to_native(hook, hook_type, context_factory)
config[hook_type.value].append(native)
return config
def merge_native_configs(self, *configs):
"""Merge permission hooks + MassGen hooks + user hooks."""
merged = {"PreToolUse": [], "PostToolUse": []}
for config in configs:
for key in merged:
merged[key].extend(config.get(key, []))
return merged
ステップ 2:バックエンドの __init__ で初期化します。
from ..mcp_tools.native_hook_adapters import YourBackendNativeHookAdapter
self._native_hook_adapter = YourBackendNativeHookAdapter()
ステップ 3:バックエンドのインターフェースメソッドを実装します。
def supports_native_hooks(self) -> bool:
return self._native_hook_adapter is not None
def get_native_hook_adapter(self):
return self._native_hook_adapter
def set_native_hooks_config(self, config):
self._massgen_hooks_config = config
ステップ 4:バックエンドプロセスの起動時にフックをマージします。オーケストレーターは変換済みのフックを指定して set_native_hooks_config() を呼び出します。バックエンドでは、オプションの構築時に独自の権限フックとマージします。
11.4 参考資料
- フックフレームワーク:
massgen/mcp_tools/hooks/ - アダプターベースクラス:
massgen/mcp_tools/native_hook_adapters/base.py - Claude Code アダプター:
massgen/mcp_tools/native_hook_adapters/claude_code_adapter.py - Copilot アダプター:
massgen/mcp_tools/native_hook_adapters/copilot_adapter.py - パス権限:
massgen/filesystem_manager/_path_permission_manager.py - オーケストレーターのフック設定:
massgen/orchestrator.py(native_hookを検索)
共通パターン
CLI ラッパーバックエンド(Codex など)
__init__: find CLI binary, set config defaults
stream_with_tools: build command -> spawn subprocess -> parse JSONL stdout -> yield StreamChunks
MCP: write project-scoped config file in workspace before launch
Custom tools: wrap as MCP server or inject via system prompt
Cleanup: remove workspace config on reset_state/clear_history
CLI バックエンドの Docker 実行
CLI ベースのバックエンドが Docker をネイティブにサポートしていない場合は、MassGen コンテナー内で CLI を実行します。
- Docker イメージに CLI をインストールします(Dockerfile の
npm install -gに追加するか、command_line_docker_packages.preinstall.npmを使用します) __init__でcommand_line_execution_mode: "docker"を検出し、ホスト上の CLI の検索をスキップしますstream_with_toolsでcontainer.client.api.exec_create()とexec_start(stream=True)を使用して、コンテナー内で CLI を実行します- CLI 独自のサンドボックスがある場合は無効にします。コンテナーが分離を提供します
- CLI がホストの認証情報を必要とする場合は、
_build_credential_mounts()を介して読み取り専用でマウントします
重要:一部の CLI バックエンドでは、Docker モードで実行する際にcommand_line_docker_network_mode: bridgeが必要です。これにより、コンテナーから API への外向きネットワークリクエストが可能になります。これを強制するため、設定の検証を追加してください。
# In config_validator.py
if backend_type == "your_backend":
execution_mode = backend_config.get("command_line_execution_mode")
if execution_mode == "docker":
if "command_line_docker_network_mode" not in backend_config:
result.add_error(
"YourBackend in Docker mode requires 'command_line_docker_network_mode'",
f"{location}.command_line_docker_network_mode",
"Add 'command_line_docker_network_mode: bridge' (required for network access)",
)
参考:codex.pyでは、_stream_docker()と_stream_local()を切り替えることでこのパターンを実装しています。
SDK バックエンドの Docker 実行(Copilot など)
SDK ベースのバックエンド(プロセス内の Python クライアント)は Docker 内では実行できません。SDK はホスト上にとどまります。その代わり、次の方法でファイル操作とシェル操作を分離します。
- Docker モードでは
excluded_toolsを使用して組み込みツールを無効にし、SDK がネイティブのファイルツールやシェルツールを使用しないようにします - ファイル操作とシェル操作はすべて、Docker コンテナー内で実行される MCP サーバーを経由させます
working_directoryを Docker にマウントされたワークスペースのパスに設定します
# In __init__:
self._docker_execution = (
kwargs.get("command_line_execution_mode") == "docker"
or self.config.get("command_line_execution_mode") == "docker"
)
# Property to check if Docker is actually active:
@property
def _is_docker_mode(self) -> bool:
if not self._docker_execution:
return False
if not self.filesystem_manager:
return False
dm = getattr(self.filesystem_manager, "docker_manager", None)
if dm is None:
return False
agent_id = self.config.get("agent_id")
if agent_id and dm.get_container(agent_id):
return True
return False
# In get_disallowed_tools():
def get_disallowed_tools(self, config):
if self._docker_execution:
return ["editFile", "createFile", "deleteFile",
"readFile", "listDirectory",
"runShellCommand", "shellCommand"]
return []
# In stream_with_tools(), merge Docker-excluded tools into session config:
if self._docker_execution:
docker_excluded = self.get_disallowed_tools(self.config)
if docker_excluded:
excluded_tools = list(set((excluded_tools or []) + docker_excluded))
CLI の Docker パターンとの主な違い:
- SDK はホスト上にとどまります(
exec_create/exec_startは使用しません) - 組み込みツールは、コンテナー内で実行するのではなく、SDK セッション設定(
excluded_tools)で無効にします - MCP サーバーは引き続きコンテナー内で実行されます(CLI パターンと同じです)
- PPM 権限コールバックは多層防御として引き続き有効です
設定の検証:同じパターンで、command_line_docker_network_modeを必須にします。
if backend_type == "copilot":
execution_mode = backend_config.get("command_line_execution_mode")
if execution_mode == "docker":
if "command_line_docker_network_mode" not in backend_config:
result.add_error(...)
参考:copilot.pyではこのパターンを実装しています。
Docker MCP のパス解決
MCP サーバーの設定は、ホスト上の絶対ファイルパスを使ってオーケストレーターが作成します(例:fastmcp run /host/path/massgen/mcp_tools/planning/_server.py:create_server)。明示的にマウントしない限り、Docker 内にはこれらのパスが存在しません。
解決策:_docker_manager.pyは、massgen/パッケージディレクトリを同じホストパスでコンテナーに読み取り専用でバインドマウントします。これにより、ホストパスを使った MCP 設定をコンテナー内でもそのまま機能させられます。また、古い pip インストール済みモジュールではなく、ホスト上の最新のソースを使用できます。
ネイティブツールを持つバックエンド向けのシェルサンドボックス
このセクションは、独自のシェル/ファイルツールを備えたバックエンド(例: コマンド実行機能を内蔵した CLI ラッパー)にのみ適用されます。バックエンドがすべてのツール実行を MassGen の MCP サーバーに委譲する場合、MassGen の PathPermissionManager がサンドボックス化を自動的に処理します。
バックエンドがファイルシステムにアクセスしたりコマンドを実行したりできる独自のツールを持つ場合、MassGen の権限モデルをバックエンドのサンドボックス機構にマッピングする必要があります:
| MassGen の概念 | 期待されるアクセス |
|---|---|
| ワークスペース | 書き込み |
| 一時ワークスペース | 読み取り専用 |
| 読み取りコンテキストパス | 読み取り専用 |
| 書き込みコンテキストパス | 書き込み — バックエンドのサンドボックス設定で明示的に許可する必要があります |
ワークスペースは通常、デフォルトで書き込み可能です。ファイルシステム全体への読み取りアクセスは通常許可されています。重要なタスクは、書き込みコンテキストパスがバックエンドの書き込み可能な許可リスト(例: Codex の sandbox_workspace_write.writable_roots、Claude Code の allowed_directories)に追加されていることを確認することです。
Docker モード: バックエンドのサンドボックス設定はスキップしてください — コンテナ自体がサンドボックスです。コンテナ内では完全なアクセスを許可してください。
SDK ラッパーバックエンド(Claude Code、Copilot など)
__init__: import SDK, configure options, detect Docker mode
stream_with_tools: call SDK with messages -> iterate events -> yield StreamChunks
MCP: pass mcp_servers dict to SDK options
Custom tools: create SDK MCP server from tool definitions
State: SDK manages conversation state internally
Docker: SDK stays on host; disable built-in file/shell tools via excluded_tools;
route file/shell ops through MCP servers in container (see §Docker Execution for SDK Backends)
API バックエンド(Claude、Gemini など)
Subclass CustomToolAndMCPBackend or ChatCompletionsBackend
Implement formatter if non-standard API format
Implement API params handler to filter/transform config -> API params
MCP + custom tools handled automatically by base class
複雑さ別のリファレンスバックエンド
| バックエンド | 行数 | 種類 | 良いリファレンスとなる用途 |
|---|---|---|---|
grok.py | 約81 | ChatCompletions のサブクラス | 最小構成の API バックエンド |
chat_completions.py | 約1150 | OpenAI 互換 | 標準的な API バックエンド |
response.py | 約1600 | Response API | 推論モデル |
claude.py | 約1830 | カスタム API | フル機能の API |
copilot.py | 約1250 | SDK ラッパー | SDK ベース + PPM 権限コールバック + Docker モード |
codex.py | 約2090 | CLI ラッパー | CLI ベース + Docker |
gemini.py | 約2460 | カスタム API | 非 OpenAI API |
claude_code.py | 約3530 | SDK ラッパー | フル機能のステートフル |
ファイル一覧
| # | ファイル | 必須? | 目的 |
|---|---|---|---|
| 1 | massgen/backend/<name>.py | はい | コアバックエンド |
| 2 | massgen/formatter/<name>_formatter.py | 非標準 API の場合 | メッセージ/ツールのフォーマット |
| 3 | massgen/api_params_handler/<name>_handler.py | HTTP API の場合 | パラメータのフィルタリング |
| 4 | massgen/backend/__init__.py | はい | エクスポート |
| 5 | massgen/cli.py | はい | バックエンドの作成 |
| 6 | massgen/backend/capabilities.py | はい | モデルレジストリ |
| 7 | massgen/config_validator.py | 特別なルールがある場合 | バリデーション |
| 8 | massgen/token_manager/token_manager.py | LiteLLM にない場合 | 価格設定 |
| 9 | massgen/backend/base.py | 新しい YAML パラメータがある場合 | 除外パラメータ |
| 10 | massgen/api_params_handler/_base.py | 新しい YAML パラメータがある場合 | 除外パラメータ |
| 11 | massgen/tests/test_* | はい | テスト |
| 12 | massgen/configs/providers/<name>/ | はい | 設定例 |
| 13 | docs/source/reference/yaml_schema.rst | はい | スキーマドキュメント |
| 14 | docs/source/user_guide/backends.rst | はい | ユーザーガイド |
| 15 | docs/scripts/generate_backend_tables.py | 実行する | テーブルの再生成 |