はじめに
今年もアドベントカレンダーの季節がやってきましたね!
突然ですが、皆さん AWS サンプルの remote-swe-agents はご存じでしょうか。
remote-swe-agentsとは GitHub と連携した完全自律型のソフトウェア開発エージェントを AWS 上で構築できるものとなります。
今年もアドベントカレンダーが始まるということで、例年通り何かアプリを開発したいと思ってネタを探していたわけですが、最近業務でその remote-swe-agents を動かしてみることがありました。
動かしてみて一言、 「すごい」 です。ボキャブラリーがなくてすみません。
このシステム、何がすごいかというと、チャットで「このバグを修正して」と指示すると、自律的に AI エージェントが GitHub のコードを読み込み、修正し、Pull Request まで作成してくれるのです。その裏では、セッションごとに EC2 インスタンスが自動で立ち上がり、エージェントが動作するという仕組み です。
さらに驚いたのが、その基盤がすべて AWS CDK(TypeScript)で表現されていて、リポジトリを Clone して少し設定をして cdk deploy を実行すれば、ものの数十分で基盤が構築できてしまう 点です。
AWS サンプルの remote-swe-agents の基盤構成図は、ざっくり以下のようになっています(GitHub から引用しています)。
私のアカウントやいくつか記事をご覧になっている方々はご存知かと思いますが、私はかれこれ 3 年間ほど Azure を触っています。AWS サンプルを動かしながら、どうしても思ってしまうわけです。
「この構成、Azure でも作れないかな!?」
ということで、NRI xPalette Advent Calendar 2025 シリーズ 1 の 9 日目は、Azure で自律型ソフトウェア開発 AI エージェントを構築してみた奮闘記 となります。
この記事は、AWS の remote-swe-agents を Azure に移行する過程での奮闘記です。完全に移行できたわけではなく、一部機能は実装中です。また、コードの品質についても、まずは動かすことを優先した「動いた」レベルであることをご理解ください。リファクタリングの余地は大いにあります!
AWS の remote-swe-agents を開発された方々、そして素晴らしいサンプルを公開してくださり、ありがとうございます。このプロジェクトがなければ、このような挑戦はできませんでした。
また、今回のソースコードは以下の GitHub に格納しておりますので、是非ご覧ください。
作成したアーキテクチャ
今回 Azure で作成したアーキテクチャは以下となります。
AWS と比較すると、以下のように各サービスを Azure のサービスに対応させています。
CloudFront は Azure Front Door に対応しますが、今回は簡略化のため対象外としています。
| 役割 | AWS サンプル | Azure リソース | 備考 |
|---|---|---|---|
| フロント・API | Lambda | App Service | コンテナでデプロイ |
| 認証 | Cognito | Microsoft Entra ID | OAuth 2.0 / MSAL |
| データベース | DynamoDB | Cosmos DB for NoSQL | チャットやセッション情報の保管 |
| ストレージ | S3 | Blob Storage | Worker ソースコード配置 |
| LLM | Bedrock | OpenAI Service | Microsoft Foundry で構築 |
| イベント配信 | EventBridge | Web PubSub | リアルタイムイベント通知 |
| VM 管理 | EC2 | Virtual Machines | セッションごとに起動 |
| VM イメージ | EC2 AMI | Compute Gallery | カスタムイメージの保管 |
| VM イメージビルド | EC2 Image Builder | VM Image Builder | テンプレートからイメージ作成 |
| コンテナレジストリ | ECR | Container Registry | Docker イメージ管理 |
| シークレット管理 | Systems Manager | Key Vault | パスワード等の秘密情報 |
| ネットワーク | VPC | Virtual Network |
どんなアプリに仕上がったか
本題に入る前に、どんなアプリに仕上がったか簡単に紹介します。
今回、ロジックは極力変更せずに Azure で構築したため、アプリケーションの UI やデザインは元の AWS サンプルとほぼ同じです。ただ、動きは変わらないものの、裏側のインフラはすべて Azure のサービスに置き換わっています。
基本的な使い方は以下の通りです:
- ログイン: Microsoft Entra ID で認証を行います
- チャット開始: 「○○ のバグを修正して」などと指示します
- 自動処理: バックグラウンドで VM が立ち上がり、AI エージェントが動作を開始します
- GitHub 連携: エージェントがコードを読み込み、修正し、Pull Request を作成します
- リアルタイム通知: Azure Web PubSub を通じて、状況がリアルタイムで画面に反映されます
実際に動かしてみた際の様子はこちらになります。
開発環境
今回開発に使用した環境は以下の通りです。
| 項目 | バージョン / 内容 |
|---|---|
| OS | Windows 11 + WSL2 |
| Node.js | v22.19.0 |
| TypeScript | 5.7.3 |
| Next.js | 15.3.3 |
| Azure CLI | 2.75.0 |
| Bicep CLI | 0.39.26 |
ディレクトリ構成
今回は AWS サンプルの構成を踏襲しつつ、Azure に合わせた構成としています。
最も大きな違いは、基盤構築が AWS CDK から Bicep に変わっている点です。
remote-swe-agents-azure/
├── packages/
│ ├── agent-core/ # 共通ロジック(Cosmos DB、Blob Storage等)
│ ├── webapp/ # Next.jsフロントエンド(App Service)
│ └── worker/ # AIエージェント処理(Azure VM)
├── bicep/
│ ├── templates/
│ │ └── main.bicep # メインテンプレート
│ ├── parameters/
│ │ └── main.bicepparam # パラメータファイル
│ └── modules/ # 各リソースのモジュール
│ ├── app-service/
│ ├── cosmos-db/
│ ├── cognitive-services/
│ ├── key-vault/
│ ├── web-pubsub/
│ └── ... (その他)
├── docker/
│ └── webapp.Dockerfile # WebAppコンテナ用
└── README.md
基盤構築編
それではまず、上記アーキテクチャに沿って基盤を構築していきます。
移行するにあたっての設計ポイント
AWS サンプルから Azure に移行するにあたり、特に重要だったポイントをいくつかピックアップして紹介します。
アプリケーション
今回、Azure App Service に Next.js アプリを Docker コンテナとしてデプロイしています。
構成図でいうと以下の赤枠の部分です。
ポイントは以下の通りです:
- アプリケーションは Azure Container Registry にイメージを格納し、デプロイする
- VNet 統合 により、Private Endpoint で保護されたリソース(Cosmos DB、Key Vault 等)にアクセス可能となる
- Key Vault からシークレット(API キー等)を 環境変数 として注入する
@Microsoft.KeyVault(VaultName=xxx;SecretName=yyy)
Bicep での実装のポイントとしては、App Service の linuxFxVersion に DOCKER|<レジストリ>.azurecr.io/<イメージ名>:<タグ> 形式で指定することで、コンテナに対応した形で App Service を作成できることです。以下にその部分だけ抽出した例を示します。
resource appService 'Microsoft.Web/sites@2024-11-01' = {
name: appServiceName
kind: 'app,linux,container'
properties: {
siteConfig: {
linuxFxVersion: 'DOCKER|<レジストリ>.azurecr.io/<イメージ名>:<タグ>'
}
}
}
今回作成した bicep/modules/app-service/app_module.bicep の全量はこちらです。パラメータは bicep/parameters/main.bicepparam を参照ください。
App Service には そのままビルドした Next.js アプリをデプロイすることもできますが、今回は AWS サンプルを踏襲してコンテナでデプロイしています。
個人的にはコンテナの方が簡単かつ他のアーキテクチャに応用が利くと感じました。
そのままデプロイする方法は Azure App Service に Next.js をデプロイする方法 などの記事を参照ください。
AI エージェント動作環境
Worker が動作する環境は、AWS サンプルと同じく VM を利用します。
構成図でいうと以下の赤枠の部分です。
以下の流れで作成・起動します:
-
Image Template を作成する
- Ubuntu 24.04 ベース
- VM に必要なライブラリやツール(GitHub CLI, Azure CLI など)をインストール
- 起動時スクリプトとして、以下のようにしておく
- Worker ソースコード(tar.gz)を Blob Storage からダウンロード
-
npx tsx src/main.tsで Worker 起動
- systemd サービスとして自動起動
-
Azure Image Builder でカスタムイメージをビルドし、Compute Gallery に保存する
Terminal
az image builder run -g <リソースグループ名> -n <イメージテンプレート名> -
WebApp から VM を起動する
- Azure SDK を使用し、WebApp から VM を起動する
- 作成時にユーザー割り当てマネージド ID を付与し、アクセス権限を付与する
実装例を紹介します。ここで肝となるのは、Image Template で指定したセットアップスクリプトと、WebApp から VM を起動する部分 です。
本章では基盤構築部分のため、Image Template のセットアップスクリプトを紹介します。このスクリプトをイメージ作成時に実行しています。
実装における工夫点やハマりポイント:
- VM イメージビルド時に Worker のソースコードをイメージに含めようとしましたが、Image Builder の実行環境ではユーザー割り当てマネージド ID が利用できず、Blob Storage へのアクセスができませんでした。そこで、ソースコードは起動時にダウンロードする方式に変更しています。
- ソースコードの更新検知に ETag を使用し、Blob Storage 上のファイルが更新されていればダウンロード・ビルドし直す仕組みを実装しています。
イベント処理
AWS では EventBridge でイベントを配信していましたが、Azure では同様のサービスとして、Azure Web PubSub を採用しました。
構成図でいうと以下の赤枠の部分です。
リアルタイムのイベント通信をこのサービスで実現しており、仕組みは以下の通りです。
- WebApp から Worker へのイベント通知(メッセージ受信、停止命令等)
- Worker から WebApp へのステータス通知(処理状況、完了通知等)
- ユーザー割り当てマネージド ID で認証し、トークンを取得
- WebSocket 接続で双方向通信
Azure Web PubSub では、グループという概念を使って Worker 固有のチャンネルを作成し、特定の Worker にのみメッセージを配信できるようにしています。
// Worker側:グループに参加
await webPubSubClient.joinGroup(`worker/${workerId}`);
// WebApp側:特定のWorkerにメッセージ送信
await serviceClient.sendToGroup(webPubSubHub, `worker/${workerId}`, event);
AI
AWS では Amazon Bedrock(Claude)を使用していましたが、Azure では Azure OpenAI Service に変更しました。また、OpenAI は Microsoft Foundry 上に構築しています。
構成図でいうと以下の赤枠の部分です。
今回使用したモデル:
- GPT-4o
- GPT-4.1
- GPT-5-mini
- o4-mini
モデルはbicep/parameters/main.bicepparamで配列定義し、Bicep でループ処理してデプロイメントを作成しています。
データベース
データベースは AWS でも NoSQL であるため、Cosmos DB for NoSQL を採用しました。
構成図でいうと以下の赤枠の部分です。
パーティションキー設計:
DynamoDB では PK(Partition Key)と SK(Sort Key)の 2 つでアイテムを管理していましたが、Cosmos DB ではパーティションキーは 1 つのみとしました。そのため、PK をパーティションキーとし、SK は通常のプロパティとして格納しています(そのため、コンテナは一つだけになっています)。
データ構造例:
| アイテム種類 | PK | SK | 説明 |
|---|---|---|---|
| セッション | sessions |
{workerId} |
チャットセッション情報 |
| メッセージ | message-{workerId} |
{timestamp} |
会話履歴 |
| トークン使用量 | token-{workerId} |
{modelId} |
モデル別トークン使用量 |
| 認証セッション | auth-sessions |
{sessionId} |
ログインセッション |
基盤デプロイ
それでは、こちらの構成を Bicep で構築します。
Bicep のソースコードの構成は、以前書いた記事の設計思想に従って作成しています。
ここからの記載は、実際のパラメータ名に沿って記載をしています。
どのパラメータのことかわからない場合は、パラメータファイル: bicep/parameters/main.bicepparam を参照してください。
事前準備
まず、Azure 環境にリソースグループを作成し、デプロイに必要な情報を集めます。
# リソースグループ作成
az group create --name m2-sakai-je-RG-01 --location japaneast
# サブスクリプションIDを取得
az account show --query id -o tsv
また、ユーザーを認証するために、Microsoft Entra ID のアプリケーション登録を行い、クライアント ID とシークレットを取得しておきます。
Bicep コードの構築
ここまでの記載でソースコードの例として何度か示しておりますが、今回は以下のファイルでインフラを定義しています。
- テンプレート: bicep/templates/main.bicep
- パラメータ: bicep/parameters/main.bicepparam
main.bicep のポイントは以下となります:
- モジュール化されたリソース定義を組み合わせて、すべてのインフラを構築する
- リソースの作成順序を考慮し、
dependsOn句で依存関係を明示的に記述する - リソースに紐づくもの(権限, Private Endpoint)はまとめて記載して可読性を向上させる
例えば、Cosmos DB 関連のリソースは以下のように呼び出しています。
(Cosmos DB アカウント → Private Endpoint → 権限 → データベース → コンテナー)
デプロイ
今回、Azure デプロイスタック を使用してデプロイします。
デプロイスタックは、リソースのライフサイクルを一元管理できる機能で、スタックから削除されたリソースを自動的に削除することができます。
以下のような deploy-stack.sh を作成し、上記の Bicep を実行するのみです。
今回はオプションとして以下のように設定しました。
-
--action-on-unmanage 'deleteResources':管理されなくなったリソースは削除する -
--deny-settings-mode 'denyDelete':作成したリソースは削除不可(編集可能)
これらをデプロイすると以下のように一度にリソースが作成されます。
カスタムイメージを作成
デプロイが完了したら、Azure Image Builder でカスタムイメージをビルドします。
# イメージテンプレートを実行
az image builder run \
--name m2-sakai-je-worker-template-01 \
--resource-group m2-sakai-je-RG-01
ビルドには 30〜40 分程度かかります(正直こんなに時間がかかるのか、と感じました)。
完了すると、事前に作成していた Compute Gallery にイメージが保存されます。
Worker のソースコードを配置
Worker インスタンス(VM)は起動時に実行するソースコードを Blob Storage から取得する設計にしています。Worker(packages/worker)と agent-core(packages/agent-core)を tar.gz に固めて、Blob Storage にアップロードします。
# ワークスペースのルートで実行
tar -czf source.tar.gz packages/agent-core packages/worker package.json package-lock.json
# Azure CLIでアップロード
az storage blob upload \
--account-name m2sakaijestorage01 \
--container-name worker-source \
--name source.tar.gz \
--file source.tar.gz \
--auth-mode login
Key Vault にシークレットを登録する
最後に、Key Vault に必要なシークレットを登録します。今回は以下の 3 つを登録します。
# 0の準備で作成したアプリケーションのクライアントシークレット
az keyvault secret set \
--vault-name m2-sakai-je-KV-01 \
--name AzureAdClientSecret \
--value "your-client-secret"
# VMの管理者パスワード
az keyvault secret set \
--vault-name m2-sakai-je-KV-01 \
--name VmAdminPassword \
--value "your-strong-password"
# Azure Container Registryのパスワード
az keyvault secret set \
--vault-name m2-sakai-je-KV-01 \
--name AcrAdminPassword \
--value "your-acr-password"
これで基盤構築は完了です!お疲れさまでした!!!
アプリ実装編
基盤ができたので、いよいよアプリケーションを実装していきます。
先ほど作成した Azure の各種リソースを使って、実際のアプリケーションコードを実装していきます。ここも一部ポイントとなっている部分を中心に紹介します。
共通部品(agent-core)
agent-core パッケージは、WebApp と Worker の両方から使用される共通ロジックです。
AWS SDK から Azure SDK への置き換え
AWS サンプルのコードを Azure に移行するにあたり、以下のように SDK を置き換えました。ここでもすべては紹介できないため、一部主要な SDK を以下に記載します。
| 用途 | AWS(一つだけ抽出) | Azure (一つだけ抽出) |
|---|---|---|
| NoSQL データベース | @aws-sdk/lib-dynamodb |
@azure/cosmos |
| ストレージ | @aws-sdk/client-s3 |
@azure/storage-blob |
| LLM | @aws-sdk/client-bedrock-runtime |
openai |
| 認証 | aws-amplify |
@azure/msal-node |
| VM 管理 | @aws-sdk/client-ec2 |
@azure/arm-compute |
| リアルタイム通信 | @aws-sdk/client-eventbridge |
@azure/web-pubsub-client |
Cosmos DB クライアントの実装
基盤構築編で作成した Cosmos DB にアクセスするためのクライアントを実装します。
実装の全体像はこちらとなっています。
実装におけるポイントは以下の通りです:
- 認証はマネージド ID(今回はユーザー割り当て)を使用する
- 一度初期化したクライアントは再利用することで性能を高める
- AWS サンプルのコードを極力変更しないため、DynamoDB 互換の関数を実装する
この Cosmos DB の実装と同じように、Blob Storage や Key Vault の接続クライアントを作成しています。
イベント処理
WebApp と Worker 間のリアルタイム通信も agent-core に実装しています。
実装の全体像はこちらとなります。
実装におけるポイントは以下の通りです:
- Managed Identity でトークンを取得し、Web PubSub REST API を呼び出す
-
channelプロパティで送信先を指定する(worker/{workerId}など) - WebApp へのイベント送信、Worker へのイベント送信を関数化する
Worker インスタンス管理
チャットでメッセージを送信すると、バックグラウンドで VM を起動する必要があります。
この VM 起動・管理ロジックも agent-core に実装しており、@azure/arm-compute SDK を使っています。
実装の全体像はこちらとなります。
VM 起動の流れは以下の通りです:
- セッション情報から
workerIdを取得する -
VM 名に
workerIdを含める(worker-{workerId}形式) -
タグに
RemoteSweWorkerIdとしてworkerIdを設定(VM 内から取得できるようにする) - Compute Gallery に保存したカスタムイメージを指定し、VM を作成して起動を待つ
実装におけるポイントは以下の通りです:
- Managed Identity(ユーザー割り当て)を VM にアタッチする
- タグで
workerIdを管理する(VM 内から IMDS で取得可能)
チャット画面(webapp)
チャット画面は Next.js 15 (App Router) で構築された Web アプリケーションです。
App Service にコンテナとしてデプロイします。
認証の実装(Cognito → Entra ID)
AWS の Cognito から Microsoft Entra ID への移行が、WebApp における大きな変更点です。
実装のポイントは以下の通りです:
- MSAL(Microsoft Authentication Library)を使用
- トークンが大きすぎて Cookie に入らない問題を Cosmos DB に保存することで解決
- 開発環境では認証をスキップできる仕組み
実装の全体像はこちらとなります。
具体的な認証フローは以下の通りです:
- ユーザーがログインボタンをクリックする
- Microsoft Entra ID の認証画面にリダイレクトする
- 認証成功後、コールバック URL に戻る
- 取得したトークンを Cosmos DB に保存する
- セッション ID のみを Cookie に保存する
これにより、Cookie サイズの問題を回避しつつ、セキュアな認証を実現しています。
agent-core の機能を呼び出し
WebApp は、agent-core パッケージの機能を呼び出すことで、VM 起動やイベント送信を実現しています。一部の例を以下に示します。
// agent-coreから関数をインポート
import { startWorker } from '@remote-swe-agents-azure/agent-core/lib/worker-manager';
import { sendWorkerEvent } from '@remote-swe-agents-azure/agent-core/lib/events';
import {
getSession,
updateSession,
} from '@remote-swe-agents-azure/agent-core/lib/sessions';
// VM起動
await startWorker(workerId);
// Workerにイベント送信
await sendWorkerEvent(workerId, {
type: 'onMessageReceived',
// ...
});
// セッション取得・更新
const session = await getSession(workerId);
await updateSession(workerId, { status: 'running' });
Docker イメージのビルド&デプロイ
WebApp のデプロイは、以下の手順で行います:
# 1. ACRにログイン
az acr login --name m2sakaijeacr01
# 2. Dockerイメージをビルド
docker build \
-f docker/webapp.Dockerfile \
-t m2sakaijeacr01.azurecr.io/remote-swe-agent-azure-webapp:<バージョンタグ> \
.
# 3. ACRにプッシュ
docker push m2sakaijeacr01.azurecr.io/remote-swe-agent-azure-webapp:<バージョンタグ>
Azure App Service では「デプロイセンター」から、レジストリとイメージタグを指定できます。設定して保存すると自動で再起動され、最新のイメージが Pull されます。
AI エージェント処理(worker)
VM 上で動作する AI エージェントのロジックです。
起動時に Blob Storage からソースコードをダウンロードし、systemd サービスとして実行されます。
VM 情報の取得(EC2 → Azure IMDS)
Worker は VM 上で動作するため、自分自身の情報を取得する必要があります。
AWS では EC2 メタデータサービスを使用していましたが、Azure では Instance Metadata Service (IMDS) を使用します。実装の全体像はこちらです。
実装におけるポイントは以下の通りです:
- IMDS は
169.254.169.254という特別な IP アドレスでアクセスする - リソースグループ名や VM 名などのメタデータを取得可能
- タグ情報も取得できるため、
workerIdもここから取得可能
Web PubSub クライアント
Worker も Web PubSub に接続し、WebApp からのイベントを受信します。
グループ機能を使って、特定の Worker にのみメッセージを配信しています。実装の全体像はこちらです。
イベント処理の仕組みは以下の通りです:
- Worker は起動時に
worker/{workerId}グループに参加する - WebApp は Worker にメッセージを送りたい場合、そのグループにブロードキャストする
- Worker はブロードキャストメッセージを受信し、
channelプロパティで自分宛か判定 - 自分のセッション宛のメッセージのみ処理する
エージェントループの実装
エージェントのメインロジックは、AWS サンプルとほぼ同じです。
おおまかな流れは以下の通りです:
- Cosmos DB からメッセージ履歴を取得する
- Azure OpenAI Service にリクエストを行い、回答を取得する(GPT-4o など)
- Tool Call があれば実行する(GitHub へのアクセス、ファイル操作など)
- 回答結果を Cosmos DB に保存する
- 回答結果を WebApp に通知する(Web PubSub 経由)
このループが自律的に回り続けることで、「指示 → 分析 → 実装 → PR 作成」という一連の流れが自動化されています。
動作確認
テスト用リポジトリの作成
まず、テスト用の GitHub リポジトリを用意します。
今回は、React で作成したシンプルな Web アプリケーションを用意しました(npm create vite で作成したデフォルトのアプリです)。
そして、GitHub の Issue に以下のような内容を作成しておきます。
ログイン
それでは、App Service の URL にアクセスし Microsoft Entra IDでログイン ボタンを押下します。すると Microsoft の認証画面が表示され、認証が成功するとトップ画面が表示されることが確認できました!
セッション作成 → エージェント回答
チャット画面で以下のように入力し、Create Session ボタンを押下します。
GitHub 以下のリポジトリについて修正を行い、Pull Request を作成してください。
- リポジトリ:https://github.com/m2-sakai/react-sample
- 要件:Issue #1
- ブランチ:feature/issue-1
すると、以下のようにチャットが表示され AI エージェントから回答が返ってきました!
要望通り、しっかり GitHub にアクセスして Pull Request を作成してくれたみたいですね。
Pull Request の確認
それでは最後に GitHub で Pull Request が作成されているか確認します。
リンクをクリックして GitHub を確認すると...
ちゃんと Pull Request が作成されています!
しっかり Issue の内容通りに、日本語版の README を作成してくれました。
こんな形で様々な Issue を AI が処理して自動で Pull Request を上げてくれるので、放っておいてもどんどん開発が進められますね!
おわりに
本記事では AWS サンプルの remote-swe-agents を Azure に移行した話をお届けしました。
はじめは移行するだけ、と高を括っていましたが、予想以上に大変でした。同じような機能を持つリソースは存在するものの、微妙に使い勝手が異なったり SDK も異なるため、一から作った部分も多かったと感じています。
まだまだ移行できていない機能もたくさんありますし、改めて remote-swe-agents のすごさを実感しました。
とはいえ、最終的に動作するものが作れて本当に良かったと思っています。
弊社のアドベントカレンダーはまだまだ続きますので、ぜひこれからも読んでいただけると幸いです!
最後まで読んでいただき、ありがとうございました!

















