代筆屋の中身が空だった
MCP連携が動いて、Skillsも装備した。技術的にはTypewriterは「完成」に近い。
で、実際に使い込み始めて気づいた。Typewriterの中にClaudeがいるのに、そのClaudeが何もしていない。
オーケストレーターのClaudeに「投稿を書いて」と言う。するとオーケストレーターが自分で文章を考え始める。出来上がった文章をTypewriterに「これ保存して」と渡す。Typewriterは保存する。以上。
僕: 「投稿を書いて」
→ オーケストレーター: 文章を考えて、Typewriterに保存指示
→ Typewriter: はい保存しました
これ、Typewriterにわざわざ Agent SDK を載せてClaudeを入れている意味がない。ただのデータベースと同じだ。
なんでこうなるかというと、オーケストレーターが賢すぎるんだよね。
「投稿を書いて」と言われたら、自分で書いた方が早い。わざわざTypewriterに「考えろ」と言って、Typewriterが書いた文章を確認して、修正を指示して……という手間を踏む理由がない。だから自分でやってしまう。
これ、人間の組織でもよくある構造なんだよね。優秀なマネージャーが部下の仕事を全部自分でやっちゃう問題。「自分でやった方が早いし正確」。たしかにそうなんだけど、それだとマネージャーがボトルネックになる。部下は永遠に「保存するだけの箱」のまま。
AIの世界でも同じことが起きるとは思わなかった。
本来やりたかったのはこういう流れだ。
僕: 「投稿を書いて」
→ オーケストレーター: 「Typewriter、この話題で下書き3案作って」
→ Typewriter: 自分で考えて3案作る
→ オーケストレーター: 「2番がいいと思う。ヒロさんどう?」
→ 僕: 「OK」
→ オーケストレーター: 「Typewriter、2番を投稿して」
→ Typewriter: 投稿する
オーケストレーターは司令塔であってプレイヤーじゃない。考えるのも書くのもTypewriterの仕事。オーケストレーターは指示を出して、結果を見て、僕に確認を取る。それぞれに明確な役割がある。
前の記事で書いた通り、Typewriterはただの投稿アプリじゃない。Slack、メール、Discord、全部拾って代筆する万能エージェントとして構想している。だからこそTypewriterの中にAIが必要で、だからこそSDKを使っている。それなのに「保存箱」じゃ話にならない。
この構造を直すには、「お願い」じゃなくてシステムレベルで役割分担を強制する必要があった。
この問題に対しCodeがやったことは大きく3つある。
まず、Typewriter自身にツールを持たせた。
それまでTypewriterは「テキストを受け取って保存する」しかできなかった。これを変えて、Typewriter自身が以下のツールを使えるようにした。
save_draft— 下書きを保存post_to_x— Xに投稿update_skill— 自分の文体スキルを更新
ツールというのは、AIが「実行できるアクション」のこと。ツールを持っていないAIは、テキストを返すことしかできない。ツールを持たせることで初めて、AIが外の世界に作用できるようになる。
Typewriterにこの3つのツールを持たせたことで、「考えて、保存して、投稿して、学習する」という一連の流れを自分で完結できるようになった。
次に、オーケストレーターのシステムプロンプトを修正した。
オーケストレーターに「Typewriterへの指示はすべて typewriter_chat 経由で伝えろ。直接下書きを作るな。直接保存するな」と明記した。
ポイントは「〜してください」じゃなくて「〜は禁止」と書くこと。AIには「お願い」より「禁止」の方が効く。「Typewriterに任せてください」と書くと、状況によっては無視する。「Typewriterを介さずに下書きを作成することは禁止」と書くと、従う確率が上がる。
自由を与えると悪い方向に使い、制約を与えると守る。人間の直感とは逆かもしれないけど、LLMの性質としてそうなっている。
最後に、会話セッション機能を作った。
オーケストレーターがTypewriterと「会話」できるようにした。一方的な指示じゃなくて、やりとりができる。
オーケストレーター: 「AIと人間の関係について、投稿案を3つ作って」
Typewriter: 「3案作りました。1は〜、2は〜、3は〜」
オーケストレーター: 「2番がいいけど、語尾がちょっと硬い。柔らかくして」
Typewriter: 「修正しました」
技術的にはSQLiteに typewriter_sessions テーブルと typewriter_messages テーブルを追加して、セッション単位で会話履歴を管理する。start_draft_session(topic, count) でセッションを開始、revise_draft(session_id, request) で修正を依頼、close_draft_session(session_id) で完了ということらしい。
これで初めて、TypewriterのAIが「考える」存在になった。
Codeに頼んで、彼らの会話をUI上で僕が見れるようにもしてもらった。
もう一つ、この時期にやった重要なことがある。スキルの自己更新機能。
前の記事で hiro-writing-style というスキルをTypewriterに装備させた話を書いた。僕の文体ルールを定義して読み込ませる仕組み。
でもスキルの更新は手動だった。僕が「この語尾は使わないで」とフィードバックして、それを人間がスキルファイルに書き足す。面倒だし、漏れが出る。
そこで、Typewriter自身がフィードバックを解釈してスキルを更新できるようにした。learn_from_feedback(feedback_text) というツールを追加して、Typewriterが受け取ったフィードバックを自分で解釈して、スキルファイルに反映する。
僕: 「この文章、ちょっと丁寧すぎる。もっと砕けた感じで」
→ オーケストレーター: 「ヒロさんがこう言ってるよ」(そのまま伝える)
→ Typewriter: フィードバックを解釈して、スキルに「です・ます調を避ける」を追加
重要なのは、フィードバックの解釈をオーケストレーターじゃなくてTypewriter自身がやるということ。文体の専門家はTypewriterだから、文体に関する判断はTypewriterがやるべきだ。オーケストレーターは「ヒロさんがこう言ってるよ」と伝えるだけ。司令塔は伝達に徹する。
これは小さいことのように見えるかもしれないけど、エージェント設計においてはかなり重要な原則だと思っている。「誰がどの判断をすべきか」を明確にすること。全部をオーケストレーターに集約すると、さっきの「賢すぎるマネージャー」問題が再発する。専門領域の判断は専門のエージェントに任せる。
こうしてTypewriterは「保存箱」から「自律的に考えて書いて学習するエージェント」に進化した。
Before:
僕 → オーケストレーター(全部自分で考える) → Typewriter(保存するだけ)
After:
僕 → オーケストレーター(司令塔・ゲートキーパー)
↓ typewriter_chat経由
Typewriter(考える・書く・保存する・学習する)
ここまでの作業は、コード量としてはそこまで大量じゃないらしい。ツール定義を追加して、システムプロンプトを書き直して、会話セッション用のテーブルをデータベースに作る。Code曰く数日の作業のようだ(Codeはいつも人間がフルタイムでやった場合の見積もりを出す癖がある)。
でも「構造の問題」に気づいてそれを直すという判断は、コードの量とは関係ないんだよね。
「動いてるからいい」で済ませていたら、ずっと「保存箱」のままだった。オーケストレーターが全部やる構成でも、一応は動く。投稿はできる。でも、それはエージェントシステムじゃなくて、ただのチャットボットだ。
エージェントとチャットボットの違い。それは、末端のアプリが「自分で考える」かどうかだと思う。指示されたことをそのまま実行するだけなら、APIと変わらない。指示の意図を解釈して、自分で判断して、結果を返す。それがエージェントだ。
Typewriterを「X投稿ボット」で終わらせるつもりはなかった。Slack、メール、Discord、全部に対応する代筆屋。複数のデータソースを跨いで、文脈を理解して、適切な文章を書く。そのためには、Typewriter自身が「考える」能力を持っていないと成立しない。
だから「動いている保存箱」のまま放置するわけにはいかなかった。
で、ここまで来て、ようやく「想定していた形」でTypewriterが動くようになった。
オーケストレーターが指示を出す。Typewriterが考えて下書きを作る。オーケストレーターがチェックして、僕に見せる。僕がOKを出したら投稿される。会話セッションで修正のやりとりもできる。フィードバックからスキルが育っていく。
完成だ。少なくとも、そう思っていた。
次の記事で書くけど、この「完成した」と思った直後に、全部ひっくり返ることになる。
No Comments