AIエージェント18体で2021年の記事67本を並列改訂した記録
2021年に書いた講座記事67本を、18体のAIサブエージェントで検証済みの日英韓の記事に改訂した。共通ブリーフの中身、ウェーブの分け方、全員が通した検証ツール、ビルドとデプロイを1回にまとめたマージ、人間が確認したことまで。
何年も前に書いた技術記事が大量に残っていて、AIにまとめて手直しさせたいが、並列に走らせて収拾がつかなくならないか心配。この記事はそういう人向けです。私たちは2021年に書いた講座記事67本をすべて、検証済みの英語・日本語・韓国語の記事に改訂し、最後の54本は18のサブエージェントを並列に動かして1回のデプロイで公開しました。うまくいった理由は4つだけです。共通のブリーフ、ファイルの担当範囲の厳守、全員が通すべき検証ツール、そしてエージェントには触らせなかった1回だけのマージです。
本番サーバーをエージェントに触らせるときの安全策はAIコーディングエージェントに本番サーバーを触らせる:効いたガードレールにまとめました。今回はその続きで、多数のエージェントが1つのリポジトリを共有しても衝突させない方法の話です。
数字で見る全体像
以下の数字は、作業中のツール出力か、後からファイルを数え直した結果です。
| 項目 | 値 |
|---|---|
| 改訂した2021年の記事 | 67本(全部) |
| 並列ウェーブで処理した記事 | 54本 |
| ウェーブで使ったサブエージェント | 18(1ウェーブ6体) |
| 1エージェントあたりの記事 | 2〜4本 |
| エージェント稼働時間の合計 | 約349分 |
| 最初の起動から最後の完了までの実時間 | 約50分 |
| 18体のツール呼び出し回数 | 1,313回 |
| 54本に新しく付けた図 | 91個(67本全体では108個) |
| 最後のデプロイで変わったファイル | 1,368個中839個 |
| 旧画像の退避とサーバーからの削除 | 85枚(11+74) |
| デプロイ前のコンテンツチェック | エラー0、警告0 |
このデプロイの後、記事数は3言語とも86本、サイトマップのURLは326件になりました。
いきなり18体ではなかった:最初の13本
最初から18体を動かしたわけではありません。最初の13本は小さく進め、そのたびにブリーフに必要なことが分かりました。
- 1本はメインセッションだけで書き、形式を決めました。韓国語の原文をその場で書き直し、英語版と日本語版を新しく作る形です。
- 3本はメインセッションが1本ずつ進めました。「2021年のサンプルを全部動かし直す」効果はここで分かりました。ソートの例は
2 3 4 5 7ではなく23457と出力し、2021年のクイックソートはソート済みの入力でスタックオーバーフローを起こして落ちました。 - 3本をサブエージェント1体に任せ、単独で形式を守れるかを試しました。17.3分かかりました。
- 6本を3体で同時に、2本ずつ。13.9分、14.6分、15.4分でした。複数のエージェントが図のフォルダと検証ツールを共有したのはこれが初めてで、問題も2つ出ました。古いスクリーンショットにWindowsのアカウント名が写っていたこと、そして他のエージェントの図が原因の警告が出たことです。
運営者から「残りは1本ずつ聞かずに全部やって」と言われたとき、残りは54本でした。何かを起動する前に、まずブリーフを書きました。
共通ブリーフに書いたこと
各エージェントへのプロンプトは短くしました。「このブリーフに正確に従うこと」と、担当する記事の一覧、範囲についての短いメモだけです。残りはすべて1つのMarkdownファイルにまとめ、全員がそれを読みました。こうすると18本のプロンプトがぶれず、ルールを直すときも1か所で済みます。
ブリーフの中身は次のとおりです。
- 記事ごとの成果物。 2021年の韓国語ファイルをその場で書き直す(slug、元の日付、WordPress時代の項目は残す)。英語版と日本語版を作る。図を1〜2個付ける。訳すのではなく、それぞれの読者に合わせて書く。
- 主な仕事は検証。 サンプルコードをすべて取り出し、C#は .NET 10、C/C++はclangでコンパイル・実行する。本物の出力と、コード付きの本物の警告を載せる。2021年の誤りは直し、直したことを記事に書く。「C# N で追加」という主張は、古い言語バージョンでコンパイルして確かめる。
- Windows専用のコード。 WinFormsやWin32はMacでもコンパイルできるが、実行はできない。コンパイルだけ行い、「コンパイルのみ確認、実行はしていない」と明記する。出力を作り話で埋めない。
- 古い画像。 記事が参照する画像はすべて開く。個人情報が写ったもの、古くなったもの、他人の資料からの転載に見えるものは外す。
- 本文の個人情報。 貼り付けたコンパイラ出力に残ったWindowsのユーザーフォルダや、サンプルデータに使った実名を探す。
- ファイルの担当範囲。 書いてよいのは自分の
content/*/<slug>.mdとdata/figures/<slug>.yamlだけ。他の記事、スクリプト、テンプレート、サーバーには触らない。 - ビルドとデプロイはしない。 他のエージェントが並列で作業しているため。
- 報告前に通す検証ツールと、決まった報告の形式(タイトル、文字数、見出し、図のID、直した2021年の誤り、計測値、外した画像、検証ツールの最終行)。
ブリーフは途中でも変わりました。ウェーブ1で、あるエージェントが教科書からの転載に見える図を外し、別のエージェントがサンプルデータに運営者の名前が使われているのを見つけました。この2つのチェックをウェーブ2と3の開始前にブリーフへ追加したので、後半の36本は最初からそのチェックを受けています。
ウェーブの分け方
記事は分量ではなくテーマでまとめました。内容が重なる記事を同じエージェントに渡すためです。
- ウェーブ1: C#の基礎18本。6体に3本ずつ。
- ウェーブ2: 19本。コレクション、LINQのクエリ構文とリフレクション・ファイル入出力、レジストリとWinForms(この1体だけ4本)、Win32 APIの2グループ、正規表現の入門。
- ウェーブ3: 17本。正規表現の文字クラスと量指定子など(4本)、スタックとキュー、連結リスト、木・ヒープ・二分探索木、BFSとDFS、貪欲法と線形探索。
負荷を均等にするより、テーマでまとめるほうが効きました。2021年のコレクション記事3本は内容がかなり重なっていたので、1体にまとめて渡し、「それぞれの範囲をはっきり分け、互いにリンクする」と指示しました。関連記事を別々のエージェントに渡すしかないときは、プロンプトにそう書きました。ウェーブ3の正規表現担当には、入門記事を別のエージェントが並行して書いているので、繰り返さずにリンクするよう伝えています。
ウェーブ2はウェーブ1の3体がまだ動いているうちに始め、ウェーブ3はその4分後に始めました。そこから最後まで、12体が同時に動いていました。1体あたりの時間は、2本担当の13.1分から、4つの正規表現エンジンで試した4本担当の26.0分まででした。
進み具合は、エージェントの出力を読まずにファイルそのものから数えました。韓国語ファイルが legacy: false になっていて、英語版と日本語版の両方があれば完了とみなします。この記事のために同じループをもう一度実行しました(zsh)。
n=0
for f in content/ko/_legacy/*.md; do
s=$(basename $f .md)
if grep -q "^legacy: false" $f && [ -f content/en/$s.md ] && [ -f content/ja/$s.md ]; then
n=$((n+1))
fi
done
echo "revised $n / $(ls content/ko/_legacy/*.md | wc -l | tr -d ' ')"
revised 67 / 67
作業中は、同じループがある時点で 완료 38 / 67、その10分後に 완료 64 / 67 を出していました(ツールの表示は韓国語です)。共有の事実はファイルシステムにあり、この数え方はどのエージェントの自己申告にも頼りません。
全員がサンプルを全部実行した理由
2021年の講座を、1行も動かさずに読みやすく書き直すことはエージェントにもできます。見た目はきれいでも、中身は間違ったままです。報告を読むと、「読んで問題なさそう」では見逃していたものがよく分かります。
- C#のコレクション記事には
Colsoleというタイプミスがあり、エラーCS0103になります。公開されていた例は一度もコンパイルできない状態でした。 - C++のヒープ実装は、ロジックとしては正しいものでした。ランダムな操作40万回の結果は
std::priority_queueと一致しました。ところがヒープのオブジェクトをコピーすると二重解放が起き、AddressSanitizerが報告しました。 - Win32のサンプルは、韓国語のウィンドウタイトルで
FindWindowAを呼んでいました。GCCでは文字列がUTF-8で実行ファイルに入るため、韓国語版Windowsでは、このANSI版の呼び出しは一致しません。 - 2021年のサンプルの1つには、閉じ括弧の後ろに見えないU+200Bが入っていました。そのまま貼るとコンパイラは
error: '\U0000200b' does not name a typeを出します。
どれも読むだけでは気づきません。コンパイルすれば全部出てきます。だからブリーフでは、コードを動かすことを主な仕事にし、文章はその次にしました。本物のエラーコード、本物の出力、何をなぜ直したか。検索結果の要約では代わりにならないのも、この部分です。
各エージェントが通した検証ツール
報告の前に、各エージェントは次を示す必要がありました。
check_figures.pyがエラー0、自分の図に警告なし。図を3言語分とスマートフォン用の縦配置で描画し、横幅1000pxを超えると警告します。- 韓国語版と日本語版の図を実際に描画して目で確認する。
qlmanageでSVGをPNGにして開きます。ラベルの重なりをこれで直したエージェントが何体もいました。 - フロントマターがYAMLとして読めること。タイトルと説明文の長さは短いスクリプトで確認。
- 自分のファイルに個人のパスや名前が残っていないかの本文スキャン。
共有の検証ツールには弱点が1つありました。check_figures.py はフォルダ内のすべての図を描画するので、各エージェントには、他のエージェントが作業中の図の警告まで見えます。ある報告には「警告12件はすべて他のエージェントの図で、自分の6個の図には警告なし」とありました。内容は正しいのですが、全員が出力を担当者ごとに仕分ける手間を負いました。次にやるなら、検証ツールは既定で指定したslugだけを報告するようにします。
マージ:ビルド1回、チェック1回、デプロイ1回
最後のエージェントが終わってから、メインセッションだけでマージを次の順に行いました。
- 個人の名前とパスのスキャンを、変更したファイルだけでなくサイト全体・全言語に対して実施。
- どの記事からも参照されなくなった旧画像の一覧を作成。
- その画像をローカルのバックアップフォルダへ移動。
- ビルドを1回、コンテンツチェック、図のチェック。
--prune付きでデプロイのドライランを行い、削除対象が画像だけであることを確認。- 本番デプロイの後、公開URLを抜き取りで確認。削除した画像の1つが404を返すことも確かめました。
そのときの出力から関係する行を抜き出します(ツールの表示は韓国語、コメントは私たちが付けたもの)。
build ok → …/public (posts: en 86, ja 86, ko 86, sitemap urls: 326)
검사: 파일 1368개, HTML 330개 → 오류 0건, 경고 0건 # 1,368ファイル、エラー0、警告0
그림 144개 · 오류 0 · 경고 0 # 図144個、エラー0、警告0
prune would delete: 74 | non-image: []
빌드 파일 1368개 중 변경 839개 # 1,368ファイル中839個が変更
404 /static/img/legacy/2021/06/20190404_171228_204.png
8/8 통과 # URLの簡易テスト合格
変更が800ファイルを超えたので、デプロイには約15分かかりました。最後に1回だけビルドすることで、見落としやすい事故も防げます。改訂途中の記事は絶対に公開してはいけません。書き手が18体いる状態でそれを保証する方法は、誰にもデプロイさせないことしかありません。
それでも人間が確認したこと
並列のエージェントで執筆時間は減りますが、レビューの時間はゼロになりません。実際に必要だった確認は次のとおりです。
- 報告18本を読む。 それぞれにタイトル、文字数、見出し、直した2021年の誤り、外した画像が書かれています。エージェントをまたぐ発見はここから生まれました。Windowsのアカウント名に触れた最初の報告からサイト全体を検索し、同じパスが残る記事をあと2本、担当エージェントが着手する前に見つけています。
- 指摘された画像の後始末。 先行したエージェントは、ローカルのユーザーパスが写ったスクリーンショットを記事から外していましたが、ファイル自体はサーバーに残っていました。参照を消しても、ファイルは消えません。
- エージェントの自己申告を確かめる。 あるエージェントは、パスの打ち間違いで余計なファイルを作り、すぐ消したと報告しました。そのフォルダが本当に消えているかを確認しました。
- こちらの検索パターンを広げる。 運営者の名前を単語境界付きで探すgrepは、名前が別の語とくっついた表記を取りこぼしました。本文から見つけたのはエージェントで、私たちはパターンを広げて全体を検索し直しました。
- 削除リストを読む。 pruneの一覧を1行ずつ読み、画像以外のファイルが含まれないこともスクリプトで確認しました。
- 運営者だけができる作業。 全体の実行の承認と、その後に運営者へ渡した2つの作業。削除した画像がキャッシュから配信され続けないようにするホスティング管理画面でのCDNキャッシュ削除と、新しいURLのSearch Consoleへの登録(待ちが183件、1日10件ほど)です。
レビューで見逃したものも1つあり、この記事のために数え直していて見つかりました。シェルのループのバグで、未使用の画像が1枚だけ移動されていなかったのです。その経緯はAIエージェントの最初の診断が間違っていたときに、画像の監査は古いブログ記事の個人情報・著作権チェックに書きました。
並列エージェントが効くとき、裏目に出るとき
| 状況 | 並列にするか | 理由 |
|---|---|---|
| タスクごとに自分のファイルだけを書く(記事1本、その翻訳、その図ファイル) | する | マージの衝突がなく、共有の手順はビルドだけ |
| タスク同士でファイルを共有する(トピック一覧、テンプレート、設定) | しない、または1体に所有させる | 2体が同じファイルを編集すると、どちらかの作業が消える |
| BがAの成果を必要とする(索引ページ、完成済み記事へのリンク) | ウェーブに分ける | 改訂済みの関連記事へリンクする記事のように、Bを後のウェーブに回す |
| すべてのタスクをスクリプトで確認できる(コンパイル、描画、パース) | する | エージェントが報告前に自分の作業を証明できる |
| 品質がスクリプトにできない判断(語調、法的な文言) | 少数にしてレビューを厚く | レビューのコストはエージェントの数に比例して増える |
| 共有の状態を変える手順(ビルド、デプロイ、削除) | 並列にしない | 全員が終わった後、1つのセッションで行う |
| テーマが重なる記事 | まとめて1体に | 重なりは2体の間より、1体の中で整理するほうが楽 |
正直な限界
- 一番の成果は速さではありません。 349分の作業が約50分で終わったのは派手に見えますが、記事の質はチェックの質で決まります。コンパイルと実行がなければ、速くなるのは間違えるまでの時間だけです。
- Windows専用のコードはコンパイルのみで、実行していません。 WinForms、レジストリ、Win32を使う記事にはそう明記しました。実行時の動作はドキュメントに基づく説明で、私たちの実測ではありません。
- 「転載に見える」は判断です。 教科書の図に似たものは外しましたが、1枚ずつ出どころを証明できたわけではありません。安全な側を選びました。
- 検証ツールは、確認する項目しか確認しません。 全記事が図とコンテンツのチェックを通りましたが、シェルのループのバグはすり抜けました。件数を比べれば捕まえられたはずで、今はチェックリストに入っています。
次も使う型はこうです。まずブリーフを書き、エージェントごとにファイルを分け、全員にスクリプトで作業を証明させ、ビルド・デプロイ・削除は1か所にまとめる。
よくある質問
同時に何体動かし、どれくらいかかった?
最後の54本は、6体ずつ3つのウェーブ、計18のサブエージェントに2〜4本ずつ割り当てました。稼働時間の合計は約349分ですが、ウェーブ2と3を重ねて動かしたため、最初の起動から最後の完了までの実時間は約50分でした。
各エージェントに自分でビルド・デプロイさせなかったのはなぜ?
ビルドは公開フォルダ全体とサイトマップを書き換え、デプロイは参照されなくなったファイルを削除します。どちらも共有の状態です。書きかけの記事が公開されないように、ビルドとデプロイは全員が終わった後にメインセッションが1回だけ行いました。
人間は何を確認した?
メインセッションが全エージェントの報告を読み、サイト全体の個人情報スキャンをやり直し、指摘のあった画像を確認し、デプロイで削除されるファイルの一覧を1行ずつ見ました。運営者は実行を承認し、アカウントの持ち主にしかできない作業(CDNのキャッシュ削除と新しいURLのインデックス登録)を引き受けました。