Grandream

Grandream

公開: 7 min read

AIエージェント × jujutsu Workspace Pool

AIエージェント × jujutsu Workspace Pool
AIエージェントの開発をご検討中ですか? AIエージェント開発サービスを見る →

前回の記事「AIエージェントを並行で動かすなら jujutsu」では、エージェントごとに jj の workspace(作業用のフォルダ)を分けると、並行開発が楽になると書きました。

実際に毎日使ってみると、次の問題に気づきます。workspace を作るたびに依存パッケージのインストールが走り、名前を考え、終わったら片づける。エージェントを1日に何十回も起動すると、この準備と後始末が地味に重くなります。

私たちが社内のプロジェクトで採用しているのは、workspace を最初に数個だけ用意しておき、作業のたびに「貸し出して、返してもらう」運用です。この記事では、その考え方と、作業の始めと終わりを ws-start と ws-finish という2つの手順にまとめた方法を紹介します。jj の基本は「jujutsu(jj)入門」を参照してください。


全体像: 作業場所と成果物を分けて考える

最初に、この運用の考え方を1つの図で示します。

作業場所・成果物・PR の3段の関係

ポイントは、作業場所(workspace)と成果物(Change ID)を別のものとして扱うことです。

Git の worktree に慣れていると、「ログイン修正は wt-login フォルダにある」のように、作業場所と成果物を同じものとして覚えがちです。この運用では、作業場所はただの借り物で、成果物は jj の Change ID で追います。「Issue #123 の変更は ws-03 にある」ではなく、「Issue #123 → Change ID → PR」と辿ります。

この区別があるので、作業場所を使い回せるようになります。以下、順に説明します。

workspace を「プール」にする

プールとは

プールは、あらかじめ用意しておく workspace の集まりです。私たちの環境では、リポジトリの隣のフォルダに10個並べています。

myapp/                  ← メインのフォルダ(人間が使う)
myapp.workspaces/
  ws-01/                ← エージェントに貸し出す作業場所
  ws-02/
  ...
  ws-10/
  .state/
    locks/              ← 貸出中の印
    tasks.jsonl         ← 貸出と返却の記録

図書館の閲覧席にたとえると分かりやすいと思います。席(workspace)は決まった数だけあり、来た人(エージェント)は空いている席を借りて作業し、終わったら返します。席に名前を付けて特定の人の専用にはしません。

プールにする理由

プールにする理由は3つあります。

1つ目は、準備の手間を減らすためです。workspace は一度作れば、中の依存パッケージ(node_modules など)も残ります。次に貸し出すときは、中身を最新の状態に切り替えるだけで済み、インストールの多くを省けます。

2つ目は、名前を考えなくて済むためです。workspace の名前は ws-01 から ws-10 で固定です。作業ごとの名前は、後で説明する「作業名」として記録に残すだけで、フォルダの名前にはしません。

3つ目は、後片づけを不要にするためです。返却された workspace は消さずに残し、次の貸出で再利用します。成果物は Change ID としてリポジトリに残っているので、フォルダを消す必要がありません。

貸出(lease)と返却(release)

貸出と返却は、小さな自作スクリプト(以下 agent-ws)で行います。

workspace の貸出と返却のサイクル
agent-ws lease --task login-fix     # 空いている workspace を1つ借りる
agent-ws release --path <workspace> # 作業を記録して返す

貸出(lease) では、リモートの最新を取得し、空いている workspace に貸出中の印を付けてから、最新の main の上で新しい作業(Change)を始めます。印は locks/ws-01.json のようなファイルで、「すでにあれば失敗する」モードで作ります。2体のエージェントが同時に借りに来ても、同じ席を二重に貸すことはありません。

返却(release) では、workspace の最新の状態を jj に記録し、その作業の Change ID を tasks.jsonl に書き残してから、貸出中の印を外します。記録は追記するだけで、書き換えません。

{"at":"...","event":"release","status":"done","task_id":"login-fix","workspace_id":"ws-01","agent_id":"claude-code","change_id":"yympklro..."}

この1行があれば、workspace を次の人に貸した後でも、login-fix の成果物がどの Change かを辿れます。

main が進んでも全員を一斉に更新しない

作業中に main が進んでも、貸し出し中の workspace を一斉に最新化(リベース)することはしません。エージェントが作業している最中に足元を動かすと、エージェントが混乱するからです。最新の main へ移すのは、次に貸し出すときと、成果物を公開するときだけです。

作業の始めと終わりを2つの手順にまとめる

プールと貸出の仕組みがあっても、毎回コマンドを並べるのは面倒です。そこで、エージェントへの指示として、作業の始めと終わりを2つの手順(Claude Code や Codex の skill)にまとめました。

ws-start から ws-summary までの流れ

人間は、エージェントに「ws-start でログイン画面を直して」「終わったら ws-finish して」と頼むだけです。

ws-start: 作業名を決めて workspace へ入る

ws-start では、エージェントが依頼内容から短い作業名(例: login-fix、issue-123-search)を決め、その名前で workspace を借ります。作業名は記録に残り、後から成果物を探す手がかりになります。

workspace に入ったら、エージェントはまず起点と Change ID を確認し、人間に1行で伝えてから実装を始めます。起点が意図と違えば、ここで止まれます。

実装中、エージェントは git add も git commit もしません。jj では、保存したファイルの内容が自動で記録されるからです。区切りを付けたいときだけ jj commit を使い、リベースや push のような履歴を書き換える操作はしません。

ws-finish: PR を作らずに派生元へ合流させる

ws-finish では、テストを実行し、jj diff で意図しない変更(生成物や .env など)が混ざっていないか確かめてから、jj commit -m "..." で確定し、agent-ws land で派生元に合流させます。

合流(land) は、返却に加えて、作業の Change を派生元のブランチ(jj ではブックマーク)の先頭へリベースし、ブックマークを前に進める操作です。小さな修正のたびに PR を作らず、手元で main などへ直接積み上げられます。

ただし、PR を作らないということは、GitHub の CI を通らないということです。そこで land は合流の前に、あらかじめ決めた検査コマンドを workspace の中で実行し、1つでも失敗したら合流させません。ここに置くのは、ネットワークや LLM を使わない、数秒で終わる検査だけです。時間のかかる検査を置くと、エージェントの作業が毎回そこで待たされるからです。

次の場合も、land は止まります。

  • 説明(コミットメッセージ)のない Change がある
  • 書き換える範囲に、貸し出し中の別の workspace の作業が含まれている
  • リベースした結果、コンフリクトが起きた

止まったときは、エージェントが他の Change を勝手に書き換えて解決せず、どの Change で止まったかを人間に報告します。push は、人間が頼んだときだけ行います。

ws-summary: 終わった後に何が残っているかを残す

複数のセッションを並行で動かすと、合流した後に「どのセッションは開発環境での動作確認が済んだのか」が分からなくなります。そこで ws-finish の最後に、ws-summary の形式でセッションのまとめを表示させます。

## セッションサマリ: login-fix
- 目的: ログイン画面のエラー表示を直す
- 変更: パスワードが違うときに、入力欄の下へ理由を表示する
- Change: yympklro「fix(login): エラー理由を表示する」、main へ合流済み、未 push
- 検証済み: 単体テスト 成功
- 未確認(開発環境での動作確認): 誤ったパスワードで理由が表示されること
- 未対応・注意: なし
- 状態: 動作確認待ち

最後の「状態」は「動作確認待ち」「完了」「要対応」の3つから選びます。検証は実際に実行したものだけを書き、実行していなければ「未実行」と書かせます。まとめを見るだけで、そのセッションに何が残っているかが分かります。

メインのフォルダはエージェントに編集させない

この運用では、メインのフォルダ(人間が使う jj の default workspace)を、エージェントに編集させません。コードだけでなく、ドキュメントや設定の1行の修正も、ws-start で workspace へ入ってから行います。

メインのフォルダで直接編集すると、そこが複数の作業の置き場になり、最初の記事で書いた「他の作業の差分が見えてしまう」問題に戻るからです。

規則として書くだけでは、エージェントはときどき忘れます。そこで Claude Code では、フック(ツールを使う前に走る検査)で止めています。エージェントがメインのフォルダのファイルを編集しようとすると、フックが拒否し、「ws-start で workspace へ入ってから編集してください」と理由を返します。エージェントはその理由を読んで ws-start から始め直します。

メインのフォルダの編集をフックで止める流れ

メインのフォルダかどうかは、.jj/repo がフォルダかファイルかで見分けています。jj では、メインの workspace の .jj/repo はフォルダで、追加した workspace ではリポジトリの場所を指すファイルになるからです。

このフックで止められるのは、ファイル編集ツールによる書き込みだけです。シェルのコマンドで書き込む操作は止められないので、そこは AGENTS.md の規則で補っています。

ws-finish で合流させた後は、メインのフォルダも合流後の先頭へ自動で移します。人間が手でコマンドを打たなくても、メインのフォルダが常に最新の状態を指すようにするためです。人間が編集中のファイルがあればそのまま持ち越し、合流した内容と衝突する場合は移動を取り消して警告します。

まとめ

この運用の考え方は、次の4つにまとめられます。

  • workspace はプールにして、作業のたびに貸し出し、返してもらう
  • 成果物は作業場所ではなく Change ID で追い、記録は追記だけで残す
  • 作業の始めと終わりを ws-start と ws-finish にまとめ、終わりには ws-summary で残作業を見える形にする
  • メインのフォルダはフックで守り、エージェントには編集させない

jj の workspace は、ブランチがなくても作れ、書きかけも自動で記録されます。この性質があるので、作業場所を使い回しても成果物を見失いません。エージェントを並行で動かす回数が増えてきたら、作業場所を毎回作る運用から、貸し出す運用へ切り替えることを検討してみてください。

関連記事

Grandream

Grandream

株式会社グランドリーム

AI・システム開発のプロフェッショナルチームです。AIエージェント・業務自動化・Webシステム開発などを手がけています。

AIエージェント開発のご相談はお気軽に

PoC段階から本番運用まで一貫対応します。

AI開発について相談する

AIエージェント開発サービスの詳細を見る →

どのサービスが合うか30秒で診断する →