Open SWE

Open SWE 完全ガイド:オープンソースコーディングエージェントフレームワークの全貌

作成日: 2026-04-08
対象読者: エンジニア・アーキテクト・AIエージェント導入を検討する技術者
想定知識レベル: Python・Git・CI/CD・LLM の基礎知識があること


目次

  1. はじめに — Open SWE とは
  2. アーキテクチャ概要 — Deep Agents + LangGraph
  3. エージェントハーネス — Deep Agents フレームワーク
  4. サンドボックス環境 — クラウド隔離環境
  5. ツールアーキテクチャ — 約15のキュレートされたツール
  6. コンテキストエンジニアリング — AGENTS.md とソースコンテキスト
  7. オーケストレーション — サブエージェントとミドルウェア
  8. 呼び出しインターフェース — Slack・Linear・GitHub 統合
  9. バリデーション — 自動品質保証
  10. PR 自動作成 — コミットからプルリクエストまで
  11. LLM モデル統合 — Claude Opus 4.6 とモデル切り替え
  12. デプロイメント — Docker と LangGraph 設定
  13. カスタマイズ — 拡張ポイントの全体像
  14. セキュリティ — サンドボックス隔離と本番環境保護
  15. 実践的な使用例 — ワークフローの具体例
  16. 類似ツールとの比較 — Devin・GitHub Copilot Workspace 等
  17. まとめ — Open SWE の位置づけと展望

1. はじめに — Open SWE とは

1.1 Open SWE の概要

Open SWE は、LangChain が開発・公開しているオープンソースのコーディングエージェントフレームワークである。ソフトウェアエンジニアリング(Software Engineering)タスクをAIエージェントに委譲し、Slack メッセージや Linear チケット、GitHub のPRコメントなどをトリガーとして自律的にコードの変更・テスト・プルリクエスト作成までを実行する。

GitHub 上で 9,300 を超えるスターを獲得しており(2026年4月時点)、MIT ライセンスで公開されている。コードベースの 98.6% が Python で構成され、LangGraph と Deep Agents フレームワークの上に構築されている。

従来、コーディングエージェントは各企業が独自に構築するか、Devin のようなクローズドな SaaS を利用するかの二択であった。Open SWE は、Stripe、Ramp、Coinbase といった企業が社内で運用しているコーディングエージェントと同等の機能を、オープンソースとして誰でも利用・カスタマイズできる形で提供することを目指している。

1.2 なぜ企業がコーディングエージェントを必要とするのか

現代のソフトウェア開発において、エンジニアの時間の多くはルーティン作業に費やされている。具体的には以下のようなタスクが挙げられる。

タスクの種類具体例自動化の可能性
バグ修正単純なロジックエラー、型の不整合、null チェック漏れ
リファクタリング非推奨APIの置き換え、コードスタイル統一
依存関係更新ライブラリバージョンアップ、セキュリティパッチ適用中〜高
テスト追加既存機能へのユニットテスト追加、カバレッジ向上
ドキュメント更新API仕様書の更新、README の修正
設定変更CI/CD パイプラインの調整、環境設定の修正

これらのタスクは、個々としては単純であっても積み重なることでエンジニアの生産性を大きく低下させる。コーディングエージェントは、こうしたタスクを自律的に処理することで、エンジニアがより創造的で高度な設計・意思決定に集中できる環境を実現する。

1.3 Open SWE の設計哲学

Open SWE の設計は、以下の3つの哲学に基づいている。

1. コンポジション(Composition over Forking)

多くのオープンソースエージェントプロジェクトは、フォークして独自にカスタマイズすることを前提としている。しかし、フォークベースのアプローチでは上流の変更を取り込むことが困難になり、長期的なメンテナンスコストが増大する。Open SWE は、Deep Agents フレームワークを基盤として「コンポジション」によるカスタマイズを推奨している。つまり、フレームワーク自体を変更するのではなく、設定やプラグインの形で振る舞いを拡張する。

2. キュレーションされたツールセット

汎用的なツールを大量に提供するのではなく、ソフトウェアエンジニアリングタスクに特化した約15のツールを厳選して提供する。各ツールは単一責任の原則に従い、明確な入出力仕様を持つ。

3. 安全なサンドボックス実行

すべてのコード実行はクラウド上の隔離されたサンドボックス環境で行われる。本番環境への直接アクセスは許可されず、エージェントの行動はプルリクエストという形で人間のレビューを経て初めて本番に反映される。

1.4 本記事の構成

本記事は、Open SWE の全体像を17のセクションに分けて体系的に解説する。アーキテクチャの概要から始まり、各コンポーネントの詳細、実践的な使用例、セキュリティ考慮事項、類似ツールとの比較まで網羅する。各セクションには設定例やコード例を含め、読者が実際に Open SWE を導入・カスタマイズするための具体的な知識を提供する。

1.5 Open SWE プロジェクトの基本情報

項目詳細
リポジトリlangchain-ai/open-swe
ライセンスMIT License
言語構成Python 98.6%
GitHub Stars9,300+(2026年4月時点)
主要依存関係LangGraph、Deep Agents
デフォルトLLManthropic:claude-opus-4-6
デプロイ方式Docker コンテナ
設定形式LangGraph JSON、pyproject.toml

2. アーキテクチャ概要 — Deep Agents + LangGraph

2.1 全体アーキテクチャ

Open SWE のアーキテクチャは、大きく分けて6つのレイヤーで構成される。

┌─────────────────────────────────────────────────────────────────┐
│                    呼び出しインターフェース層                      │
│              (Slack / Linear / GitHub PR Comments)               │
├─────────────────────────────────────────────────────────────────┤
│                     オーケストレーション層                        │
│           (LangGraph State Machine / Middleware)                 │
├─────────────────────────────────────────────────────────────────┤
│                    エージェントハーネス層                         │
│              (Deep Agents Framework / Agent Core)                │
├──────────────────────┬──────────────────────────────────────────┤
│    ツール層           │         コンテキスト層                    │
│  (~15 Curated Tools) │  (AGENTS.md / Issue History / Context)   │
├──────────────────────┴──────────────────────────────────────────┤
│                     サンドボックス層                              │
│        (Modal / Daytona / Runloop / Custom Providers)            │
├─────────────────────────────────────────────────────────────────┤
│                      LLM 層                                     │
│           (Claude Opus 4.6 / Configurable Models)               │
└─────────────────────────────────────────────────────────────────┘

各レイヤーは疎結合に設計されており、個別に差し替えやカスタマイズが可能である。例えば、サンドボックスプロバイダーを Modal から Daytona に切り替えたり、LLM を Claude から別のモデルに変更したりすることが、設定の変更のみで実現できる。

2.2 LangGraph による状態管理

Open SWE のコアとなるエージェントループは、LangGraph の状態マシンとして実装されている。LangGraph は LangChain エコシステムにおけるエージェントオーケストレーションフレームワークであり、有向グラフとして複雑なワークフローを表現する。

# src/open_swe/graph.py の概念的な構造
from langgraph.graph import StateGraph, END

# エージェントの状態定義
class AgentState(TypedDict):
    messages: list[BaseMessage]
    sandbox: SandboxSession
    context: ContextBundle
    todos: list[TodoItem]
    pr_url: str | None

# グラフの構築
workflow = StateGraph(AgentState)

# ノードの追加
workflow.add_node("initialize", initialize_agent)
workflow.add_node("plan", create_plan)
workflow.add_node("execute", execute_tools)
workflow.add_node("validate", run_validation)
workflow.add_node("commit", commit_changes)
workflow.add_node("create_pr", open_pull_request)

# エッジの定義
workflow.add_edge("initialize", "plan")
workflow.add_edge("plan", "execute")
workflow.add_conditional_edges(
    "execute",
    should_continue,
    {
        "continue": "execute",
        "validate": "validate",
        "done": END
    }
)
workflow.add_edge("validate", "commit")
workflow.add_edge("commit", "create_pr")
workflow.add_edge("create_pr", END)

graph = workflow.compile()

LangGraph を採用する利点は、以下の通りである。

  1. 状態の永続化: エージェントの実行状態をチェックポイントとして保存し、中断・再開が可能
  2. ストリーミング: 実行過程をリアルタイムでストリーミング配信し、Slack等でユーザーに進捗を通知
  3. ヒューマン・イン・ザ・ループ: 特定のステップで人間の承認を求めるフローの実装が容易
  4. デバッグとトレーシング: LangSmith と連携してエージェントの全実行履歴を可視化

2.3 Deep Agents フレームワークとの関係

Deep Agents は、LangChain が提唱するエージェント構築のためのアーキテクチャパターンであり、以下の原則を定義している。

  • コンポジションベース: エージェントの振る舞いをフォークではなく合成で定義する
  • ミドルウェアパターン: リクエスト/レスポンスのパイプラインに介入するミドルウェアを提供
  • サブエージェントパターン: 複雑なタスクを独立したサブエージェントに分割して並列実行する
  • コンテキストウィンドウ管理: LLM のコンテキストウィンドウを効率的に活用する戦略

Open SWE は、この Deep Agents フレームワークの最初の本格的な実装例として位置づけられている。

2.4 データフローの全体像

以下は、Slack メッセージを契機とした典型的なデータフローを示す。

[Slack Message]
    ↓ "repo:owner/name バグを修正してください"
[Invocation Surface]
    ↓ パース: リポジトリ情報 + タスク内容を抽出
[Context Engineering]
    ↓ AGENTS.md 読み込み、イシュー履歴、コードベース分析
[Agent Harness]
    ↓ LLM にコンテキストとツールを提供
[Sandbox Creation]
    ↓ クラウドにLinux環境を起動、リポジトリをクローン
[Tool Execution Loop]
    ↓ read_file → edit_file → execute(テスト) → ...
[Validation]
    ↓ リンター、フォーマッター、テストの実行
[Commit & PR]
    ↓ 変更をコミット、プルリクエストを作成
[Response]
    ↓ Slack にPRリンクを返信
[Human Review]
    ↓ エンジニアがPRをレビュー・マージ

2.5 設定ファイルの全体構造

Open SWE プロジェクトの設定は、主に以下のファイルで管理される。

open-swe/
├── langgraph.json          # LangGraph エージェント設定
├── pyproject.toml           # Python パッケージ設定
├── Dockerfile               # コンテナビルド設定
├── src/
│   └── open_swe/
│       ├── graph.py         # エージェントグラフ定義
│       ├── tools/           # ツール実装
│       ├── middleware/      # ミドルウェア実装
│       ├── sandbox/         # サンドボックスプロバイダー
│       ├── context/         # コンテキストエンジニアリング
│       └── invocations/     # 呼び出しインターフェース
└── AGENTS.md                # エージェント指示書テンプレート

langgraph.json の構成:

{
  "graphs": {
    "agent": "./src/open_swe/graph.py:graph"
  },
  "env": {
    "ANTHROPIC_API_KEY": "",
    "GITHUB_TOKEN": "",
    "SANDBOX_PROVIDER": "modal",
    "LANGSMITH_API_KEY": "",
    "SLACK_BOT_TOKEN": "",
    "LINEAR_API_KEY": ""
  },
  "dependencies": ["."],
  "dockerfile_lines": [
    "RUN apt-get update && apt-get install -y git"
  ]
}

pyproject.toml の主要設定:

[project]
name = "open-swe"
version = "0.1.0"
requires-python = ">=3.11"

[project.dependencies]
langgraph = ">=0.3.0"
langchain-anthropic = ">=0.3.0"
deep-agents = ">=0.1.0"
modal = ">=0.70.0"
pygithub = ">=2.0.0"
httpx = ">=0.27.0"

3. エージェントハーネス — Deep Agents フレームワーク

3.1 エージェントハーネスとは

エージェントハーネス(Agent Harness)は、Open SWE の中核を成すコンポーネントであり、LLM とツール群を結びつけ、エージェントの実行ループを制御する。Deep Agents フレームワーク上に構築されており、「フォークではなくコンポジション」という設計原則を具現化している。

従来のエージェントフレームワークでは、カスタマイズのためにフレームワーク自体のコードをフォークして変更する必要があった。これは以下の問題を引き起こす。

  • アップストリームの変更追従が困難: フォーク元のバグ修正や新機能を取り込むのにコンフリクト解消が必要
  • メンテナンスコストの増大: 独自変更部分の保守を自力で行う必要がある
  • コミュニティの断片化: 各組織が独自フォークを持つと、改善がエコシステム全体に還元されにくい

Deep Agents フレームワークは、これらの問題をコンポジションパターンで解決する。

3.2 コンポジションアーキテクチャの詳細

コンポジションアーキテクチャは、以下の拡張ポイントを提供する。

from deep_agents import AgentHarness, HarnessConfig

# カスタムハーネスの構築例
config = HarnessConfig(
    # LLMの選択
    model="anthropic:claude-opus-4-6",
    
    # ツールの選択と追加
    tools=[
        # デフォルトツール
        *default_swe_tools,
        # カスタムツール
        my_custom_database_tool,
        my_internal_api_tool,
    ],
    
    # ミドルウェアの設定
    middleware=[
        check_message_queue_before_model,
        open_pr_if_needed,
        ToolErrorMiddleware(),
        # カスタムミドルウェア
        MyLoggingMiddleware(),
    ],
    
    # システムプロンプトのカスタマイズ
    system_prompt="""
    あなたは社内のコーディングエージェントです。
    以下のルールに従ってください:
    - コードスタイルは PEP 8 に準拠
    - テストカバレッジ 80% 以上を維持
    - セキュリティに関する変更は必ずセキュリティチームにレビューを依頼
    """,
    
    # サンドボックスプロバイダー
    sandbox_provider="modal",
    
    # コンテキスト設定
    context_config={
        "agents_md": True,
        "issue_history": True,
        "max_context_tokens": 100000,
    },
)

harness = AgentHarness(config)

3.3 エージェントのライフサイクル

エージェントハーネスは、以下のライフサイクルでタスクを処理する。

Phase 1: 初期化(Initialization)

async def initialize_agent(state: AgentState) -> AgentState:
    """エージェントの初期化フェーズ"""
    # 1. サンドボックス環境の起動
    sandbox = await create_sandbox(
        provider=config.sandbox_provider,
        repo_url=state["repo_url"],
        branch=state.get("branch", "main"),
    )
    
    # 2. リポジトリのクローン
    await sandbox.execute(f"git clone {state['repo_url']} /workspace")
    await sandbox.execute("cd /workspace && git checkout -b agent/fix")
    
    # 3. AGENTS.md の読み込み
    agents_md = await sandbox.read_file("/workspace/AGENTS.md")
    
    # 4. コンテキストの構築
    context = build_context(
        agents_md=agents_md,
        issue=state.get("issue"),
        thread_history=state.get("thread_history"),
    )
    
    return {
        **state,
        "sandbox": sandbox,
        "context": context,
    }

Phase 2: 計画(Planning)

エージェントは、与えられたタスクとコンテキストに基づいて実行計画を立てる。この計画は write_todos ツールを使って構造化される。

Phase 3: 実行(Execution)

ツールを使ってコードの読み取り、編集、テスト実行を繰り返す。必要に応じてサブエージェント(task ツール)を起動し、複雑なタスクを分割・並列処理する。

Phase 4: 検証(Validation)

リンター、フォーマッター、テストスイートを実行し、変更が品質基準を満たしているか検証する。

Phase 5: コミットとPR作成(Commit & PR)

検証をパスした変更をコミットし、プルリクエストを作成する。PR の本文にはタスクの概要、変更内容、テスト結果が含まれる。

3.4 状態管理とチェックポイント

LangGraph の状態管理機能を活用し、エージェントの実行状態は各ステップでチェックポイントとして保存される。

# チェックポイントの設定例
from langgraph.checkpoint.sqlite import SqliteSaver

# SQLite ベースのチェックポイント
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")

# グラフのコンパイル時にチェックポインタを指定
graph = workflow.compile(checkpointer=checkpointer)

# 実行時にスレッドIDを指定して状態を管理
config = {"configurable": {"thread_id": "slack-msg-12345"}}
result = await graph.ainvoke(initial_state, config=config)

これにより、以下のシナリオに対応できる。

  • 長時間実行の中断と再開: ネットワーク障害やタイムアウト時に最後のチェックポイントから再開
  • ヒューマン・イン・ザ・ループ: 特定のステップで人間の承認を待ち、承認後に再開
  • デバッグ: 問題が発生した実行のステップごとの状態を調査

3.5 エラーハンドリング戦略

エージェントハーネスは、多層的なエラーハンドリング戦略を採用している。

class ToolErrorMiddleware:
    """ツール実行エラーを優雅に処理するミドルウェア"""
    
    async def __call__(self, state, tool_call, tool_result):
        if tool_result.is_error:
            # エラーの種類に応じた処理
            if isinstance(tool_result.error, SandboxTimeoutError):
                # タイムアウト: リトライロジック
                return await self.retry_with_timeout_extension(
                    state, tool_call
                )
            elif isinstance(tool_result.error, FileNotFoundError):
                # ファイル未検出: エージェントに通知して代替戦略を促す
                return ToolMessage(
                    content=f"ファイルが見つかりませんでした: {tool_call.args['path']}. "
                    "ls や glob ツールで正しいパスを確認してください。",
                    tool_call_id=tool_call.id,
                )
            elif isinstance(tool_result.error, PermissionError):
                # 権限エラー: セキュリティ制約として記録
                logger.warning(f"Permission denied: {tool_call}")
                return ToolMessage(
                    content="この操作はセキュリティポリシーにより制限されています。",
                    tool_call_id=tool_call.id,
                )
            else:
                # その他: スタックトレースを含めてエージェントに通知
                return ToolMessage(
                    content=f"エラーが発生しました: {tool_result.error}\n"
                    "別のアプローチを試してください。",
                    tool_call_id=tool_call.id,
                )
        return tool_result

4. サンドボックス環境 — クラウド隔離環境

4.1 サンドボックスの必要性

コーディングエージェントがシェルコマンドを実行し、ファイルを読み書きし、外部APIにアクセスする以上、セキュリティの観点から実行環境の隔離は必須である。Open SWE は、すべてのコード実行をクラウド上の隔離されたサンドボックス環境で行う。

サンドボックスが提供する保護レイヤーは以下の通りである。

保護層内容
ファイルシステム隔離作業ディレクトリ外のファイルへのアクセスを制限
ネットワーク隔離許可されたエンドポイント以外への通信を遮断
リソース制限CPU、メモリ、ディスク容量の上限設定
時間制限長時間実行の防止(タイムアウト設定)
権限制限root 権限の制限、機密ファイルへのアクセス制御

4.2 サンドボックスプロバイダー

Open SWE は、複数のサンドボックスプロバイダーをサポートしており、プロバイダーの切り替えは環境変数の変更のみで実現できる。

4.2.1 Modal

Modal は、クラウドコンピューティングプラットフォームであり、Open SWE のデフォルトサンドボックスプロバイダーである。

# Modal サンドボックスの設定例
from open_swe.sandbox.modal_provider import ModalSandboxProvider

provider = ModalSandboxProvider(
    image="debian:bookworm-slim",
    cpu=2.0,
    memory=4096,  # MB
    timeout=600,   # 秒
    gpu=None,       # GPU不要の場合
    volumes={
        "/workspace": modal.Volume.from_name("workspace-vol"),
    },
    secrets=[
        modal.Secret.from_name("github-token"),
    ],
)

Modal の利点:

  • コールドスタートが速い: コンテナの起動が秒単位
  • 従量課金: 実行時間に対してのみ課金
  • GPU サポート: 機械学習関連のタスクにも対応
  • Python ネイティブ: Python コードから直接制御可能

4.2.2 Daytona

Daytona は、開発環境のプロビジョニングに特化したプラットフォームである。

# Daytona サンドボックスの設定例
from open_swe.sandbox.daytona_provider import DaytonaSandboxProvider

provider = DaytonaSandboxProvider(
    workspace_template="open-swe-default",
    region="us-east-1",
    machine_type="standard",
    env_vars={
        "GITHUB_TOKEN": os.environ["GITHUB_TOKEN"],
    },
)

Daytona の利点:

  • Dev Container サポート: .devcontainer 設定をそのまま利用可能
  • 永続的なワークスペース: 長時間のタスクにも対応
  • チーム共有: 複数のエージェントで同じワークスペース設定を共有

4.2.3 Runloop

Runloop は、AI エージェント向けに最適化されたサンドボックス環境を提供する。

# Runloop サンドボックスの設定例
from open_swe.sandbox.runloop_provider import RunloopSandboxProvider

provider = RunloopSandboxProvider(
    blueprint_id="open-swe-v1",
    resources={
        "cpu": "2",
        "memory": "4Gi",
        "disk": "20Gi",
    },
)

4.2.4 LangSmith

LangSmith をサンドボックスプロバイダーとして使用する場合、実行トレーシングとサンドボックスが統合される。

4.2.5 カスタムプロバイダー

独自のサンドボックスプロバイダーを実装することも可能である。

from open_swe.sandbox.base import BaseSandboxProvider, SandboxSession

class CustomSandboxProvider(BaseSandboxProvider):
    """社内 Kubernetes クラスタを使ったカスタムプロバイダー"""
    
    async def create_session(
        self,
        repo_url: str,
        branch: str = "main",
    ) -> SandboxSession:
        # Kubernetes Pod の作成
        pod = await self.k8s_client.create_pod(
            namespace="open-swe-sandboxes",
            image="open-swe-sandbox:latest",
            resources={
                "cpu": "2",
                "memory": "4Gi",
            },
            env={
                "REPO_URL": repo_url,
                "BRANCH": branch,
            },
        )
        
        return SandboxSession(
            session_id=pod.metadata.name,
            execute=lambda cmd: self.k8s_exec(pod, cmd),
            read_file=lambda path: self.k8s_read(pod, path),
            write_file=lambda path, content: self.k8s_write(pod, path, content),
            cleanup=lambda: self.k8s_delete(pod),
        )
    
    async def cleanup_session(self, session: SandboxSession):
        await session.cleanup()

4.3 サンドボックスのライフサイクル

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   作成       │────→│   アクティブ  │────→│   クリーンアップ │
│  (Create)    │     │   (Active)    │     │  (Cleanup)    │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                      │
       ▼                    ▼                      ▼
  環境構築            ツール実行             リソース解放
  リポクローン        コード変更             Pod/VM削除
  依存解決            テスト実行             ボリューム破棄

サンドボックスのライフサイクルは自動管理される。エージェントタスクの開始時に作成され、タスク完了時に自動的にクリーンアップされる。異常終了した場合も、タイムアウトにより自動でリソースが解放される。

4.4 ファイルシステムの構造

サンドボックス内のファイルシステムは以下のように構成される。

/
├── workspace/                 # リポジトリのクローン先(作業ディレクトリ)
│   ├── .git/                  # Gitリポジトリ
│   ├── AGENTS.md              # エージェント指示書
│   ├── src/                   # ソースコード
│   ├── tests/                 # テストコード
│   └── ...
├── home/agent/                # エージェントのホームディレクトリ
│   ├── .gitconfig             # Git設定
│   └── .ssh/                  # SSH鍵(必要に応じて)
└── tmp/                       # 一時ファイル

5. ツールアーキテクチャ — 約15のキュレートされたツール

5.1 ツール設計の哲学

Open SWE のツールセットは、「少数精鋭」の原則に基づいて設計されている。一般的なエージェントフレームワークでは数十から数百のツールを提供する場合があるが、ツール数が多すぎると LLM が適切なツールを選択する精度が低下する。Open SWE は、ソフトウェアエンジニアリングタスクに必要十分な約15のツールを厳選して提供する。

各ツールは以下の設計原則に従っている。

  1. 単一責任: 各ツールは1つの明確な目的を持つ
  2. 明確な入出力: パラメータとリターン値の型が明確に定義されている
  3. エラー透過性: エラーが発生した場合、エージェントが理解可能な形でフィードバックする
  4. べき等性: 可能な限り同じ入力に対して同じ結果を返す

5.2 ツール一覧と詳細

# Open SWE のツールセット
tools = [
    execute,            # シェルコマンド実行
    fetch_url,          # Web コンテンツ取得
    http_request,       # API インタラクション
    commit_and_open_pr, # Git + PR 作成
    linear_comment,     # Linear コメント
    slack_thread_reply,  # Slack スレッド返信
    read_file,          # ファイル読み取り
    write_file,         # ファイル書き込み
    edit_file,          # ファイル編集
    ls,                 # ディレクトリ一覧
    glob,               # ファイルパターンマッチング
    grep,               # コンテンツ検索
    write_todos,        # タスク整理
    task,               # サブエージェント起動
]

5.2.1 execute — シェルコマンド実行

サンドボックス内でシェルコマンドを実行する最も基本的なツール。ビルド、テスト、依存関係のインストールなど幅広い用途で使用される。

from open_swe.tools import execute

# ツールの定義
@tool
async def execute(
    command: str,
    working_directory: str = "/workspace",
    timeout: int = 120,
) -> str:
    """
    サンドボックス内でシェルコマンドを実行します。
    
    Args:
        command: 実行するシェルコマンド
        working_directory: コマンド実行ディレクトリ
        timeout: タイムアウト(秒)
    
    Returns:
        コマンドの標準出力と標準エラー出力
    """
    result = await sandbox.execute(
        command,
        cwd=working_directory,
        timeout=timeout,
    )
    return f"Exit code: {result.exit_code}\n{result.stdout}\n{result.stderr}"

使用例:

# テストの実行
await execute(command="npm test", working_directory="/workspace")

# 依存関係のインストール
await execute(command="pip install -r requirements.txt")

# Lint の実行
await execute(command="eslint --fix src/", working_directory="/workspace")

# ビルドの実行
await execute(command="make build")

5.2.2 fetch_url — Web コンテンツ取得

指定された URL のコンテンツを取得する。ドキュメントの参照、API仕様の確認などに使用される。

@tool
async def fetch_url(
    url: str,
    headers: dict[str, str] | None = None,
) -> str:
    """
    URLからWebコンテンツを取得します。HTMLはMarkdownに変換されます。
    
    Args:
        url: 取得するURL
        headers: カスタムHTTPヘッダー
    
    Returns:
        取得したコンテンツ(Markdown形式)
    """

5.2.3 http_request — API インタラクション

汎用的な HTTP リクエストを送信する。内部 API やサードパーティ API との連携に使用される。

@tool
async def http_request(
    url: str,
    method: str = "GET",
    headers: dict[str, str] | None = None,
    body: str | None = None,
    timeout: int = 30,
) -> str:
    """
    HTTPリクエストを送信します。
    
    Args:
        url: リクエスト先URL
        method: HTTPメソッド(GET, POST, PUT, DELETE, PATCH)
        headers: リクエストヘッダー
        body: リクエストボディ(JSON文字列)
        timeout: タイムアウト(秒)
    
    Returns:
        レスポンスのステータスコードとボディ
    """

5.2.4 commit_and_open_pr — Git + PR 作成

変更をコミットし、プルリクエストを作成する。Open SWE の最も重要なツールの1つ。

@tool
async def commit_and_open_pr(
    title: str,
    body: str,
    commit_message: str,
    branch_name: str = "agent/fix",
    base_branch: str = "main",
    draft: bool = False,
    labels: list[str] | None = None,
    reviewers: list[str] | None = None,
) -> str:
    """
    変更をコミットし、GitHubにプルリクエストを作成します。
    
    Args:
        title: PRのタイトル
        body: PRの説明文
        commit_message: コミットメッセージ
        branch_name: 作業ブランチ名
        base_branch: マージ先ブランチ
        draft: ドラフトPRとして作成するか
        labels: PRに付与するラベル
        reviewers: レビュアーのGitHubユーザー名
    
    Returns:
        作成されたPRのURL
    """

使用例:

result = await commit_and_open_pr(
    title="Fix: null pointer exception in UserService",
    body="""
    ## 概要
    UserService.getProfile() で発生していたnullポインタ例外を修正しました。
    
    ## 変更内容
    - `user` が null の場合の早期リターンを追加
    - 関連するユニットテストを追加
    
    ## テスト
    - `npm test` 全テストパス
    - 追加テスト: `UserService.getProfile.null.test.ts`
    """,
    commit_message="fix: handle null user in UserService.getProfile()",
    labels=["bug", "agent-generated"],
    reviewers=["tech-lead"],
)
# 出力: "https://github.com/owner/repo/pull/123"

5.2.5 linear_comment — Linear コメント

Linear のイシューにコメントを投稿する。進捗報告やPRリンクの共有に使用される。

@tool
async def linear_comment(
    issue_id: str,
    body: str,
) -> str:
    """
    Linearイシューにコメントを追加します。
    
    Args:
        issue_id: LinearイシューID
        body: コメント本文(Markdown対応)
    
    Returns:
        作成されたコメントのID
    """

5.2.6 slack_thread_reply — Slack スレッド返信

Slack のスレッドに返信する。エージェントの進捗報告、質問への回答、完了通知に使用される。

@tool
async def slack_thread_reply(
    channel_id: str,
    thread_ts: str,
    text: str,
    blocks: list[dict] | None = None,
) -> str:
    """
    Slackスレッドに返信します。
    
    Args:
        channel_id: SlackチャンネルID
        thread_ts: スレッドのタイムスタンプ
        text: 返信テキスト
        blocks: リッチメッセージブロック(任意)
    
    Returns:
        投稿されたメッセージのタイムスタンプ
    """

5.2.7 read_file — ファイル読み取り

サンドボックス内のファイルを読み取る。コードの理解、設定ファイルの確認などに使用される。

@tool
async def read_file(
    path: str,
    start_line: int | None = None,
    end_line: int | None = None,
) -> str:
    """
    ファイルの内容を読み取ります。
    
    Args:
        path: ファイルパス(サンドボックス内の絶対パス)
        start_line: 読み取り開始行(任意)
        end_line: 読み取り終了行(任意)
    
    Returns:
        ファイルの内容(行番号付き)
    """

5.2.8 write_file — ファイル書き込み

新しいファイルを作成する、または既存ファイルの内容を完全に置き換える。

@tool
async def write_file(
    path: str,
    content: str,
    create_directories: bool = True,
) -> str:
    """
    ファイルに内容を書き込みます(上書き)。
    
    Args:
        path: ファイルパス
        content: 書き込む内容
        create_directories: 親ディレクトリを自動作成するか
    
    Returns:
        書き込み結果のメッセージ
    """

5.2.9 edit_file — ファイル編集

既存ファイルの一部を編集する。write_file がファイル全体を上書きするのに対し、edit_file は指定した部分のみを変更する。これにより大きなファイルの一部のみを効率的に変更できる。

@tool
async def edit_file(
    path: str,
    old_string: str,
    new_string: str,
) -> str:
    """
    ファイル内の指定文字列を新しい文字列に置換します。
    
    Args:
        path: ファイルパス
        old_string: 置換対象の文字列(ユニークである必要あり)
        new_string: 置換後の文字列
    
    Returns:
        編集結果のメッセージ
    """

5.2.10 ls — ディレクトリ一覧

ディレクトリ内のファイルとサブディレクトリを一覧表示する。

@tool
async def ls(
    path: str = "/workspace",
    all: bool = False,
    long: bool = False,
) -> str:
    """
    ディレクトリ内容を一覧表示します。
    
    Args:
        path: ディレクトリパス
        all: 隠しファイルも表示するか
        long: 詳細情報を表示するか
    
    Returns:
        ファイル・ディレクトリの一覧
    """

5.2.11 glob — ファイルパターンマッチング

ファイル名のパターンに基づいてファイルを検索する。

@tool
async def glob(
    pattern: str,
    path: str = "/workspace",
) -> str:
    """
    グロブパターンでファイルを検索します。
    
    Args:
        pattern: 検索パターン(例: "**/*.ts", "src/**/*.py")
        path: 検索のベースディレクトリ
    
    Returns:
        マッチしたファイルパスの一覧
    """

5.2.12 grep — コンテンツ検索

ファイル内容を正規表現で検索する。

@tool
async def grep(
    pattern: str,
    path: str = "/workspace",
    include: str | None = None,
    context_lines: int = 3,
) -> str:
    """
    ファイル内容を正規表現で検索します。
    
    Args:
        pattern: 検索正規表現パターン
        path: 検索対象パスまたはディレクトリ
        include: 対象ファイルのグロブパターン(例: "*.py")
        context_lines: マッチ前後のコンテキスト行数
    
    Returns:
        マッチしたファイル・行の一覧
    """

5.2.13 write_todos — タスク整理

実行計画をTODOリストとして構造化する。エージェントが自身の作業を整理し、進捗を追跡するために使用する。

@tool
async def write_todos(
    todos: list[dict],
) -> str:
    """
    TODOリストを作成・更新します。
    
    Args:
        todos: TODOアイテムのリスト
            [{"task": "説明", "status": "pending|done|skipped"}]
    
    Returns:
        更新されたTODOリスト
    """

使用例:

await write_todos(todos=[
    {"task": "UserService.ts のバグ箇所を特定する", "status": "done"},
    {"task": "null チェックを追加する", "status": "done"},
    {"task": "ユニットテストを作成する", "status": "pending"},
    {"task": "リンターを実行する", "status": "pending"},
    {"task": "PR を作成する", "status": "pending"},
])

5.2.14 task — サブエージェント起動

独立したサブエージェントを起動し、部分的なタスクを並列で処理する。詳細はオーケストレーションのセクションで後述する。

@tool
async def task(
    description: str,
    tools: list[str] | None = None,
    context: str | None = None,
) -> str:
    """
    サブエージェントを起動して部分的なタスクを実行します。
    
    Args:
        description: サブエージェントに与えるタスクの説明
        tools: サブエージェントが使用可能なツールのリスト
        context: 追加のコンテキスト情報
    
    Returns:
        サブエージェントの実行結果
    """

5.3 ツールの実行フロー

[LLM がツールコールを生成]
        ↓
[ToolErrorMiddleware による前処理]
        ↓
[パラメータのバリデーション]
        ↓
[サンドボックス内で実行]
        ↓
[結果の整形]
        ↓
[ToolErrorMiddleware による後処理]
        ↓
[LLM に結果をフィードバック]

5.4 カスタムツールの追加

Open SWE にカスタムツールを追加する方法は以下の通りである。

from langchain_core.tools import tool

@tool
async def query_internal_database(
    query: str,
    database: str = "production_readonly",
) -> str:
    """
    社内データベースに読み取り専用クエリを実行します。
    
    Args:
        query: SQLクエリ(SELECT のみ許可)
        database: データベース名
    
    Returns:
        クエリ結果(JSON形式)
    """
    if not query.strip().upper().startswith("SELECT"):
        return "エラー: SELECT クエリのみ許可されています"
    
    result = await db_client.execute(query, database=database)
    return json.dumps(result, indent=2, ensure_ascii=False)

# ハーネスに追加
config = HarnessConfig(
    tools=[*default_swe_tools, query_internal_database],
)

6. コンテキストエンジニアリング — AGENTS.md とソースコンテキスト

6.1 コンテキストエンジニアリングとは

コンテキストエンジニアリングとは、LLM に渡すコンテキスト情報を戦略的に設計・最適化する技術である。LLM の性能は、与えられるコンテキストの質と量に大きく依存する。Open SWE は、エージェントが正確かつ効率的にコーディングタスクを遂行するために、以下の3種類のコンテキストを統合的に管理する。

  1. プロジェクトコンテキスト(AGENTS.md): プロジェクト固有のコーディング規約、アーキテクチャ、テスト要件
  2. ソースコンテキスト: 現在のコードベースの構造、関連ファイル、依存関係
  3. タスクコンテキスト: イシューの履歴、スレッドの会話、関連するPRやコメント

6.2 AGENTS.md — プロジェクト固有の指示書

AGENTS.md は、リポジトリのルートに配置するマークダウンファイルであり、エージェントに対するプロジェクト固有の指示を記述する。Human が読む README.md のエージェント版とも言える。

# AGENTS.md

## プロジェクト概要
このリポジトリは、E-Commerceプラットフォームのバックエンドサービスです。
NestJS + TypeScript + PostgreSQL で構成されています。

## コーディング規約

### 言語とスタイル
- TypeScript strict mode を使用
- ESLint + Prettier の設定に従う
- インデントはスペース2つ
- 文字列はシングルクォートを使用

### 命名規則
- ファイル名: kebab-case(例: `user-service.ts`- クラス名: PascalCase(例: `UserService`- 変数・関数名: camelCase(例: `getUserById`- 定数: UPPER_SNAKE_CASE(例: `MAX_RETRY_COUNT`- データベーステーブル名: snake_case(例: `user_orders`)

### ディレクトリ構造

src/ ├── modules/ # 機能モジュール(ドメイン単位) │ ├── users/ │ │ ├── users.controller.ts │ │ ├── users.service.ts │ │ ├── users.module.ts │ │ ├── dto/ │ │ ├── entities/ │ │ └── tests/ │ └── orders/ ├── common/ # 共通ユーティリティ ├── config/ # 設定ファイル └── database/ # マイグレーション、シード


## テスト要件

### 実行コマンド
- ユニットテスト: `npm test`
- E2Eテスト: `npm run test:e2e`
- カバレッジ: `npm run test:cov`

### テスト方針
- 全ての新機能にユニットテストを追加すること
- カバレッジ 80% 以上を維持すること
- モックは `jest.mock()` を使用
- テストファイルは `__tests__/` ディレクトリに配置

### テストの書き方
```typescript
describe('UserService', () => {
  let service: UserService;
  let repository: MockRepository<User>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        { provide: getRepositoryToken(User), useClass: MockRepository },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
    repository = module.get(getRepositoryToken(User));
  });

  it('should return user by id', async () => {
    const mockUser = createMockUser({ id: '123' });
    repository.findOne.mockResolvedValue(mockUser);
    
    const result = await service.findById('123');
    expect(result).toEqual(mockUser);
  });
});

アーキテクチャ

パターン

  • MVC パターンに従う
  • 依存性注入(DI)を使用
  • リポジトリパターンでデータアクセスを抽象化

データベース

  • PostgreSQL 15
  • TypeORM を使用
  • マイグレーションは npm run migration:generate で生成

認証

  • JWT ベースの認証
  • @UseGuards(JwtAuthGuard) でエンドポイントを保護

コミットメッセージ規約

  • Conventional Commits に従う
  • 形式: <type>(<scope>): <description>
  • 例: fix(users): handle null user in getProfile
  • 型: feat, fix, docs, style, refactor, test, chore

レビュー時の注意

  • セキュリティに関する変更は @security-team をレビュアーに追加
  • データベーススキーマの変更は @dba-team をレビュアーに追加
  • 破壊的変更は PR に breaking-change ラベルを付与

### 6.3 AGENTS.md のベストプラクティス

効果的な AGENTS.md を作成するためのガイドラインは以下の通りである。

| 項目 | 推奨 | 非推奨 |
|------|------|--------|
| **具体性** | 具体的なコマンド、パスを記述 | 曖昧な表現(「適切に」「必要に応じて」) |
| **構造化** | 見出し・リスト・テーブルで整理 | 自由記述の長文 |
| **コード例** | 正しいコードの例を含める | テキストのみでの説明 |
| **更新頻度** | コーディング規約の変更時に更新 | 一度作って放置 |
| **対象範囲** | エージェントに必要な情報のみ | 人間向けの詳細なドキュメント全体 |

### 6.4 ソースコンテキストの構築

エージェントがタスクに着手する前に、関連するソースコードのコンテキストが自動的に構築される。

```python
async def build_source_context(
    sandbox: SandboxSession,
    task_description: str,
    max_tokens: int = 50000,
) -> str:
    """ソースコンテキストを構築する"""
    context_parts = []
    
    # 1. リポジトリ構造の概要
    tree_output = await sandbox.execute("find /workspace -type f | head -200")
    context_parts.append(f"## リポジトリ構造\n```\n{tree_output}\n```")
    
    # 2. AGENTS.md の読み込み
    agents_md = await sandbox.read_file("/workspace/AGENTS.md")
    if agents_md:
        context_parts.append(f"## AGENTS.md\n{agents_md}")
    
    # 3. パッケージ情報
    for pkg_file in ["package.json", "pyproject.toml", "Cargo.toml", "go.mod"]:
        try:
            content = await sandbox.read_file(f"/workspace/{pkg_file}")
            context_parts.append(f"## {pkg_file}\n```\n{content}\n```")
        except FileNotFoundError:
            continue
    
    # 4. タスク関連ファイルの推定と読み込み
    relevant_files = await identify_relevant_files(
        sandbox, task_description, max_files=10
    )
    for file_path in relevant_files:
        content = await sandbox.read_file(file_path)
        context_parts.append(
            f"## {file_path}\n```\n{content}\n```"
        )
    
    return "\n\n".join(context_parts)

6.5 タスクコンテキストの統合

Slack メッセージや Linear イシューから呼び出された場合、会話履歴やイシューの詳細情報がコンテキストとして統合される。

async def build_task_context(
    invocation_source: InvocationSource,
) -> TaskContext:
    """タスクコンテキストを構築する"""
    
    if isinstance(invocation_source, SlackInvocation):
        # Slack スレッドの全メッセージを取得
        thread_history = await slack_client.get_thread(
            channel=invocation_source.channel_id,
            thread_ts=invocation_source.thread_ts,
        )
        return TaskContext(
            source="slack",
            messages=thread_history,
            repository=invocation_source.repository,
            task_description=invocation_source.task,
        )
    
    elif isinstance(invocation_source, LinearInvocation):
        # Linear イシューの詳細と履歴を取得
        issue = await linear_client.get_issue(invocation_source.issue_id)
        comments = await linear_client.get_comments(invocation_source.issue_id)
        return TaskContext(
            source="linear",
            issue_title=issue.title,
            issue_description=issue.description,
            comments=comments,
            labels=issue.labels,
            repository=issue.repository,
        )
    
    elif isinstance(invocation_source, GitHubInvocation):
        # PR コメントのコンテキスト
        pr = await github_client.get_pr(
            owner=invocation_source.owner,
            repo=invocation_source.repo,
            pr_number=invocation_source.pr_number,
        )
        return TaskContext(
            source="github",
            pr_title=pr.title,
            pr_body=pr.body,
            pr_diff=pr.diff,
            comment=invocation_source.comment,
            repository=f"{invocation_source.owner}/{invocation_source.repo}",
        )

6.6 コンテキストウィンドウの管理

LLM のコンテキストウィンドウには上限があるため(Claude Opus 4.6 の場合は約200Kトークン)、コンテキストの優先順位付けと圧縮が重要となる。

class ContextManager:
    """コンテキストウィンドウの使用量を管理する"""
    
    def __init__(self, max_tokens: int = 150000):
        self.max_tokens = max_tokens
        self.priority_order = [
            "system_prompt",      # 最優先: システムプロンプト
            "agents_md",           # 高: プロジェクト規約
            "task_description",    # 高: タスクの説明
            "relevant_code",       # 中: 関連コード
            "thread_history",      # 中: 会話履歴
            "repository_structure",# 低: リポジトリ構造
        ]
    
    def build_context(self, context_parts: dict[str, str]) -> str:
        """優先順位に従ってコンテキストを構築する"""
        result = []
        remaining_tokens = self.max_tokens
        
        for key in self.priority_order:
            if key in context_parts:
                part = context_parts[key]
                token_count = estimate_tokens(part)
                
                if token_count <= remaining_tokens:
                    result.append(part)
                    remaining_tokens -= token_count
                else:
                    # トークン制限に達した場合は要約
                    summarized = summarize_content(
                        part, max_tokens=remaining_tokens
                    )
                    result.append(summarized)
                    break
        
        return "\n\n".join(result)

7. オーケストレーション — サブエージェントとミドルウェア

7.1 オーケストレーションの概要

Open SWE のオーケストレーション層は、エージェントの実行フローを制御する2つの主要メカニズムを提供する。

  1. サブエージェント: task ツールを通じた並列タスク処理
  2. ミドルウェア: エージェントループへの介入ポイント

7.2 サブエージェントの仕組み

task ツールを使用すると、メインエージェントとは独立した新しいエージェントが起動される。サブエージェントは親エージェントと同じサンドボックスを共有するが、独自のコンテキストウィンドウと実行状態を持つ。

# メインエージェントがサブエージェントを起動する例
async def handle_complex_task():
    # メインタスク: フロントエンドとバックエンドの両方に変更が必要
    
    # サブエージェント1: バックエンドの変更
    backend_result = await task(
        description="""
        UserService に新しいエンドポイント GET /api/users/:id/preferences 
        を追加してください。
        - src/modules/users/ 配下に実装
        - ユニットテストも追加
        - 既存のコーディング規約に従う
        """,
        tools=["read_file", "write_file", "edit_file", "execute", "grep", "ls"],
    )
    
    # サブエージェント2: フロントエンドの変更
    frontend_result = await task(
        description="""
        ユーザー設定ページのUIコンポーネントを作成してください。
        - src/components/UserPreferences/ 配下に実装
        - 新しいバックエンドAPIと連携
        - Storybookのストーリーも追加
        """,
        tools=["read_file", "write_file", "edit_file", "execute", "grep", "ls"],
    )

7.3 サブエージェントの実行モデル

                    ┌─────────────────────┐
                    │   メインエージェント   │
                    │  (Parent Agent)      │
                    └──────────┬──────────┘
                               │
                    ┌──────────┴──────────┐
                    │     task ツール呼出    │
                    └──────────┬──────────┘
                               │
              ┌────────────────┼────────────────┐
              ▼                ▼                ▼
    ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
    │ サブエージェント1  │ │ サブエージェント2  │ │ サブエージェント3  │
    │ (Backend)       │ │ (Frontend)      │ │ (Tests)         │
    └────────┬────────┘ └────────┬────────┘ └────────┬────────┘
             │                   │                   │
             ▼                   ▼                   ▼
    ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
    │  共有サンドボックス │ │  共有サンドボックス │ │  共有サンドボックス │
    │  /workspace     │ │  /workspace     │ │  /workspace     │
    └────────────────┘ └────────────────┘ └────────────────┘

サブエージェントの主な特徴:

  • 独立したコンテキストウィンドウ: 親エージェントのコンテキストに影響されず、タスクに集中できる
  • 共有ファイルシステム: サンドボックスのファイルシステムを共有するため、相互に変更を参照可能
  • スコープ制限: 使用可能なツールを制限し、サブエージェントの行動範囲を制御できる
  • 結果の統合: サブエージェントの結果は親エージェントに返却され、全体の作業に統合される

7.4 ミドルウェア層の詳細

ミドルウェアは、エージェントの実行ループの特定のポイントに介入するフックである。Open SWE には3つのデフォルトミドルウェアが組み込まれている。

# ミドルウェアの設定
middleware = [
    check_message_queue_before_model,  # LLM 呼出前のメッセージキューチェック
    open_pr_if_needed,                  # 必要時の自動PR作成
    ToolErrorMiddleware(),              # ツールエラーの優雅な処理
]

7.4.1 check_message_queue_before_model

このミドルウェアは、LLM を呼び出す前にメッセージキューをチェックする。エージェントの実行中にユーザーが追加の指示やフィードバックを送信した場合、それを次の LLM 呼出に含める。

async def check_message_queue_before_model(
    state: AgentState,
    config: RunnableConfig,
) -> AgentState:
    """LLM呼出前にメッセージキューをチェックする"""
    
    # メッセージキューから新しいメッセージを取得
    new_messages = await message_queue.get_pending(
        thread_id=config["configurable"]["thread_id"]
    )
    
    if new_messages:
        # 新しいメッセージをエージェントの状態に追加
        return {
            **state,
            "messages": state["messages"] + [
                HumanMessage(content=msg.content)
                for msg in new_messages
            ],
        }
    
    return state

このミドルウェアにより、以下のユーザー体験が実現される。

[ユーザー] repo:my-org/api バグを修正して
[エージェント] (作業開始)
[ユーザー] あ、src/utils.ts のバグだよ   ← 実行中に追加情報
[エージェント] (新しい情報を取り込んで作業続行)

7.4.2 open_pr_if_needed

このミドルウェアは、エージェントが commit_and_open_pr ツールを明示的に呼ばなかった場合のフォールバックとして機能する。エージェントが変更をコミットしたもののPRを作成せずにタスクを完了しようとした場合、自動的にPRを作成する。

async def open_pr_if_needed(
    state: AgentState,
    config: RunnableConfig,
) -> AgentState:
    """タスク完了時にPRが未作成の場合、自動的に作成する"""
    
    # エージェントがタスク完了を示しているか
    if not is_task_completing(state):
        return state
    
    # 未コミットの変更があるか
    diff = await state["sandbox"].execute("git diff --stat")
    staged = await state["sandbox"].execute("git diff --cached --stat")
    
    if diff.stdout.strip() or staged.stdout.strip():
        # 変更をコミット
        await state["sandbox"].execute('git add -A')
        await state["sandbox"].execute(
            'git commit -m "fix: automated changes by Open SWE agent"'
        )
    
    # PRが未作成か
    if state.get("pr_url") is None:
        # 未プッシュのコミットがあるか
        unpushed = await state["sandbox"].execute(
            "git log origin/main..HEAD --oneline"
        )
        if unpushed.stdout.strip():
            pr_url = await create_pull_request(
                sandbox=state["sandbox"],
                title=generate_pr_title(state),
                body=generate_pr_body(state),
            )
            return {**state, "pr_url": pr_url}
    
    return state

7.4.3 ToolErrorMiddleware

ツール実行時のエラーを捕捉し、エージェントが理解可能な形に変換するミドルウェアである。前述のセクション3.5で詳しく解説した。

7.5 カスタムミドルウェアの実装

独自のミドルウェアを実装して、エージェントの振る舞いを拡張できる。

class AuditLoggingMiddleware:
    """全てのツール呼び出しを監査ログに記録するミドルウェア"""
    
    def __init__(self, audit_service: AuditService):
        self.audit_service = audit_service
    
    async def before_tool(self, state, tool_call):
        """ツール実行前のフック"""
        await self.audit_service.log(
            event="tool_invocation",
            tool_name=tool_call.name,
            args=tool_call.args,
            agent_id=state.get("agent_id"),
            timestamp=datetime.utcnow(),
        )
    
    async def after_tool(self, state, tool_call, result):
        """ツール実行後のフック"""
        await self.audit_service.log(
            event="tool_result",
            tool_name=tool_call.name,
            success=not result.is_error,
            agent_id=state.get("agent_id"),
            timestamp=datetime.utcnow(),
        )


class CostControlMiddleware:
    """LLM呼び出しのコストを監視し、上限に達したら停止するミドルウェア"""
    
    def __init__(self, max_cost_usd: float = 10.0):
        self.max_cost_usd = max_cost_usd
        self.total_cost = 0.0
    
    async def before_model(self, state, config):
        if self.total_cost >= self.max_cost_usd:
            raise AgentCostLimitExceeded(
                f"コスト上限に達しました: ${self.total_cost:.2f} / ${self.max_cost_usd:.2f}"
            )
        return state
    
    async def after_model(self, state, response):
        usage = response.usage_metadata
        cost = calculate_cost(
            input_tokens=usage.input_tokens,
            output_tokens=usage.output_tokens,
            model="claude-opus-4-6",
        )
        self.total_cost += cost
        return state

8. 呼び出しインターフェース — Slack・Linear・GitHub 統合

8.1 呼び出しインターフェースの概要

Open SWE は、エンジニアが日常的に使用する3つのプラットフォームからエージェントを呼び出すことができる。

プラットフォーム呼び出し方法主な用途
Slackrepo:owner/name + タスク説明バグ修正、機能追加、質問
Linear@openswe メンションチケット起票からの自動対応
GitHub@openswe PRコメントPRへのフィードバック対応

8.2 Slack 統合

8.2.1 基本的な使い方

Slack チャンネルまたは DM で、以下の形式でエージェントを呼び出す。

repo:my-org/my-api UserService の getProfile メソッドで、
ユーザーが見つからない場合に null ポインタ例外が発生しています。
適切なエラーハンドリングを追加してください。

このメッセージを検知すると、Open SWE は以下のフローを開始する。

  1. repo:owner/name パターンを検出し、リポジトリを特定
  2. メッセージの残り部分をタスク説明として解析
  3. サンドボックスを起動し、リポジトリをクローン
  4. タスクを実行
  5. 結果を Slack スレッドに返信

8.2.2 Slack Bot の設定

# Slack Bot の設定例
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode import AsyncSocketModeHandler

app = AsyncApp(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"],
)

# repo: パターンを含むメッセージを監視
@app.message(re.compile(r"repo:(\S+/\S+)"))
async def handle_repo_message(message, say, context):
    """repo: パターンを含むメッセージを処理する"""
    match = context["matches"][0]
    repo_url = f"https://github.com/{match}"
    
    # タスク説明を抽出(repo:... パターンを除いた部分)
    task_description = re.sub(
        r"repo:\S+/\S+\s*", "", message["text"]
    ).strip()
    
    # 処理中メッセージを投稿
    await say(
        text=f"リポジトリ `{match}` に対するタスクを開始します...",
        thread_ts=message["ts"],
    )
    
    # エージェントを起動
    result = await run_agent(
        repo_url=repo_url,
        task_description=task_description,
        invocation=SlackInvocation(
            channel_id=message["channel"],
            thread_ts=message["ts"],
            user_id=message["user"],
        ),
    )
    
    # 結果を返信
    await say(
        text=format_result(result),
        thread_ts=message["ts"],
    )

8.2.3 Slack でのインタラクション例

[Engineer] repo:acme-corp/payment-service 
           決済処理で金額が0以下の場合のバリデーションが
           欠けています。修正お願いします。

[Open SWE] リポジトリ `acme-corp/payment-service` に対する
           タスクを開始します...

[Open SWE] 📋 作業計画:
           1. 決済処理のコードを確認
           2. バリデーションロジックを追加
           3. ユニットテストを作成
           4. 既存テストの実行確認
           5. PRを作成

[Open SWE] 🔍 `src/services/payment.service.ts` を分析中...

[Open SWE] ✅ 変更完了:
           - `src/services/payment.service.ts`: 
             金額バリデーション追加
           - `src/services/__tests__/payment.service.test.ts`: 
             テスト3件追加
           - 全テスト通過 (42/42)

           PR: https://github.com/acme-corp/payment-service/pull/89
           
           レビューをお願いします。

[Engineer] ありがとう!ただ、バリデーションエラー時のHTTPステータスコードは
           400ではなく422を使ってほしい。

[Open SWE] 承知しました。HTTPステータスコードを 
           400 Bad Request から 422 Unprocessable Entity に
           変更します...

[Open SWE] ✅ PRを更新しました:
           - ステータスコードを422に変更
           - テストも更新済み
           
           PR: https://github.com/acme-corp/payment-service/pull/89

8.3 Linear 統合

8.3.1 基本的な使い方

Linear のイシューコメントで @openswe をメンションすることで、エージェントが自動的にイシューの内容に基づいてタスクを実行する。

@openswe このイシューに対応してください。
バックエンドの修正が必要です。

8.3.2 Linear Webhook の設定

# Linear Webhook の設定例
from fastapi import FastAPI, Request
import hmac
import hashlib

app = FastAPI()

@app.post("/webhooks/linear")
async def handle_linear_webhook(request: Request):
    """Linear Webhookを処理する"""
    body = await request.body()
    
    # 署名の検証
    signature = request.headers.get("Linear-Signature")
    expected = hmac.new(
        os.environ["LINEAR_WEBHOOK_SECRET"].encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
    
    if not hmac.compare_digest(signature, expected):
        return {"error": "Invalid signature"}, 401
    
    payload = await request.json()
    
    # @openswe メンションを含むコメントを検出
    if (
        payload["type"] == "Comment"
        and payload["action"] == "create"
        and "@openswe" in payload["data"]["body"]
    ):
        issue_id = payload["data"]["issueId"]
        comment_body = payload["data"]["body"]
        
        # イシュー情報の取得
        issue = await linear_client.get_issue(issue_id)
        
        # リポジトリの特定
        repo_url = extract_repo_from_issue(issue)
        
        # エージェントの起動
        await run_agent(
            repo_url=repo_url,
            task_description=f"{issue.title}\n{issue.description}",
            invocation=LinearInvocation(
                issue_id=issue_id,
                comment_body=comment_body,
            ),
        )
    
    return {"ok": True}

8.3.3 Linear でのワークフロー

[Linear Issue] BUG-1234: ユーザー登録時のメールバリデーションが不正
  ├── Description: RFC 5322 に準拠しないメールアドレスが
  │                登録できてしまう問題
  ├── Labels: bug, backend
  └── Project: Payment Platform v2.0

[Comment by Engineer]
  @openswe このバグを修正してください。
  src/validators/ 配下のバリデーターを確認してください。

[Comment by Open SWE]
  イシュー BUG-1234 への対応を開始します。

  ## 分析結果
  - `src/validators/email.validator.ts` のバリデーション正規表現に問題を発見
  - RFC 5322 に準拠した正規表現に更新
  - エッジケースのテストを追加

  ## 変更ファイル
  - `src/validators/email.validator.ts`
  - `src/validators/__tests__/email.validator.test.ts`

  ## PR
  https://github.com/acme-corp/api/pull/456

  全テスト通過を確認しました。レビューをお願いします。

8.4 GitHub 統合

8.4.1 基本的な使い方

GitHub の PR コメントで @openswe をメンションすると、エージェントが PR の内容に基づいて対応する。

@openswe このPRのレビューコメントに対応してください。
型安全性を改善し、テストを追加してください。

8.4.2 GitHub App / Webhook の設定

# GitHub Webhook の設定例
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhooks/github")
async def handle_github_webhook(request: Request):
    """GitHub Webhookを処理する"""
    payload = await request.json()
    event_type = request.headers.get("X-GitHub-Event")
    
    if event_type == "issue_comment":
        # PRコメントの処理
        comment = payload["comment"]
        
        if "@openswe" in comment["body"]:
            pr_number = payload["issue"]["number"]
            repo = payload["repository"]["full_name"]
            
            # PRの差分を取得
            pr_diff = await github_client.get_pr_diff(repo, pr_number)
            
            # レビューコメントを取得
            reviews = await github_client.get_pr_reviews(repo, pr_number)
            
            # エージェントの起動
            await run_agent(
                repo_url=f"https://github.com/{repo}",
                task_description=comment["body"],
                invocation=GitHubInvocation(
                    owner=repo.split("/")[0],
                    repo=repo.split("/")[1],
                    pr_number=pr_number,
                    comment=comment["body"],
                    pr_diff=pr_diff,
                    reviews=reviews,
                ),
            )
    
    return {"ok": True}

8.4.3 GitHub でのインタラクション例

[PR #123] feat: add user preferences endpoint

[Review Comment by @tech-lead]
  エラーレスポンスの型定義が不足しています。
  また、入力バリデーションのテストケースが
  足りないように見えます。

[Comment by @engineer]
  @openswe レビューコメントに対応してください。

[Comment by @openswe]
  レビューコメントへの対応を開始します。

  ## 対応内容
  1. エラーレスポンスの型定義を追加
     - `src/types/error-response.ts` を新規作成
     - 既存のエンドポイントにも型を適用

  2. 入力バリデーションのテストケースを追加
     - 空文字列のバリデーション
     - 最大長超過のバリデーション
     - 不正な文字のバリデーション

  コミット: abc1234
  全テスト通過 (56/56)

8.5 呼び出しインターフェースの比較

特徴SlackLinearGitHub
トリガーrepo:owner/name@openswe コメント@openswe PRコメント
コンテキストスレッド履歴イシュー詳細+コメントPR差分+レビュー
応答先スレッド返信イシューコメントPRコメント
中間報告リアルタイムスレッド更新コメント追加コメント追加
PR作成新規PR作成新規PR作成既存PRへのコミット
会話継続スレッド内で追加指示可コメントで追加指示可コメントで追加指示可

9. バリデーション — 自動品質保証

9.1 バリデーションの重要性

AIエージェントが生成したコードは、人間が書いたコードと同様(場合によってはそれ以上に)品質チェックが必要である。Open SWE は、変更をコミットする前に自動的にバリデーションを実行し、品質基準を満たさないコードがプルリクエストに含まれることを防ぐ。

9.2 バリデーションパイプライン

[コード変更完了]
        ↓
[Stage 1: フォーマッター]
  prettier / black / rustfmt / gofmt
        ↓
[Stage 2: リンター]
  eslint / pylint / clippy / golangci-lint
        ↓
[Stage 3: 型チェック]
  tsc --noEmit / mypy / cargo check
        ↓
[Stage 4: テスト]
  npm test / pytest / cargo test / go test
        ↓
[全パス?]
  ├── Yes → コミット & PR作成
  └── No  → エラーを分析して修正を試行
              ↓
         [修正後、再度バリデーション]
              ↓
         [最大リトライ回数に達した?]
           ├── No  → Stage 1 に戻る
           └── Yes → 部分的な修正でPR作成(エラーを本文に記載)

9.3 バリデーションの実装

async def run_validation(
    sandbox: SandboxSession,
    agents_md: str | None = None,
) -> ValidationResult:
    """バリデーションパイプラインを実行する"""
    
    results = []
    
    # AGENTS.md からカスタムコマンドを抽出
    custom_commands = parse_validation_commands(agents_md)
    
    # Stage 1: フォーマッター
    formatter_cmds = custom_commands.get("formatters", detect_formatters(sandbox))
    for cmd in formatter_cmds:
        result = await sandbox.execute(cmd, timeout=60)
        results.append(ValidationStep(
            stage="formatter",
            command=cmd,
            passed=result.exit_code == 0,
            output=result.stdout + result.stderr,
        ))
    
    # Stage 2: リンター
    linter_cmds = custom_commands.get("linters", detect_linters(sandbox))
    for cmd in linter_cmds:
        result = await sandbox.execute(cmd, timeout=120)
        results.append(ValidationStep(
            stage="linter",
            command=cmd,
            passed=result.exit_code == 0,
            output=result.stdout + result.stderr,
        ))
    
    # Stage 3: 型チェック
    type_cmds = custom_commands.get("type_check", detect_type_checkers(sandbox))
    for cmd in type_cmds:
        result = await sandbox.execute(cmd, timeout=120)
        results.append(ValidationStep(
            stage="type_check",
            command=cmd,
            passed=result.exit_code == 0,
            output=result.stdout + result.stderr,
        ))
    
    # Stage 4: テスト
    test_cmds = custom_commands.get("tests", detect_test_commands(sandbox))
    for cmd in test_cmds:
        result = await sandbox.execute(cmd, timeout=300)
        results.append(ValidationStep(
            stage="test",
            command=cmd,
            passed=result.exit_code == 0,
            output=result.stdout + result.stderr,
        ))
    
    return ValidationResult(
        steps=results,
        all_passed=all(r.passed for r in results),
    )

9.4 自動検出されるバリデーションツール

Open SWE は、リポジトリの設定ファイルを分析して適切なバリデーションツールを自動検出する。

async def detect_formatters(sandbox: SandboxSession) -> list[str]:
    """リポジトリのフォーマッターを自動検出する"""
    formatters = []
    
    # Prettier(JavaScript/TypeScript)
    if await file_exists(sandbox, "/workspace/.prettierrc") or \
       await package_has_dep(sandbox, "prettier"):
        formatters.append("npx prettier --write --check .")
    
    # Black(Python)
    if await file_exists(sandbox, "/workspace/pyproject.toml"):
        pyproject = await sandbox.read_file("/workspace/pyproject.toml")
        if "black" in pyproject:
            formatters.append("black --check .")
    
    # rustfmt(Rust)
    if await file_exists(sandbox, "/workspace/Cargo.toml"):
        formatters.append("cargo fmt -- --check")
    
    # gofmt(Go)
    if await file_exists(sandbox, "/workspace/go.mod"):
        formatters.append("gofmt -l .")
    
    return formatters

9.5 バリデーション失敗時の自動修正

バリデーションが失敗した場合、エージェントはエラー内容を分析し、自動修正を試みる。

async def handle_validation_failure(
    state: AgentState,
    validation_result: ValidationResult,
    max_retries: int = 3,
) -> AgentState:
    """バリデーション失敗時の自動修正処理"""
    
    for attempt in range(max_retries):
        failed_steps = [s for s in validation_result.steps if not s.passed]
        
        # エラー情報をLLMに渡して修正を依頼
        fix_prompt = f"""
        以下のバリデーションが失敗しました。修正してください。
        
        {chr(10).join(
            f"### {step.stage}: {step.command}\n```\n{step.output}\n```"
            for step in failed_steps
        )}
        """
        
        # LLM による修正
        state = await agent_fix_loop(state, fix_prompt)
        
        # 再バリデーション
        validation_result = await run_validation(state["sandbox"])
        
        if validation_result.all_passed:
            return state
    
    # 最大リトライ回数に達した場合
    logger.warning(f"バリデーション修正に失敗({max_retries}回試行)")
    state["validation_warnings"] = [
        s.output for s in validation_result.steps if not s.passed
    ]
    return state

10. PR 自動作成 — コミットからプルリクエストまで

10.1 PR 作成の全体フロー

Open SWE のPR自動作成は、以下の多段階プロセスで実行される。

[変更の確認]
    ↓ git status / git diff
[ステージング]
    ↓ git add -A
[コミットメッセージ生成]
    ↓ LLM が変更内容を分析してメッセージを生成
[コミット]
    ↓ git commit -m "..."
[ブランチプッシュ]
    ↓ git push origin agent/fix-branch
[PR 本文生成]
    ↓ LLM が変更の概要、テスト結果を含むPR本文を生成
[PR 作成]
    ↓ GitHub API でPR作成
[ラベル・レビュアー設定]
    ↓ AGENTS.md のルールに基づいて設定
[完了通知]
    ↓ 呼び出し元(Slack/Linear/GitHub)に結果を返信

10.2 commit_and_open_pr ツールの内部実装

async def _commit_and_open_pr_impl(
    sandbox: SandboxSession,
    title: str,
    body: str,
    commit_message: str,
    branch_name: str,
    base_branch: str,
    draft: bool,
    labels: list[str] | None,
    reviewers: list[str] | None,
) -> str:
    """PR作成の内部実装"""
    
    # 1. 変更のステージング
    await sandbox.execute("git add -A")
    
    # 2. 変更の確認
    diff_stat = await sandbox.execute("git diff --cached --stat")
    if not diff_stat.stdout.strip():
        return "コミットする変更がありません。"
    
    # 3. コミット
    # コミットメッセージ内の特殊文字をエスケープ
    escaped_message = commit_message.replace('"', '\\"')
    commit_result = await sandbox.execute(
        f'git commit -m "{escaped_message}"'
    )
    
    if commit_result.exit_code != 0:
        return f"コミットに失敗しました: {commit_result.stderr}"
    
    # 4. ブランチのプッシュ
    push_result = await sandbox.execute(
        f"git push -u origin {branch_name}"
    )
    
    if push_result.exit_code != 0:
        # フォースプッシュは行わない(安全性のため)
        return f"プッシュに失敗しました: {push_result.stderr}"
    
    # 5. PR の作成(GitHub API)
    pr = await github_client.create_pull_request(
        owner=repo_owner,
        repo=repo_name,
        title=title,
        body=body,
        head=branch_name,
        base=base_branch,
        draft=draft,
    )
    
    # 6. ラベルの設定
    if labels:
        await github_client.add_labels(
            owner=repo_owner,
            repo=repo_name,
            issue_number=pr.number,
            labels=labels,
        )
    
    # 7. レビュアーの設定
    if reviewers:
        await github_client.request_reviewers(
            owner=repo_owner,
            repo=repo_name,
            pr_number=pr.number,
            reviewers=reviewers,
        )
    
    return pr.html_url

10.3 PR 本文の自動生成

Open SWE は、変更内容を分析して構造化されたPR本文を自動生成する。

async def generate_pr_body(
    state: AgentState,
    diff: str,
    validation_result: ValidationResult,
) -> str:
    """PR本文を自動生成する"""
    
    # 変更ファイルの一覧
    changed_files = parse_diff_stat(diff)
    
    # テスト結果の整形
    test_results = format_test_results(validation_result)
    
    body = f"""
## 概要

{state['task_description']}

## 変更内容

{chr(10).join(f'- `{f.path}`: {f.summary}' for f in changed_files)}

## テスト結果

{test_results}

## 変更の詳細

{generate_change_details(changed_files)}

---

> このPRは [Open SWE](https://github.com/langchain-ai/open-swe) 
> エージェントによって自動生成されました。
> 
> 呼び出し元: {state['invocation_source']}
> モデル: {state.get('model', 'anthropic:claude-opus-4-6')}
"""
    
    return body

生成されるPR本文の例:

## 概要

UserService の getProfile メソッドで発生していた null ポインタ例外を修正しました。

## 変更内容

- `src/services/user.service.ts`: null チェックの追加、エラーハンドリングの改善
- `src/services/__tests__/user.service.test.ts`: null ケースのテスト3件追加
- `src/types/error-response.ts`: エラーレスポンス型の新規追加

## テスト結果

| スイート | テスト数 | パス | 失敗 | 結果 |
|---------|---------|------|------|------|
| UserService | 15 | 15 | 0 | PASS |
| AuthService | 8 | 8 | 0 | PASS |
| OrderService | 12 | 12 | 0 | PASS |
| **合計** | **35** | **35** | **0** | **ALL PASS** |

## 変更の詳細

### `src/services/user.service.ts`
- `getProfile()` メソッドに null チェックを追加
- ユーザーが見つからない場合は `NotFoundException` をスロー
- エラーレスポンスに適切な型を適用

### `src/services/__tests__/user.service.test.ts`
- テストケース追加: "should throw NotFoundException when user is null"
- テストケース追加: "should throw NotFoundException when user id is invalid"
- テストケース追加: "should return user profile when user exists"

---

> このPRは Open SWE エージェントによって自動生成されました。
> 呼び出し元: Slack (#engineering)
> モデル: anthropic:claude-opus-4-6

10.4 open_pr_if_needed ミドルウェアの動作

open_pr_if_needed ミドルウェアは、エージェントが明示的にPRを作成しなかった場合のセーフティネットとして機能する。このミドルウェアの動作条件は以下の通りである。

条件動作
変更なし + PR未作成何もしない
変更あり + PR作成済み何もしない
変更あり + PR未作成 + コミット済み自動でPR作成
変更あり + PR未作成 + 未コミット自動でコミット後にPR作成

11. LLM モデル統合 — Claude Opus 4.6 とモデル切り替え

11.1 デフォルトモデル: Claude Opus 4.6

Open SWE のデフォルト LLM は anthropic:claude-opus-4-6 である。Claude Opus 4.6 は、Anthropic が提供する最高性能のモデルであり、以下の特徴を持つ。

特徴詳細
コンテキストウィンドウ200K トークン(約100万文字相当)
コーディング能力SWE-bench で最高水準のスコア
推論能力複雑な多段階推論に優れる
指示追従性長く複雑な指示にも正確に従う
ツール使用Function Calling / Tool Use に高い精度

Claude Opus 4.6 が Open SWE のデフォルトとして選ばれた理由は、以下の3点である。

  1. コーディングベンチマークでの高スコア: SWE-bench、HumanEval 等のベンチマークで一貫して高い性能を示す
  2. 大きなコンテキストウィンドウ: リポジトリ全体の構造と関連コードを同時にコンテキストに含められる
  3. 信頼性の高いツール使用: ツールコールの精度が高く、パラメータのミスが少ない

11.2 モデルの切り替え

Open SWE は、LLM のモデルを柔軟に切り替えることができる。

# 環境変数での設定
# .env ファイル
OPEN_SWE_MODEL=anthropic:claude-opus-4-6

# 他のモデルへの切り替え例
OPEN_SWE_MODEL=anthropic:claude-sonnet-4     # コスト重視
OPEN_SWE_MODEL=openai:gpt-4o                  # OpenAI使用
OPEN_SWE_MODEL=openai:o3                      # 推論強化モデル
# コードでの設定
from open_swe.config import AgentConfig

config = AgentConfig(
    model="anthropic:claude-opus-4-6",
    # または
    model="anthropic:claude-sonnet-4",
    
    # モデル固有のパラメータ
    model_kwargs={
        "temperature": 0,
        "max_tokens": 16384,
    },
)

11.3 モデル選択のガイドライン

ユースケース推奨モデル理由
複雑なバグ修正Claude Opus 4.6高度な推論能力が必要
単純なリファクタリングClaude Sonnet 4コスト効率が良い
大規模コードベースClaude Opus 4.6大きなコンテキストウィンドウ
高速応答が必要Claude Sonnet 4レイテンシが低い
実験・テスト用Claude Haiku最もコスト効率が良い

11.4 コスト最適化戦略

class AdaptiveModelSelector:
    """タスクの複雑さに応じてモデルを自動選択する"""
    
    def select_model(self, task: TaskContext) -> str:
        complexity = self.estimate_complexity(task)
        
        if complexity == "high":
            return "anthropic:claude-opus-4-6"
        elif complexity == "medium":
            return "anthropic:claude-sonnet-4"
        else:
            return "anthropic:claude-haiku"
    
    def estimate_complexity(self, task: TaskContext) -> str:
        # ファイル数、変更の種類、テスト要件などから複雑さを推定
        indicators = {
            "multi_file": task.estimated_files > 3,
            "test_required": "test" in task.description.lower(),
            "architecture_change": any(
                kw in task.description.lower()
                for kw in ["refactor", "migrate", "redesign"]
            ),
            "security_related": any(
                kw in task.description.lower()
                for kw in ["security", "auth", "permission", "vulnerability"]
            ),
        }
        
        high_indicators = sum(indicators.values())
        
        if high_indicators >= 3:
            return "high"
        elif high_indicators >= 1:
            return "medium"
        else:
            return "low"

11.5 サブエージェントのモデル設定

メインエージェントとサブエージェントで異なるモデルを使用することも可能である。コスト最適化のため、サブエージェントには軽量なモデルを使用する戦略がある。

config = HarnessConfig(
    # メインエージェント: 高性能モデル
    model="anthropic:claude-opus-4-6",
    
    # サブエージェント: コスト効率の良いモデル
    subagent_model="anthropic:claude-sonnet-4",
)

12. デプロイメント — Docker と LangGraph 設定

12.1 デプロイメントの概要

Open SWE は、Docker コンテナとしてデプロイされる。LangGraph のデプロイメントインフラストラクチャを利用し、スケーラブルかつ管理しやすい形でエージェントを運用できる。

12.2 Dockerfile の構成

# Open SWE の Dockerfile
FROM python:3.11-slim

# システム依存パッケージのインストール
RUN apt-get update && apt-get install -y \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリの設定
WORKDIR /app

# 依存関係のインストール
COPY pyproject.toml .
RUN pip install --no-cache-dir -e .

# アプリケーションコードのコピー
COPY src/ src/
COPY langgraph.json .

# LangGraph サーバーのポート
EXPOSE 8123

# LangGraph サーバーの起動
CMD ["langgraph", "up", "--host", "0.0.0.0", "--port", "8123"]

12.3 LangGraph JSON 設定の詳細

{
  "graphs": {
    "agent": "./src/open_swe/graph.py:graph"
  },
  "env": {
    "ANTHROPIC_API_KEY": "",
    "GITHUB_TOKEN": "",
    "SANDBOX_PROVIDER": "modal",
    "LANGSMITH_API_KEY": "",
    "LANGSMITH_PROJECT": "open-swe-production",
    "SLACK_BOT_TOKEN": "",
    "SLACK_SIGNING_SECRET": "",
    "LINEAR_API_KEY": "",
    "LINEAR_WEBHOOK_SECRET": ""
  },
  "dependencies": ["."],
  "dockerfile_lines": [
    "RUN apt-get update && apt-get install -y git curl"
  ]
}

12.4 ローカル開発環境のセットアップ

# 1. リポジトリのクローン
git clone https://github.com/langchain-ai/open-swe.git
cd open-swe

# 2. 仮想環境の作成
python -m venv .venv
source .venv/bin/activate

# 3. 依存関係のインストール
pip install -e ".[dev]"

# 4. 環境変数の設定
cp .env.example .env
# .env ファイルを編集して必要なAPIキーを設定

# 5. ローカルでの起動
langgraph dev

# 6. テストの実行
pytest tests/

12.5 本番環境へのデプロイ

# LangGraph Cloud へのデプロイ
langgraph deploy \
  --config langgraph.json \
  --name open-swe-production \
  --region us-east-1

# Docker Compose によるセルフホスティング
docker compose up -d

docker-compose.yml の例:

version: "3.8"

services:
  open-swe:
    build: .
    ports:
      - "8123:8123"
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - GITHUB_TOKEN=${GITHUB_TOKEN}
      - SANDBOX_PROVIDER=modal
      - MODAL_TOKEN_ID=${MODAL_TOKEN_ID}
      - MODAL_TOKEN_SECRET=${MODAL_TOKEN_SECRET}
      - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN}
      - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}
      - LANGSMITH_API_KEY=${LANGSMITH_API_KEY}
      - LANGSMITH_PROJECT=open-swe
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8123/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: open_swe
      POSTGRES_USER: open_swe
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - "5432:5432"
    volumes:
      - pg-data:/var/lib/postgresql/data

volumes:
  redis-data:
  pg-data:

12.6 スケーリング戦略

方式説明適用場面
水平スケーリングコンテナのレプリカ数を増やすリクエスト数が多い場合
垂直スケーリングコンテナのリソース(CPU/メモリ)を増やす個々のタスクが重い場合
キューベースタスクキューで非同期処理バースト的なリクエスト
リージョン分散複数リージョンにデプロイグローバルチームの場合

13. カスタマイズ — 拡張ポイントの全体像

13.1 カスタマイズの哲学

Open SWE のカスタマイズは「コンポジション」の原則に基づいている。フレームワークのコードを直接変更するのではなく、設定やプラグインの形で振る舞いを拡張する。これにより、アップストリームのアップデートを取り込みながら独自のカスタマイズを維持できる。

13.2 カスタマイズポイント一覧

カスタマイズポイント方法影響範囲
サンドボックスプロバイダーBaseSandboxProvider の実装実行環境
ツール@tool デコレータで新規追加エージェントの能力
ミドルウェアミドルウェアクラスの実装実行フロー制御
システムプロンプト設定ファイルで変更エージェントの振る舞い
LLM モデル環境変数で切り替え推論品質・コスト
呼び出しインターフェースWebhook ハンドラの追加トリガー方法
バリデーションルールAGENTS.md に記述品質基準
PR テンプレートテンプレート関数のオーバーライドPR の形式

13.3 サンドボックスプロバイダーのカスタマイズ

社内の既存インフラストラクチャを活用したカスタムサンドボックスプロバイダーの実装例を示す。

from open_swe.sandbox.base import BaseSandboxProvider, SandboxSession
import kubernetes_asyncio as k8s

class KubernetesSandboxProvider(BaseSandboxProvider):
    """社内Kubernetesクラスタを使用するサンドボックスプロバイダー"""
    
    def __init__(
        self,
        namespace: str = "open-swe",
        image: str = "internal-registry.corp/open-swe-sandbox:latest",
        resource_limits: dict | None = None,
    ):
        self.namespace = namespace
        self.image = image
        self.resource_limits = resource_limits or {
            "cpu": "4",
            "memory": "8Gi",
            "ephemeral-storage": "50Gi",
        }
        self.k8s_client = None
    
    async def initialize(self):
        """Kubernetes クライアントの初期化"""
        await k8s.config.load_incluster_config()
        self.k8s_client = k8s.client.CoreV1Api()
    
    async def create_session(
        self,
        repo_url: str,
        branch: str = "main",
        env_vars: dict[str, str] | None = None,
    ) -> SandboxSession:
        """サンドボックスセッションの作成"""
        
        pod_name = f"swe-sandbox-{uuid.uuid4().hex[:8]}"
        
        # Pod マニフェストの構築
        pod_manifest = k8s.client.V1Pod(
            metadata=k8s.client.V1ObjectMeta(
                name=pod_name,
                namespace=self.namespace,
                labels={"app": "open-swe-sandbox"},
            ),
            spec=k8s.client.V1PodSpec(
                containers=[
                    k8s.client.V1Container(
                        name="sandbox",
                        image=self.image,
                        resources=k8s.client.V1ResourceRequirements(
                            limits=self.resource_limits,
                        ),
                        env=[
                            k8s.client.V1EnvVar(name="REPO_URL", value=repo_url),
                            k8s.client.V1EnvVar(name="BRANCH", value=branch),
                        ] + [
                            k8s.client.V1EnvVar(name=k, value=v)
                            for k, v in (env_vars or {}).items()
                        ],
                    ),
                ],
                restart_policy="Never",
                # 自動クリーンアップ: 1時間後に削除
                active_deadline_seconds=3600,
            ),
        )
        
        # Pod の作成
        await self.k8s_client.create_namespaced_pod(
            namespace=self.namespace,
            body=pod_manifest,
        )
        
        # Pod の準備完了を待機
        await self._wait_for_pod_ready(pod_name)
        
        return SandboxSession(
            session_id=pod_name,
            execute=lambda cmd, **kw: self._exec_in_pod(pod_name, cmd, **kw),
            read_file=lambda path: self._read_from_pod(pod_name, path),
            write_file=lambda path, content: self._write_to_pod(
                pod_name, path, content
            ),
            cleanup=lambda: self._delete_pod(pod_name),
        )

13.4 カスタムツールの追加例

社内システムと連携するカスタムツールの実装例をいくつか示す。

# 社内ドキュメント検索ツール
@tool
async def search_internal_docs(
    query: str,
    category: str | None = None,
    max_results: int = 5,
) -> str:
    """
    社内ドキュメントを検索します。
    
    Args:
        query: 検索クエリ
        category: カテゴリフィルタ(api, architecture, runbook)
        max_results: 最大結果数
    
    Returns:
        関連ドキュメントの一覧(タイトル、URL、要約)
    """
    results = await internal_search_api.search(
        query=query,
        category=category,
        limit=max_results,
    )
    return format_search_results(results)


# CI/CD パイプラインステータスチェックツール
@tool
async def check_ci_status(
    branch: str = "main",
    pipeline: str | None = None,
) -> str:
    """
    CI/CDパイプラインのステータスを確認します。
    
    Args:
        branch: チェック対象のブランチ
        pipeline: 特定のパイプライン名
    
    Returns:
        パイプラインのステータス情報
    """
    status = await ci_client.get_pipeline_status(
        branch=branch,
        pipeline=pipeline,
    )
    return json.dumps(status, indent=2)


# 社内Slackチャンネル通知ツール
@tool
async def notify_team_channel(
    channel: str,
    message: str,
    mention_users: list[str] | None = None,
) -> str:
    """
    チームのSlackチャンネルに通知を送信します。
    
    Args:
        channel: チャンネル名(#を除く)
        message: 通知メッセージ
        mention_users: メンションするユーザーのリスト
    
    Returns:
        送信結果
    """
    if mention_users:
        mentions = " ".join(f"<@{user}>" for user in mention_users)
        message = f"{mentions}\n{message}"
    
    result = await slack_client.post_message(
        channel=f"#{channel}",
        text=message,
    )
    return f"通知を送信しました: {result.ts}"

13.5 システムプロンプトのカスタマイズ

# 社内向けにカスタマイズしたシステムプロンプト
CUSTOM_SYSTEM_PROMPT = """
あなたは {company_name} の社内コーディングエージェントです。

## 基本方針
- すべてのコードは社内コーディング標準に従うこと
- セキュリティに関する変更は必ずセキュリティチームの承認を得ること
- データベーススキーマの変更は DBA チームに通知すること
- 本番環境のデータに直接アクセスしないこと

## コミット規約
- Conventional Commits 形式を使用
- チケット番号を含める(例: "fix(PROJ-123): ...")

## テスト方針
- 新機能には必ずユニットテストを追加
- カバレッジ 80% 以上を維持
- E2E テストが存在する場合は実行

## PR 作成ルール
- Draft PR として作成
- ラベル "agent-generated" を必ず付与
- テスト結果をPR本文に含める
- 変更ファイルが5つ以上の場合は tech-lead をレビュアーに追加

## 禁止事項
- .env ファイルの変更
- secrets/ ディレクトリのファイル操作
- package-lock.json / yarn.lock の手動変更
- force push
"""

config = HarnessConfig(
    system_prompt=CUSTOM_SYSTEM_PROMPT.format(company_name="Acme Corp"),
)

13.6 呼び出しインターフェースの拡張

新しい呼び出しインターフェースを追加する例として、Microsoft Teams 統合を示す。

from open_swe.invocations.base import BaseInvocation

class TeamsInvocation(BaseInvocation):
    """Microsoft Teams からの呼び出し"""
    
    source = "teams"
    
    def __init__(
        self,
        conversation_id: str,
        message_id: str,
        user_id: str,
        repository: str,
        task_description: str,
    ):
        self.conversation_id = conversation_id
        self.message_id = message_id
        self.user_id = user_id
        self.repository = repository
        self.task_description = task_description
    
    async def send_update(self, message: str):
        """Teams に進捗報告を送信"""
        await teams_client.reply_to_message(
            conversation_id=self.conversation_id,
            message_id=self.message_id,
            text=message,
        )
    
    async def send_result(self, result: AgentResult):
        """Teams に最終結果を送信"""
        card = self.build_adaptive_card(result)
        await teams_client.reply_to_message(
            conversation_id=self.conversation_id,
            message_id=self.message_id,
            attachments=[card],
        )

14. セキュリティ — サンドボックス隔離と本番環境保護

14.1 セキュリティモデルの概要

Open SWE のセキュリティモデルは、「最小権限の原則」と「多層防御」に基づいている。

┌─────────────────────────────────────────────────────────────┐
│                    アクセス制御層                              │
│  (API キー認証 / Webhook 署名検証 / ユーザー権限チェック)       │
├─────────────────────────────────────────────────────────────┤
│                    ネットワーク隔離層                          │
│  (サンドボックスのネットワークポリシー / 許可リスト)             │
├─────────────────────────────────────────────────────────────┤
│                    実行隔離層                                 │
│  (コンテナ隔離 / ファイルシステム制限 / リソース制限)           │
├─────────────────────────────────────────────────────────────┤
│                    出力制御層                                 │
│  (PR レビュー必須 / 自動バリデーション / 監査ログ)              │
└─────────────────────────────────────────────────────────────┘

14.2 サンドボックス隔離の詳細

14.2.1 ファイルシステムの隔離

# サンドボックスのファイルシステムポリシー
SANDBOX_FS_POLICY = {
    # 読み書き可能
    "readwrite": [
        "/workspace/**",       # リポジトリの作業ディレクトリ
        "/tmp/**",             # 一時ファイル
        "/home/agent/**",      # エージェントのホーム
    ],
    
    # 読み取り専用
    "readonly": [
        "/usr/**",             # システムバイナリ
        "/etc/ssl/**",         # SSL証明書
    ],
    
    # アクセス禁止
    "denied": [
        "/etc/shadow",         # パスワードファイル
        "/etc/passwd",         # ユーザー情報(制限的に)
        "/root/**",            # rootのホーム
        "/var/log/**",         # システムログ
    ],
}

14.2.2 ネットワークポリシー

# サンドボックスのネットワークポリシー
SANDBOX_NETWORK_POLICY = {
    # 許可するアウトバウンド通信
    "egress_allow": [
        "github.com:443",              # GitHub API
        "api.github.com:443",          # GitHub API
        "registry.npmjs.org:443",      # npm レジストリ
        "pypi.org:443",                # PyPI
        "rubygems.org:443",            # RubyGems
        "crates.io:443",               # Cargo レジストリ
    ],
    
    # 拒否するアウトバウンド通信
    "egress_deny": [
        "*:22",                        # SSH(デフォルト拒否)
        "*.internal.corp:*",           # 社内ネットワーク
        "169.254.169.254:*",           # クラウドメタデータ
    ],
    
    # インバウンド通信は全面拒否
    "ingress_deny": ["*:*"],
}

14.3 シークレット管理

# シークレットの安全な管理
class SecretManager:
    """エージェントが使用するシークレットを安全に管理する"""
    
    # エージェントに直接渡さないシークレット
    AGENT_RESTRICTED_SECRETS = [
        "ANTHROPIC_API_KEY",    # LLM API キー
        "SLACK_BOT_TOKEN",      # Slack Bot トークン
        "LINEAR_API_KEY",       # Linear API キー
        "MODAL_TOKEN_SECRET",   # Modal シークレット
    ]
    
    # エージェントのサンドボックスに渡すシークレット
    SANDBOX_ALLOWED_SECRETS = [
        "GITHUB_TOKEN",         # GitHub トークン(スコープ制限済み)
        "NPM_TOKEN",           # npm レジストリトークン(読み取り専用)
    ]
    
    def get_sandbox_env(self) -> dict[str, str]:
        """サンドボックスに渡す環境変数を取得する"""
        env = {}
        for key in self.SANDBOX_ALLOWED_SECRETS:
            value = os.environ.get(key)
            if value:
                env[key] = value
        return env

14.4 GitHub トークンのスコープ制限

# GitHub App のパーミッション設定(推奨)
permissions:
  # リポジトリ
  contents: write         # コードの読み書き(ブランチ作成に必要)
  pull-requests: write     # PR の作成・更新
  issues: read             # イシューの読み取り
  metadata: read           # リポジトリメタデータ
  
  # 不要(付与しない)
  # administration: なし    # リポジトリ設定の変更
  # actions: なし           # GitHub Actions の管理
  # secrets: なし           # リポジトリシークレット
  # environments: なし      # デプロイ環境
  # pages: なし             # GitHub Pages

14.5 監査ログ

class AuditLogger:
    """全てのエージェント操作を記録する監査ログ"""
    
    async def log_event(self, event: AuditEvent):
        """監査イベントを記録する"""
        record = {
            "timestamp": datetime.utcnow().isoformat(),
            "event_type": event.type,
            "agent_id": event.agent_id,
            "user_id": event.user_id,
            "repository": event.repository,
            "action": event.action,
            "details": event.details,
            "risk_level": event.risk_level,
        }
        
        # 高リスクイベントは即座にアラート
        if event.risk_level == "high":
            await self.send_alert(record)
        
        # 全イベントをログストアに保存
        await self.log_store.insert(record)
    
    async def send_alert(self, record: dict):
        """高リスクイベントのアラートを送信する"""
        await slack_client.post_message(
            channel="#security-alerts",
            text=f"🚨 高リスクエージェント操作検出:\n"
            f"```{json.dumps(record, indent=2)}```",
        )

14.6 セキュリティチェックリスト

Open SWE を本番環境にデプロイする前に確認すべきセキュリティチェックリストを以下に示す。

カテゴリチェック項目重要度
認証API キーは環境変数で管理しているか必須
認証Webhook の署名検証が有効か必須
認証GitHub トークンのスコープは最小限か必須
隔離サンドボックスのネットワークポリシーが設定されているか必須
隔離サンドボックスのリソース制限が設定されているか必須
隔離本番環境への直接アクセスが遮断されているか必須
監査全操作の監査ログが有効か推奨
監査高リスク操作のアラートが設定されているか推奨
出力PR の自動マージが無効化されているか必須
出力PR に "agent-generated" ラベルが付与されるか推奨
シークレットエージェントに不要なシークレットを渡していないか必須
シークレットサンドボックス内にシークレットが残留しないか必須

15. 実践的な使用例 — ワークフローの具体例

15.1 使用例1: Slack 経由でのバグ修正

シナリオ

E-Commerce プラットフォームの決済サービスで、特定の条件下で金額計算が正しくないというバグ報告が上がっている。エンジニアは Slack から Open SWE を呼び出してバグ修正を依頼する。

ステップバイステップの流れ

Step 1: Slack でのタスク依頼

[Engineer @tanaka]
repo:acme-corp/payment-service
決済金額の計算で、割引クーポンが適用された場合に
消費税の計算が割引前の金額に基づいて行われています。
割引後の金額に対して消費税を計算するよう修正してください。
関連チケット: PAY-4567

Step 2: エージェントの初期化

[Open SWE]
タスクを受け付けました。リポジトリ `acme-corp/payment-service` 
に対する修正を開始します。

📋 作業計画:
1. 決済金額計算のコードを確認
2. 消費税計算ロジックの特定
3. 割引適用後の金額で消費税を計算するよう修正
4. 関連するユニットテストの更新・追加
5. 全テストの実行確認
6. PRの作成

Step 3: コード分析

エージェントは以下のツールを順に使用してコードを分析する。

# エージェントの内部動作(概念的な表現)

# 1. リポジトリ構造の確認
await ls(path="/workspace/src")
# → services/, controllers/, models/, utils/, ...

# 2. 関連ファイルの検索
await grep(
    pattern="tax|消費税|calculateTax",
    path="/workspace/src",
    include="*.ts"
)
# → src/services/pricing.service.ts:45: calculateTax(amount)
# → src/services/pricing.service.ts:78: applyDiscount(amount, coupon)
# → src/utils/tax-calculator.ts:12: export function calculateTax(...)

# 3. 該当ファイルの読み取り
await read_file(path="/workspace/src/services/pricing.service.ts")

Step 4: バグの特定と修正

# バグ箇所の特定
# pricing.service.ts の calculateOrderTotal() メソッド内:
#
# 現在のコード(バグあり):
#   const tax = calculateTax(subtotal)          // 割引前の金額で計算
#   const discount = applyDiscount(subtotal, coupon)
#   const total = subtotal - discount + tax
#
# 正しいコード:
#   const discount = applyDiscount(subtotal, coupon)
#   const discountedAmount = subtotal - discount
#   const tax = calculateTax(discountedAmount)   // 割引後の金額で計算
#   const total = discountedAmount + tax

# 修正の適用
await edit_file(
    path="/workspace/src/services/pricing.service.ts",
    old_string="""    const tax = calculateTax(subtotal);
    const discount = applyDiscount(subtotal, coupon);
    const total = subtotal - discount + tax;""",
    new_string="""    const discount = applyDiscount(subtotal, coupon);
    const discountedAmount = subtotal - discount;
    const tax = calculateTax(discountedAmount);
    const total = discountedAmount + tax;""",
)

Step 5: テストの追加

# テストファイルの編集
await edit_file(
    path="/workspace/src/services/__tests__/pricing.service.test.ts",
    old_string="  // 消費税計算テスト(ここに追加)",
    new_string="""  describe('消費税計算(割引適用時)', () => {
    it('割引後の金額に対して消費税を計算すること', async () => {
      const order = createMockOrder({
        items: [{ price: 10000, quantity: 1 }],
        coupon: { type: 'percentage', value: 10 }, // 10%割引
      });
      
      const result = await service.calculateOrderTotal(order);
      
      // 小計: 10000円
      // 割引: 1000円(10%)
      // 割引後金額: 9000円
      // 消費税(10%): 900円
      // 合計: 9900円
      expect(result.subtotal).toBe(10000);
      expect(result.discount).toBe(1000);
      expect(result.tax).toBe(900);  // 10000*0.1=1000 ではなく 9000*0.1=900
      expect(result.total).toBe(9900);
    });

    it('割引なしの場合は小計に対して消費税を計算すること', async () => {
      const order = createMockOrder({
        items: [{ price: 10000, quantity: 1 }],
        coupon: null,
      });
      
      const result = await service.calculateOrderTotal(order);
      
      expect(result.tax).toBe(1000);
      expect(result.total).toBe(11000);
    });

    it('100%割引の場合は消費税が0になること', async () => {
      const order = createMockOrder({
        items: [{ price: 10000, quantity: 1 }],
        coupon: { type: 'percentage', value: 100 },
      });
      
      const result = await service.calculateOrderTotal(order);
      
      expect(result.tax).toBe(0);
      expect(result.total).toBe(0);
    });
  });""",
)

Step 6: バリデーションの実行

# テストの実行
await execute(command="npm test")
# → Tests: 48 passed, 0 failed

# リンターの実行
await execute(command="npx eslint src/ --fix")
# → No errors found

# 型チェック
await execute(command="npx tsc --noEmit")
# → No errors

Step 7: PR の作成

await commit_and_open_pr(
    title="fix(pricing): calculate tax on discounted amount",
    body="""## 概要
割引クーポン適用時の消費税計算が割引前の金額に基づいていた
バグを修正しました。

関連チケット: PAY-4567

## 変更内容
- `src/services/pricing.service.ts`: 消費税計算の順序を修正
  - 割引適用後の金額に対して消費税を計算するように変更
- `src/services/__tests__/pricing.service.test.ts`: テスト3件追加
  - 割引適用時の消費税計算
  - 割引なしの場合
  - 100%割引の場合

## テスト結果
全48テスト パス(新規3件を含む)

## 検証方法

npm test

""",
    commit_message="fix(pricing): calculate tax on discounted amount (PAY-4567)",
    labels=["bug", "agent-generated", "payment"],
    reviewers=["payment-team-lead"],
)

Step 8: Slack への結果報告

[Open SWE]
✅ バグ修正が完了しました。

## 変更概要
- 割引クーポン適用時に消費税が割引前の金額で計算されていた
  バグを修正
- 割引後の金額に対して消費税を計算するようにロジックを変更
- テスト3件を追加(全48テスト パス)

## PR
https://github.com/acme-corp/payment-service/pull/234

レビューをお願いします。@payment-team-lead

15.2 使用例2: Linear 起票からの PR 自動作成

シナリオ

プロダクトマネージャーが Linear にイシューを起票し、エンジニアが @openswe でエージェントに対応を依頼する。

Linear イシュー:

Title: ユーザープロフィールにアバター画像アップロード機能を追加
Labels: feature, frontend, backend
Priority: Medium
Description:
  ユーザーがプロフィールページからアバター画像を
  アップロードできる機能を追加する。
  
  要件:
  - 画像形式: JPEG, PNG, WebP
  - 最大ファイルサイズ: 5MB
  - リサイズ: 200x200 にリサイズして保存
  - ストレージ: S3 に保存
  - API: PUT /api/users/:id/avatar

エンジニアのコメント:

@openswe バックエンドのAPIエンドポイントを実装してください。
フロントエンドは別途対応します。
リポジトリは acme-corp/user-service です。

エージェントの対応:

エージェントは Linear イシューの内容を読み取り、以下のファイルを作成・修正する。

  1. src/modules/users/users.controller.ts — エンドポイントの追加
  2. src/modules/users/users.service.ts — アバターアップロードロジック
  3. src/modules/users/dto/upload-avatar.dto.ts — DTOの定義
  4. src/common/utils/image-processor.ts — 画像リサイズユーティリティ
  5. src/modules/users/__tests__/avatar-upload.test.ts — テスト

15.3 使用例3: GitHub PR コメントでの修正対応

シナリオ

エンジニアが作成した PR に対してレビュアーからフィードバックがあり、@openswe でエージェントに修正を依頼する。

[PR #567] feat: add rate limiting middleware

[Review by @security-lead]
L23-30: レート制限の実装で、分散環境での動作が考慮されていません。
Redis を使ったレート制限に変更してください。
また、IPアドレスだけでなくユーザーIDも考慮したレート制限にしてください。

[Comment by @developer]
@openswe レビューコメントの指摘事項に対応してください。
Redis クライアントは既に src/common/redis.ts にあります。

エージェントは既存の PR ブランチ上で以下の修正を行う。

  • インメモリのレート制限を Redis ベースに変更
  • IPアドレスとユーザーIDの複合キーによるレート制限
  • テストの更新(Redis のモック使用)
  • 既存の PR に新しいコミットをプッシュ

15.4 使用例のまとめ

使用例呼び出し元入力出力
バグ修正Slackバグの説明修正PR + Slack通知
機能追加Linearイシュー詳細実装PR + Linearコメント
レビュー対応GitHubレビューコメント既存PRへのコミット
リファクタリングSlackリファクタ指示リファクタPR
テスト追加Linearテスト要件テストコードPR
ドキュメント更新Slack更新箇所の指示ドキュメントPR

16. 類似ツールとの比較 — Devin・GitHub Copilot Workspace 等

16.1 コーディングエージェントの分類

コーディングエージェントは、以下の軸で分類できる。

分類軸選択肢
提供形態SaaS / オープンソース / セルフホスト
自律性完全自律型 / 半自律型(ヒューマン・イン・ザ・ループ)
対象範囲フルスタック / バックエンド特化 / フロントエンド特化
実行環境クラウドサンドボックス / ローカル / ハイブリッド
統合先IDE / CLI / チャット(Slack等) / チケットシステム

16.2 主要ツールとの比較表

特徴Open SWEDevinGitHub Copilot WorkspaceClaude Code (CLI)Cursor Agent
提供形態オープンソースSaaSSaaSCLI ツールIDE組込
ライセンスMITプロプライエタリプロプライエタリプロプライエタリプロプライエタリ
自律性高(完全自律)高(完全自律)中(ヒューマン・イン・ザ・ループ)中(対話型)中(対話型)
実行環境クラウドサンドボックスクラウドVMクラウドローカルローカル
Slack統合ネイティブなしなしなしなし
Linear統合ネイティブ部分的なしなしなし
GitHub統合ネイティブありネイティブあり部分的
PR自動作成ありありあり手動手動
サブエージェントありありなしなしなし
カスタマイズ性
デフォルトLLMClaude Opus 4.6独自モデルGPT-4系Claude複数選択可
セルフホスト可能不可不可N/A(ローカル)N/A(ローカル)
価格無料(LLM料金別途)$500/月〜GitHub有料プラン従量課金$20/月〜

16.3 Open SWE の差別化要因

16.3.1 vs Devin

Devin は、コーディングエージェントの先駆者として市場をリードしている。Open SWE との主な違いは以下の通りである。

Open SWE の優位点:

  • オープンソース: コードベースを完全に把握でき、セキュリティ監査が可能
  • カスタマイズ性: サンドボックス、ツール、ミドルウェアをすべてカスタマイズ可能
  • セルフホスト: 社内インフラにデプロイでき、データが外部に出ない
  • コスト: フレームワーク自体は無料、LLM 料金のみ
  • LLM選択の自由: Claude、GPT-4、Gemini など任意のモデルを使用可能

Devin の優位点:

  • 実績: 多くの企業で運用実績がある
  • UI/UX: 洗練されたWebインターフェース
  • 即座に利用可能: セットアップなしで使い始められる
  • サポート: 商用サポートが利用可能

16.3.2 vs GitHub Copilot Workspace

GitHub Copilot Workspace は、GitHub が提供するコーディングエージェントである。

Open SWE の優位点:

  • マルチプラットフォーム: Slack、Linear、GitHub の3つのインターフェースをサポート
  • 完全自律実行: 人間の介入なしにPRまで作成可能
  • サブエージェント: 複雑なタスクを分割して並列処理
  • 柔軟なサンドボックス: 複数のプロバイダーから選択可能

GitHub Copilot Workspace の優位点:

  • GitHub との深い統合: Issues、PR、Actions との seamless な連携
  • GitHub エコシステム: 既存の GitHub ワークフローとの親和性
  • マネージドサービス: インフラ管理が不要

16.3.3 vs Claude Code (CLI)

Claude Code は、Anthropic が提供するターミナルベースのコーディングエージェントである。

Open SWE の優位点:

  • 非同期実行: バックグラウンドで自律的にタスクを実行
  • チーム利用: Slack 等を通じてチーム全体で利用可能
  • 自動PR作成: 変更を自動的にPRとして提出
  • プロジェクト標準の強制: AGENTS.md によるルール統制

Claude Code の優位点:

  • 対話型: リアルタイムで対話しながらコーディング
  • ローカル実行: ネットワーク不要で動作
  • 低レイテンシ: ローカルファイルへの直接アクセスで高速
  • セットアップが簡単: インストールして即座に利用可能

16.4 ツール選定の指針

ニーズ推奨ツール
チームでの非同期タスク自動化Open SWE
個人での対話型コーディングClaude Code / Cursor
即座に使える SaaSDevin
GitHub 中心のワークフローGitHub Copilot Workspace
完全なカスタマイズが必要Open SWE
セキュリティ要件が厳しいOpen SWE(セルフホスト)
予算が限られているOpen SWE(オープンソース)

17. まとめ — Open SWE の位置づけと展望

17.1 Open SWE が解決する課題

Open SWE は、ソフトウェアエンジニアリングにおける以下の課題を解決する。

課題Open SWE による解決策
ルーティン作業の負荷バグ修正、リファクタリング、テスト追加を自動化
コーディングエージェントの構築コストオープンソースフレームワークで構築コストを削減
ベンダーロックインLLM、サンドボックス、ツールをすべて差し替え可能
セキュリティ懸念サンドボックス隔離、セルフホスト、監査ログ
チームコラボレーションSlack、Linear、GitHub を通じたシームレスな統合
品質保証自動バリデーション(リンター、テスト)をコミット前に実行
カスタマイズの限界コンポジションベースのアーキテクチャで柔軟にカスタマイズ

17.2 Open SWE のアーキテクチャの要約

本記事で解説した Open SWE のアーキテクチャを改めて整理する。

┌──────────────────────────────────────────────────────────────────┐
│                         Open SWE                                 │
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │    Slack     │  │   Linear    │  │   GitHub PR Comments    │  │
│  └──────┬──────┘  └──────┬──────┘  └───────────┬─────────────┘  │
│         └────────────────┼─────────────────────┘                 │
│                          ▼                                       │
│  ┌───────────────────────────────────────────────────────────┐   │
│  │              Agent Harness (Deep Agents)                  │   │
│  │  ┌─────────────────────────────────────────────────────┐  │   │
│  │  │           Middleware Layer                          │  │   │
│  │  │  check_message_queue │ open_pr_if_needed │ errors  │  │   │
│  │  └─────────────────────────────────────────────────────┘  │   │
│  └───────────────────────────────────────────────────────────┘   │
│         │                          │                             │
│         ▼                          ▼                             │
│  ┌──────────────┐  ┌──────────────────────────────────────────┐ │
│  │   ~15 Tools  │  │        Context Engineering              │ │
│  │  execute     │  │  AGENTS.md + Source + Task Context       │ │
│  │  read/write  │  └──────────────────────────────────────────┘ │
│  │  edit/grep   │                                               │
│  │  task (sub)  │  ┌──────────────────────────────────────────┐ │
│  │  commit_pr   │  │          Sandbox Environment             │ │
│  └──────────────┘  │  Modal / Daytona / Runloop / Custom      │ │
│                     └──────────────────────────────────────────┘ │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────────┐│
│  │                    LLM Layer                                 ││
│  │           Claude Opus 4.6 (Default) / Configurable           ││
│  └──────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘

17.3 導入のロードマップ

Open SWE を組織に導入する際の推奨ロードマップを示す。

Phase 1: 評価(1-2週間)

  • ローカル環境でのセットアップと基本動作確認
  • 小規模なリポジトリでのテスト実行
  • チーム内でのデモンストレーション

Phase 2: パイロット(2-4週間)

  • 1-2チームでのパイロット運用
  • Slack 統合のセットアップ
  • AGENTS.md の作成と改善
  • カスタムツールの検討

Phase 3: 本格運用(1-2ヶ月)

  • 全チームへの展開
  • Linear / GitHub 統合の追加
  • カスタムサンドボックスプロバイダーの構築(必要に応じて)
  • 監査ログとアラートの設定

Phase 4: 最適化(継続的)

  • エージェントのパフォーマンス分析
  • コスト最適化(モデル選択の調整)
  • カスタムミドルウェアの開発
  • AGENTS.md の継続的な改善

17.4 今後の展望

Open SWE とコーディングエージェントの分野は急速に進化しており、以下のトレンドが予想される。

1. マルチエージェント協調の高度化

現在のサブエージェントパターンは比較的単純な分割・統合モデルだが、将来的にはエージェント間の対話や交渉を通じた、より高度な協調が実現されるだろう。例えば、フロントエンドエージェントとバックエンドエージェントがAPIの仕様について相互に調整するようなシナリオが考えられる。

2. 自己改善メカニズム

エージェントが過去の実行結果(成功・失敗のパターン)を学習し、同種のタスクに対するパフォーマンスを自動的に改善していくメカニズムの導入が期待される。

3. 本番環境との安全な統合

現在はプルリクエストの作成までが自動化の範囲だが、適切なガードレールを設けた上でのデプロイの自動化や、本番環境のモニタリングデータに基づく自動修正など、より広い範囲の自動化が進むだろう。

4. ドメイン特化型エージェント

汎用的なコーディングエージェントに加えて、セキュリティ監査、パフォーマンス最適化、データベースマイグレーションなど、特定のドメインに特化したエージェントの登場が予想される。Open SWE のコンポジションアーキテクチャは、こうしたドメイン特化型エージェントの基盤として最適である。

5. エンタープライズ向け機能の強化

大規模組織での運用に必要な以下の機能が強化されていくことが予想される。

  • アクセス制御の細粒度化: リポジトリ単位、ファイル単位でのアクセス制御
  • コンプライアンス対応: SOC2、ISO 27001 等の認証に対応した運用機能
  • マルチテナント対応: 複数チーム・プロジェクトの効率的な管理
  • コスト管理: チーム・プロジェクト単位でのコスト追跡と予算管理

17.5 結論

Open SWE は、LangChain エコシステムの中核プロジェクトとして、コーディングエージェントフレームワークの標準を確立しつつある。オープンソースであること、コンポジションベースのアーキテクチャであること、そして複数の呼び出しインターフェースをサポートすることが、Open SWE を他のソリューションから差別化する主要な要因である。

特に以下の3点が Open SWE の本質的な価値である。

  1. 透明性: MIT ライセンスのオープンソースプロジェクトであり、コードベースを完全に把握できる。セキュリティ監査、カスタマイズ、フォークがすべて許可されている。

  2. 拡張性: Deep Agents フレームワークに基づくコンポジションアーキテクチャにより、サンドボックスプロバイダー、ツール、ミドルウェア、呼び出しインターフェースのすべてがプラガブルに設計されている。フレームワークをフォークすることなく、独自のニーズに合わせた拡張が可能である。

  3. 実用性: Slack、Linear、GitHub という3つの主要プラットフォームとのネイティブ統合により、エンジニアは既存のワークフローを変えることなくコーディングエージェントの恩恵を受けられる。

コーディングエージェントは、ソフトウェア開発の生産性を飛躍的に向上させるポテンシャルを持つ技術であり、Open SWE はその最前線に位置している。本記事で解説したアーキテクチャ、ツール、設定の知識を活用し、自組織にコーディングエージェントを導入する第一歩を踏み出していただければ幸いである。


参考情報

リソースURL
Open SWE リポジトリhttps://github.com/langchain-ai/open-swe
LangGraph ドキュメントhttps://langchain-ai.github.io/langgraph/
Deep Agents 概要https://blog.langchain.dev/deep-agents/
LangSmithhttps://smith.langchain.com/
Anthropic Claudehttps://docs.anthropic.com/

本記事は 2026年4月時点の情報に基づいています。Open SWE は活発に開発が続けられているプロジェクトであり、最新の情報は公式リポジトリをご確認ください。