🧑‍💻

Slackからクラウドのコーディングエージェントを動かす

に公開

こんにちは、@_mkazutaka です。

タイトル通り、Slackからクラウドのコーディングエージェントを動かしてみたのでその紹介です。

もともとチケット作る際に単に作るだけでなくコードから色々踏まえてくれると新しく入った方でもチケットの精度あがるから助かるなと思ったり、軽い修正だったらコーディングエージェントなりがクラウドで環境立ててよしなにやってほしいなとおもってました。

それをやるにはClaude Code WebなりCodex WebなりをAPI経由で呼び出せると嬉しいんだがまぁないと(claude codeをkubernetesで動かすパッケージがhelmで見つけた気がするんだがリンク忘れちゃった)

という形で色々探ってはいたのですが、ちょうどWarpがOzというサービスを出してやりたいことができそうだったので、その検証と紹介です

Warp と Oz とは

Warpは、エージェント型開発環境でClaudeやらCodexやら選択して開発を進められるものになります。Ozは、そこがさらにクラウドでエージェントを動かせるためのプラットフォームになります。

https://x.com/warpdotdev/status/2021247666965958999

公式サイトによると以下のように紹介されています。

Warpはエージェント型開発環境であり、現代的なターミナル環境と強力なAIエージェントを統合したツールです。WarpのAI機能は、クラウドエージェント向けのオーケストレーションプラットフォームであるOzによって実現されています。
https://docs.warp.dev/

Ozの面白いところは、Oz Agent APIというものを出していてAPI経由でエージェントを動かすことができます。また、検証した限りはCloudflare Tunnelも動くのでクラウド上で動かしたものを外部から検証することもできる。

Oz Agent APIを使用してHTTP経由でクラウドエージェントの実行を作成・監視するか、公式に提供されているPythonおよびTypeScript SDKを利用することで、型安全なリクエスト処理、リトライ機構、構造化されたエラーハンドリングを実現できます。
https://docs.warp.dev/reference/api-and-sdk/api-and-sdk

若干クセはあるのですが、これを使えばSlack上からAPI経由でコーディングエージェントを叩けるというかたちです。

やってみる

ステップとしては以下のような形にしています

  1. Oz上で環境を作る
  2. SlackBotを作る(SlackBotからOzのAPIを呼ぶ)
  3. Cloudflare Workers上にデプロイする

Oz上の環境を作る

Ozでエージェントを動かすには、環境構築がまず必要になります。
Ozにアクセスしてサイドバーのenvironmentから以下のような図で環境を設定することができます。

Setup commandsでエージェントを実行する前の環境構築を行います。
環境構築を行う際の作業ディレクトリは、workspaceになります。連携したGitHubのRepositoryはその配下にある形にあります。

なのでRepository上でゴニョゴニョしたい場合は、cd xxxrepository をする必要があります。注意点として、設定したコマンドごとに作業ディレクトリがworkspaceディレクトリに戻るので、都度作業ディレクトリを変える必要があります(cd xxxrepository && echo Helloみたいなことをしないといけない)
このあたりは、Codex Webとは微妙に挙動が違うので注意が必要です。

また環境変数を設定するところもないので、気持ち悪いですがexportでよしなに設定する必要があったりします。

SlackBotを作る(SlackBotからOzのAPIを呼ぶ)

具体のコードは、ClaudeCodeでかけるので割愛します。ざっくりイメージと実装した注意点だけ書きます。

会話の発火

Ozの呼び出しコードは、以下のような形になります。
oz-agent-sdkを使ってライブラリから呼び出すことができます。

import OzAPI from "oz-agent-sdk";
import type { Env, OzRunResult, OzArtifact } from "../types";

export function createOzClient(env: Env): OzAPI {
  return new OzAPI({ apiKey: env.WARP_API_KEY });
}

export function buildRunConfig(env: Env): Record<string, string> {
  const config: Record<string, string> = {};
  if (env.OZ_ENVIRONMENT_ID) config.environment_id = env.OZ_ENVIRONMENT_ID;
  if (env.OZ_MODEL_ID) config.model_id = env.OZ_MODEL_ID;
  if (env.OZ_BASE_PROMPT) config.base_prompt = env.OZ_BASE_PROMPT;
  return config;
}

export async function startRun(
  client: OzAPI,
  env: Env,
  prompt: string,
  conversationId?: string,
): Promise<{ runId: string; config: Record<string, string> }> {
  const config = buildRunConfig(env);

  const result = await client.agent.run({
    prompt,
    ...(Object.keys(config).length > 0 ? { config } : {}),
    ...(conversationId ? { conversation_id: conversationId } : {}),
  } as any);
  return { runId: result.run_id, config };
}

export async function retrieveRun(client: OzAPI, runId: string): Promise<OzRunResult> {
  const run = await client.agent.runs.retrieve(runId);
  const artifacts: OzArtifact[] = (run.artifacts || []).map((a) => {
    if (a.artifact_type === "PULL_REQUEST") {
      return { type: "PULL_REQUEST", url: a.data.url, title: a.data.branch };
    }
    return { type: a.artifact_type, title: a.data.title };
  });
  return {
    state: run.state,
    sessionLink: run.session_link,
    statusMessage: run.status_message?.message,
    conversationId: run.conversation_id,
    artifacts,
  };
}

export async function cancelRun(client: OzAPI, runId: string): Promise<void> {
  await client.agent.runs.cancel(runId);
}

ここでまた注意点なのですが、ライブラリ上ではconversationIdが型として定義されているので、それを使えば一見継続して会話ができそうなのですが、APIのドキュメントにはそんなものはなく、conversationIdを指定しても新しく会話が作成されます。
継続して会話ができないので、継続して会話をする場合は、初期メッセージに以前の会話がどうだったかを伝える必要があります。そのうち実装されるかもしれませんね。

会話の取得

会話をキックしたあとは、それらの状態を定期的に取得する必要があります。
上記のretrieveRun関数で取得することができます。
が、ここでまた注意点です。retrieveRunで取得できるのは、あくまでステータス(statusMessageは今何やってるかぐらいで詳細な情報がない)なので、終わりましたぐらいの結果しか取得できません。本来やりたかったことに対しての情報のうすさがあります。

コーディングエージェントの会話の内容を詳細に取得するには、ドキュメントにないAPIを叩く必要があります。ドキュメントにない以上、ここではあまり紹介はしないのですが、簡単に説明すると、GraphQLのエンドポイントがあるので、それを叩くとprotobuf形式でレスポンスが返ってきます。それをデコードすれば完了です(ちなみにGraphQLのレスポンスをProtobuf形式で返すのスキーマの二重管理とか辛くないのかなと思ったりしたりしなかったり)。幸いにはGraphQLのエンドポイントを叩く際の認証は、APIキーをそのまま使えたりします。

Cloudflare Workers上にデプロイする

私の場合、Cloudflareでデプロイしたので、WorkersでSlackからのレスポンスをハンドリングします。
定期的に状態を見に行く必要があるので、そこからCloudflare Workflowsを発火させWorkersでの処理は終わらせます。
Cloudflare Workflowsで定期的にOz上でステータスを監視してメッセージを更新という流れにしています。

結果

Slack上できた修正依頼をそのままコーディングエージェントに投げて修正できます。便利ー。

感想

まだ一つぐらいしかPRをマージしてないのですが、修正系のものだとすごく楽ですね。あとは、PMが聞いてきた質問に対してコードを確認する必要があったりすると、もうサクッと調査してくれます。

一方でゼロから実装などのインタラクティブ性が必要なものはやりにくいなという感じです。PMが作ったチケットを実装観点で細かく分けるとかよしなに使い道は探る必要がありそう。Skillsをよしなに使えるようにしてあげれば、色々と便利になる予感はしています。

Claude CodeのRemote Controlが出てたり、インターネット越しにやりたい需要はあるはずなので、こういうのが今後増えていきそうですね。

株式会社エクスプラザ

Discussion