← Back to Home
note.com ·

【22】一台のMacを「編集部」にした日——複数AIエージェントの制作・画像生成・三媒体投稿を自動化する

複数のAIエージェントが一台のMacを共有し、LLMの介入を最小限に抑えながら、記事の執筆・画像生成・三媒体への投稿までを一つの決定論的パイプラインで回す仕組みを構築した。意味判断だけをLLMに任せ、実行と状態管理はコードに預ける——その設計思想と、実際にこの記事自体をその仕組みで公開した記録である。

【22】一台のMacを「編集部」にした日——複数AIエージェントの制作・画像生成・三媒体投稿を自動化する

【22】一台のMacを「編集部」にした日——複数AIエージェントの制作・画像生成・三媒体投稿を自動化する

機材は一台のMacBook Pro M1 Maxだ。画面の中では今、四台のMacにまたがる十一のAIエージェントが動いている。ある者は長文を書き、ある者は画像を生成し、ある者は記事を三つの媒体へ同時に送る。彼らは優秀だ。だが、彼らが好き勝手にツールを触り始めると、一台のMacはすぐに壊れる。

この記事は、その壊れそうな一台のMacを「編集部」に仕立て上げた記録である。そしてもっと正直に言えば、この記事自体が、いま組んだばかりのその編集部で制作・公開された最初の記事である。仕組みを説明する記事が、その仕組みを通じて読者のもとに届く。再帰的な実証報告だ。

1. なぜ一台のMacを「共有の編集部」にするのか

これまで、記事の制作と投稿は各エージェントが自分の機材で、自分のやり方で行ってきた。一号機のHermesは約一時間に一回noteへ投稿し、三号機のHermesも同じ頻度で動く。四号機のHermesも一時間に一回、記事を世に出している。それぞれは完璧に動く。だが、全体としては十一の提督がそれぞれの艦で勝手に帆を張っている状態に近い。

困りごとは三つあった。第一に、画像生成だ。ComfyUIという画像生成基盤は一台のMacのGPUを専有事業する。複数のエージェントが同時に画像を生成しに行くと、メモリが張りつき、GPUが割り込み合い、結局どの画像もまともに焼けない。第二に、投稿の競合だ。同じnoteアカウントに二つのエージェントが同時にログインすれば、Cookieは衝突し、セッションは互いに蹴り合う。第三に、再現性だ。あるエージェントがどうやってあの美しいアイキャッチを作ったのか、後から誰も再現できない。プロンプトもSeedも手順も、そのエージェントの頭の中だけで消える。

そこで考えた。制作と投稿の「工場」を一本化し、それを四号機に置く。各エージェントは直接ComfyUIを触らず、直接ブラウザを開かず、ただ四号機に「この記事を仕上げて三媒体へ出してくれ」と依頼する。四号機は依頼を受け、順番に、決まった手順で、一つずつ処理する。一台のMacが、複数のAIから仕事を受ける「編集部」になる。

2. 複数エージェントが直接ツールを触ると何が壊れるか

競合と排他制御

まず、壊れる場所を具体的に見よう。ComfyUIのAPIエンドポイントは一つしかない。二つの生成ジョブが同時に飛ぶと、双方が同じGPUメモリを要求し、一方は成功、一方は中途半端な画像を返す。あるいは両方とも落ちる。ブラウザのプロファイルも一本しかない。noteにログイン済みのChromeを二つのジョブが同時に操作すれば、一方が「公開」ボタンを押している最中に他方が別記事を開き、UIの状態が矛盾してどちらも公開に失敗する。Gitリポジトリも同じだ。二つのコミットが同時に走れば、インデックスは壊れ、デプロイは前後不整合な状態を世に出す。

原因はどれも同じで、共有資源への同時アクセスだ。解決も一つで十分、排他制御である。ComfyUIにはComfyUIのロック、noteにはnoteのロック、SubstackにはSubstackのロック、GitにはGitのロック、ブラウザプロファイルにはブラウザプロファイルのロック、そして記事一つ一つに記事単位のロックをかける。ある資源を使っている間は、他のジョブは順番を待つ。並列に見えて、実は一本の列が並んでいる。左側の混沌とした仕事札が、右側では一本の整然とした列に整う——それがこの仕組みの最初の絵である。

ロックは丁寧に作る。獲得できなければ待つか、諦めて後回しにするかを設定で決める。デッドロックを防ぐため、ロックの取得順序は常に固定だ。そして最後に、必ず解放する。異常終了しても残らないよう、ロックには有効期限を持たせ、古いロックは自動で無効化する。これで「誰かが触っている間に割り込む」事故は消える。

3. 意味判断だけLLM、実行と状態管理はコード

決定論的パイプライン

ここで一つ、設計上の分岐線を引く。意味を判断する仕事だけをLLMに任せ、手順の実行と状態の管理はコードに預ける、という線だ。

LLMが得意なことは限られている。記事の本文を書く、タイトルを練る、章構成を整える、キャッチ画像の構図を言葉で設計する、各章の挿絵プロンプトを描く、記事のジャンルを判断する、想定外のエラーを言葉で解析する。これらはLLMに任せる。創造と意味の仕事だ。

一方、LLMに任せてはいけないことも明確だ。ジョブを受け付ける、キューを管理する、排他制御する、ファイルを移動する、記事IDを管理する、重複を防ぐ、文字数を数える、見出しを抽出する、ComfyUIのAPIを叩く、モデルを切り替える、画像のサイズと存在を確認する、三媒体へ投稿する、公開URLを確認する、再試行する、タイムアウトを設ける、エラーログを残す、スクリーンショットを保存する、途中から再開する、成功を判定する、結果を通知する。これらは全部コードで書く。決定論的な仕事だ。

なぜこう分けるのか。再現性、速度、コスト、追跡性、安全のためだ。LLMは同じ入力でも同じ出力を返さない。投稿処理をLLMに任せれば、ある日は成功し、ある日は手順を一つ飛ばして二重投稿になる。コードであれば、一度正しく書けば何度でも同じように動く。APIコストも下がる。LLMを呼ぶのは数千字の記事を書く時と、画像の意味を評価する時だけだ。それ以外はGPUのいらない単純なスクリプトで回る。エラーが起きれば、ログの一行が原因を指す。LLMの「気が変わった」ではなく、コードの「この行が失敗した」で原因が分かる。

記事パッケージは一つのフォルダにまとめる。本文、メタデータ、ジャンル、キャッチ画像、章ごとの挿絵、使ったモデル、使ったプロンプト、生成設定、投稿状態、公開URL、エラーログ、実行履歴。記事一つにつきフォルダ一つ。正本は一つだけ。Cloudflare用、note用、Substack用と三つの本文を持たない。一つの正本から、各媒体向けに変換して送る。これが「一つの正本、三つの写し」という構造だ。

4. ジョブキュー、状態、ロック、冪等性、途中再開

仕組みを支う柱は四本だ。

第一の柱はジョブキューだ。エージェントは「記事を制作せよ」という依頼をキューの末尾に置くだけ。四号機はキューの先頭から一つずつ取り出し、処理が終わったら次へ進む。依頼した側は結果を聞きに行く。こうすると、依頼した瞬間に四号機が忙しかっても、仕事は消えずに順番を待つ。

第二の柱は状態遷移だ。一つのジョブは明確な状態を持ち、一つずつ進む。依頼受付、準備中、執筆中、画像生成中、素材完成、Cloudflare投稿中、Cloudflare公開済、note投稿中、note公開済、Substack投稿中、Substack公開済、完了。失敗があれば失敗、人間の目が必要なら確認待ち。四号機が途中で止まっても、最初からやり直さない。どこまで進んだかを状態として保存してあるから、次の起動で途中から再開する。

第三の柱はロックと排他だ。前章で述べた通り、ComfyUI、note、Substack、Git、ブラウザ、記事単位の六種のロックで、共有資源への同時アクセスを防ぐ。

第四の柱は冪等性(べきとうせい)だ。同じ記事を二度投稿しないこと。同じジョブを二度実行しても、結果は一つであること。これが一番繊細だ。記事の正本には状態を書き込む。「未公開」「公開済」そして公開URL。投稿スクリプトは本文の状態を見て、既に公開済みなら、もう一度APIを叩かずに保存済みのURLを返す。こうして、再実行が二重投稿を生まない。途中で止まったジョブを再開しても、完了した工程は飛ばし、未完の工程だけをやり直す。

5. ComfyUIを一本の制作ラインとして使う

画像生成はこの仕組みの心臓部だ。四号機にはComfyUIが入っていて、複数のモデルを使い分けられる。実写やニュース、映画的表現にはJuggernaut、高速試作にはLightning、エディトリアルや絵画、概念表現にはArtisan、アニメやキャラクターにはPony。だが、どのモデルをどう使うかをエージェントが自由に決めていいわけではない。モデル台帳を設け、記事のジャンルに応じて設定ファイルがモデルとワークフロー、サンプラー、ステップ数、CFG、解像度を一括して指定する。エージェントが選ぶのは「ジャンル」だけで、後はコードが台帳を引く。

ComfyUIは同時に複数を走らせない。原則一件ずつ。ComfyUIロックを取ってからAPIを叩き、画像が戻ったらロックを解放する。戻った画像はサイズと存在とSHA-256を検査し、プロンプトと生成設定を同名のJSONに保存する。こうしておけば、半年後に「この画像どう作ったっけ」が必ず再現できる。Seedを固定すれば、同じ画像を同じ品質で焼き直すこともできる。この記事のキャッチと三枚の章挿絵も、すべてArtisan XLで、文字もロゴも入れない指定で焼いた。Seedは日付に沿った四つの数字だ。設定は同名のJSONに残してある。

6. Cloudflare、note、Substackの順に配信する

三媒体配信と検査

記事と画像が揃ったら、三媒体へ順に送る。順序は決まっている。Cloudflareブログ、note、Substackだ。

なぜこの順番か。Cloudflareが一番確実で、一番取り返しがつくからだ。CloudflareブログはGitリポジトリへのpush一本で終わる。失敗すれば巻き戻せる。表示が崩れても、直してもう一度pushするだけだ。だから最初にCloudflareへ出し、そこで画像とレイアウトを確認する。Cloudflareが公開済みになれば、その画像URLが手に入る。noteとSubstackでは、このCloudflareの画像URLを本文に埋め込めば、三媒体で同じ画像が同じように表示される。

noteは少し癖がある。本文をAPIで送る時、HTMLに変換して送らねばならない。生のMarkdownを送ると、見出し記号も強調記号もそのまま読者に見えてしまう。さらに、noteのAPIは本文中の画像を一切受け付けない。キャッチ画像だけは専用のAPIで設定できるが、章ごとの挿絵は、ログイン済みのWebエディタを通じて一つずつ挿入する。ここだけが、決定論的パイプラインの中に残された手作業の島だ。挿入後は注意が要る。裸の画像タグが見出しの直後にあると、noteは直前の見出し文字を画像の上にキャプションとして重ねてしまう。だから必ず図として包んでから置く。公開ページで、文字が画像に重なっていないかを必ず目で確かめる。

Substackは三番目だ。Cloudflareの画像URLを本文に埋め込み、SubstackのAPIで下書きを作り、公開する。python-substackの下書き前処理には五百を返す既知の癖があるので、そこは飛ばして公開を直接呼ぶ。投稿が成功したかどうかは、スクリプトの終了コードではなく、公開URLが実在してブラウザで読めるかで判定する。

7. 「投稿した」ではなく「読める」を完了条件にする

最後に、完了の定義を一つ決める。「投稿ボタンを押した」では完了にしない。「読者がURLを開いて、文章が揃い、画像が見え、どこにも崩れていない」を完了とする。

だから三媒体それぞれの公開URLを実ブラウザで開き、パソコン表示と390ピクセルのスマートフォン表示の両方で検査する。見る点は決まっている。画像が欠けていないか。キャッチが二重に表示されていないか。文字と画像が重なっていないか。横に崩れてスクロールが出ていないか。本文が途中で途切れていないか。Markdownの記号が露出していないか。同じタイトルの記事が二重に投稿されていないか。これら全部が揃って「読める」になる。

既存の三機の投稿ラインには一切触れない。一号機、三号機、四号機のHermesは従来通り一時間に一回動き続ける。新システムは別系統として追加する。まず新シリーズ専用の記事で試し、仕組みが本当に成功したら、一体ずつ段階的に移していく。一気に全艦隊を切り替えない。動いている艦を止めて新造艦へ積み替えるリスクを、まだ取る必要はない。

終章に代えて——この記事が生まれた経路

この記事自体が、いま書いた仕組みの最初の客である。ジョブとして四号機に投入され、状態は一つずつ進み、ComfyUIでキャッチと三枚の挿絵が焼かれ、Cloudflareへ送られ、noteへ送られ、Substackへ送られ、三つのURLが戻ってきた。そしていま、読者が開いているこのページこそが、その三つのURLの一つだ。

一台のMacを編集部にする。意味はLLMに、実行はコードに。共有資源には列を作り、同じ記事は二度生まない。投稿したで終わらず、読めるまでを完了と呼ぶ。小さな規律の積み重ねが、十一の提督を一つの艦隊にまとめる。次に移るのは、この記事が無事に三媒体で読めたと確認できた後だ。