こんにちは。技術推進本部の @shia です。前回は kuro の活動事例を紹介しましたので、今回はその裏側を解説していきます。
なぜ自作したのか
前回の記事を読んで「既存の SaaS やマネージドなエージェントサービスを使えばいいのでは?」と思った方もいるかもしれません。一応、いくつかの理由から自作という判断に至っています。
まず、開発着手した 2 月時点では利用できるサービスの選択肢がそもそも多くありませんでした。使えると考えたのは Devin くらいで、実際使ってもいましたが、 GitHub 上のやり取りに制約があったり、開発環境の都合などで当時はやや物足りなさを覚えてました。
次に汎用サービスよりは機能が少ないかもしれないが、自作してしまった方がもろもろ価値を出しやすいと考えました。ハーネスエンジニアリングという表現がはやるくらいにはその辺が大事なのでここは今もそこまで変わってないでしょう。一定育ったら裏側の仕組みはどうとでも差し替えできるものと考えていたのでスモールスタートとしてもちょうど良いです。
また、このタイミングで社内にエージェントを作る経験が必要だとも考えていました。まだ本当になるかはわかりませんが、未来のエンジニアは AI エージェントのハーネスの設計が主な仕事になるかも、という予想が流行っていたりしてますし、そういったエージェントを育てているという知見は今後の AI 活用の土台になると判断しました。
では、AI エージェントの設計に関してはすでにネットにたくさんの記事がありますので、会社で AI エージェントを入れるにあたって特に気にしたものをピックアップして解説します。
設計のコンセプト
kuro は社内のエンジニア、のように振る舞うことを期待しています。つまり、社のエンジニアが持てる最低権限(社内公開情報が読める、PR を提出できる、..)を付与し公開された場(e.g. Slack の公開チャンネル)で、必要とされるあらゆる作業を手伝ってくれる、という汎用寄りのエージェントになります。
権限設定は現実的な制約をもとに行なっています。まず、社内にはエージェントでいうと Notion Custom Agent のお試しくらいがされている状況の中、エージェントと一緒に仕事をしていくという体験を全員ができる状態にしたい。そして、開発時点では AI 活用においてセンシティブな情報をどう扱うかという方針がまだ定まっておらず、 DM や非公開チャンネルに対して解放するともろもろが漏れまくり DM も非公開チャンネルも意味をなさなくなります。自己学習に使われないように気を使わないといけないし、運用をしている人々は品質改善のためやりとりを眺めるんですよね。
あとは社内に便利に使える単一エージェントを提供する、というのもあります。言い換えると、ほとんどの社員が自作エージェントを作る必要を感じない状態をつくるのも目標の一つになっています。各所で自分たちのためのエージェントを作って頑張って育てるよりは kuro のような軸を置き、そこにあらゆる情報や利用を集約することが良いと判断しました。
全体の設計
kuro は nanoclaw をベースにしましたが、 Claude Agent SDK 以外はほぼ原型がないくらい変わっています。つまり Claude Agent SDK がコアの ReAct を担当し、その周りのハーネスを我々が作っています。
外側からみると、GitHub や Slack からいろんなメッセージを受け取って、それに応じてエージェントを起こし、結果をそれぞれのプラットフォームに返すという流れになっています。

クエストエージェントはメインプロセスとは別のプロセスとして起動されます。これによりプロセスに渡す環境変数の制限やタイムアウト管理、並列化などが可能になっています。
クエストとは?に関しては下で解説しますのでここでは依頼の単位、と考えてもらえれば。注目すべきは開発エージェントの存在でして、クエストエージェントとは別になっています。これは kuro 本体と実際開発環境で必要としてるものが違うところを反映するための設計になります。わかりやすい例をあげると、 kuro に開発をお願いしたとします。その時、そのままだとレポジトリをクローンして、そこに cd して〜という流れになると思いますが、このように動かすとレポジトリ側に用意している Claude 向けの各種の設定(e.g. hook, skills, …) が有効活用できないんですよね。なお作業ディレクトリを分けるとアクセスしていいパスとそうじゃないパスが区別がしやすくなるというメリットもあります。
コンテキスト管理
AI エージェントが実際に価値を生むためには、適切なコンテキストを持っていることが重要です。個人のエージェントにおいてもそうですが、公開されてない情報が大量にある会社のものは特にそうです。ここでは適切なコンテキスト生成のための工夫を紹介します。
クエスト
個人で使っているとシングルチャンネルのやりとりになりがちですが、会社だとそうともいきません。Slack でも話しかけたいし、GitHub からアサインしたりレビュー 依頼したりもしたいものです。
ただ、単純にやっていくといろいろごっちゃになります。例えば Slack からの依頼で PR を作って、その PR 上でコメントすると、PR を作っていた時の記憶がない状態でまた 1 から説明が必要になったりします。なんなら自分が作成した PR であることすら忘れてる場合もあるでしょう。
そこで kuro は「クエスト」という概念を作りました。
クエストは一つの作業単位で、複数のプラットフォームをまたいでも同じクエストなら同じセッションとして扱われます。 Slack でやりとりを始めて PR を作成するとその PR は Slack で話をしていたスレッドと同じクエストとして紐づきます。結果、 kuro は PR 上で話してても Slack での会話の文脈を覚えた状態で返答できます。

実装としてはクエスト毎に Claude Agent SDK のセッション ID を一つ作って紐づけるという感じになっています。
知識ベース
個人向けエージェントであればいわゆるエピソードメモリーがメインになることが多いですが、会社向けエージェントにおいてはサービスのドメイン知識の比重が大きくなります。具体例をあげるとサービスの使用方法、内部仕様などがそうですね。
kuro では以下の3つが主要な知識ソースになっています。
社内知識ベース(MCP 経由)
一定品質が高く、かつ重要と判断された社内ドキュメントを集めているストレージがあり、そこへの口を MCP で公開しているのでそれに接続しています。この知識ベースは例えば Notion にある特定 DB やレポジトリに乗っているドキュメントなどが含まれ定期的に更新されています。提供してる道具はとてもシンプルな RAG による検索と各ドキュメントが引けるくらいの単純な機能ですが、 kuro はこれを用いて依頼者の意図を把握する羅針盤として活用しています。わかりやすい例だと、STORES 予約の仕様に関する質問をするとこの mcp を検索し、仕様やレポジトリへの情報を得ます。それでも不十分ならここで得たレポジトリの実装を読みにいく、という感じです。
quest-memory
クエストは Claude Agent SDK の一つのセッションで管理するといいましたが、それだけでは足りない場合があります。単純にエージェントが横道にそれる可能性を減らしたり、コンテキストコンパクションによる重要な情報消失を減らすためだったり、後で参照したり、人間が進捗を管理したいから可視化されていたりすると良いんですね。クエストメモリーは一定形式でエージェントに自由に書かせており、前回の記事でもちょっとだけお見せしたスクショが実はクエストメモリーでした。

依頼者、依頼内容、完遂のためにやるべき作業、現状、成果物などを記録・更新するようにしており、これは他の依頼がきた時にも参照されます。
kuro-memory
最後に kuro 自身による自己学習のメモリーがあります。
エージェントはレスポンスの中でメモリを更新できるようになっており、次回の起動時にそれを参照することで文脈を引き継げます。
実際ログを見ていて思うこととして、クエストや kuro 本人のメモリーはタスク遂行において結構な助けになっています。例えば全く同じ依頼を手元(社内知識ベースMCPのみ)と kuro に投げてみた結果を比較すると後者の品質が良い場合が多いので、価値がある知識をいい感じに社内知識ベースに還元するのが今後の課題だったりします。
依頼者特定
GitHub と Slack を行き来していると、ID を突き合わせる必要が出てきます。ずばり GitHub のユーザー名と Slack の UID は別物なので、「この PR を作った人は Slack で誰なのか」がわからないんですよね。
kuro だけじゃなくてもいろんなところからの需要があり、このようなアカウント名を突き合わせるためのサービス、 idhub を作りました。 kuro はこのサービスを利用することで Slack、 GitHub、 Notion などに問い合わせずとも人の識別子を得ることができます。
これにより「この Slack メッセージを送った人が GitHub でどのユーザーか」「このコミットをしたのは誰で、どこに質問すればいいか」といった識別が可能になり、 PR 依頼者をレビューアに指定する、メンションするなどが実現できています。
運用の話
拡張性
前回の事例でも紹介した通り、STORES は複数のリポジトリを横断して変更することがあります。つまり、一つの開発環境で複数のクエストを処理しようとすると衝突しかねません。
そこで、開発環境を複数用意して、それをクエストごとに割り当てるようにしました。

仕組みとしてはシンプルで、あらかじめ複数の開発環境を用意しておき、クエストエージェントが spawn_dev_agent ツールを呼び出すと、ホストプロセスが空いている開発環境を取得して開発エージェントを起動します。開発エージェントはその開発環境内で作業し、結果をクエストエージェントに返します。
品質管理
AI が本当に価値を生んでいるかどうか、ちゃんと確認したいですよね。こういったシステムの評価はとても難しく、利用者の意見に依存するところがありますが、メトリックスでわかるものも多いため、いろいろなメトリックスを収集しています。
具体的には以下のようなものを取っています。
- クエストごとのコスト(USD)
- クエスト実行時間
- エージェントの成功/失敗/タイムアウトの状態
- 人間とのやりとりの回数
これに加えて、 AI による PR 数、ロールバックの数なども収集しており、それらと統合して品質の監視を試みています。溜め始めている段階で数字の見せ方が難しいのでここでは詳細を述べませんが、現時点ではロールバック頻度が大きく増えるといった問題は起きていません。
セキュリティ
前回の記事の末尾でプロンプトインジェクションの話をしましたが、そのあたりを含めてセキュリティ面での取り組みを紹介します。
呼び出せる人の制限
まず社員のみが kuro に話しかけられるようにしています。例えば Slack には別のワークスペースと接続するという Slack Connect というものがあったり、ゲストユーザ、外部トリガーで動くもの(e.g. メールを Slack チャンネルに流したり) などがあり、それら全部から kuro が利用できても困るので、明示的に制限しておくことは重要です。
ファイルの保護
CLAUDE.md やソースコードには書き込めないようにしてエージェントが自分の行動指針を書き換えたり、コードを直接変更してしまうことを防いでいます。ハーネスの挙動を kuro 自身が変更するのを許すといろいろ収拾がつかないので最初は sudo 権限を奪って systemd で動かすことで更新をブロックしたつもりになっていた…のですが、自分のソースコードを修正、ビルドして自分自身のプロセスを kill し、 systemd による再起動を起こすことでデプロイしてる様子を観測しツールレベルで修正を禁止しました。びっくりですね。で、現在は必要なら開発環境から PR を作成するようにしています。
Read は積極的に、Write は渋る
基本的な方針として、情報の読み取りは広めに許可していますが、書き込みは絞っています。例えば読取に関しては個人情報が含まれてないものに関してはほぼ許可しておりますが、書き込みに関しては社内のレポジトリに対してのベースブランチ以外への書き込み、および Notion の特定 DB への書き込みのみが許されています。今後どうやって安全に書き込みを拡大できるかは課題になっています。
ツールの使用制限
エージェントを動かすとほとんどが bypassPermission と disallowTools の組み合わせで動かすのかな〜と思いますが、 kuro はささやかな防御策として Claude Code の auto mode を真似た仕組みを実装しており、安全性を高める試みもしています。これにより不要なファイルアクセス、破壊的な変更などをもう少し賢くブロックし、プロンプトインジェクションに対する抵抗力も上げています。
注:Agent SDK の Auto モードは Team / Enterprise / Claude API からしか使用できなく、 kuro は現在 Bedrock 経由しているため、公式の auto モードは使用できておりません
こうやっていきたい
細かいバグ修正や改善は日々やっているのですが、大きめの方向性として二つあります。
開発環境の払い出し
今は固定プールで同じインスタンス上に複数の workspace を用意してやっているのですが、サーバー起動などに制限があります(ポート被りなど)。物理的に別インスタンスに動的に払い出せるようにすることで、開発サーバに自分でアクセスし、操作することでより自律度の高い作業が可能になると考えています。
メモリ検索強化とコンテキストメモリ管理強化
今のメモリは markdown ファイルベースで、エージェントが必要に応じて読み書きするシンプルな仕組みです。ただ、クエスト数や知識が増えてくると検索精度や管理、コストの問題が出てくると予想しています。ここをうまくやることで、より長期的な文脈保持や知識活用ができるようにしたいと考えています。
一方で AI 周りはほぼ毎日のようななにかしら新しいものが出てきているので半年後くらいには Claude Managed Agent などで十分!となり kuro に使った土管だけが残る可能性もあるな、と考えてたりもします。どうなるんでしょうね〜
まとめ
ざっくりと kuro の裏側を説明しましたが、いかがだったでしょうか。
社用のエージェントを作りながら感じるのは、情報の流れの管理や、コミュニケーションチャンネルを適切に制御するのは本当に難しいな、というところです。もし社内でエージェントを内製する時にこの事例を思い出していろんな手戻りが減るのなら幸いです。