仕組み

長い文書は実際どうやって書かれているか

40 ページのレポートは「とても長い 1 回の返答」ではありません。状態機械を持つバックグラウンドジョブです——アウトライン、質問、調査、節ごとの執筆。どの段階も落ちたあとに再実行でき、作業は重複せず予算も焼けない。編集のオフセットはモデルが選ぶのではなくコードが計算し、すべての版が残ります。

ふつうの回答はモデルへの 1 リクエストです。文書は違います。数分から数十分走り、タブを閉じても生き残らなければならず、どの 1 リクエストにも収まらない 40 ページ分の一貫性を保たなければなりません。だからこれは状態機械を持つバックグラウンドジョブとして動きます。そして以下のほぼすべての設計判断は、たった 1 つの問いから出ています——これが途中で死んだらどうなるか?

各ステップ

ステップ何をするか途中で死んだら
種別を選ぶリクエストをあなたの文書タイプのどれかに対応づける再実行して上書き
アウトライン節を出す行の挿入は衝突時スキップ
質問書き始める前に必要なものを一度だけ聞く待つ——あなたの回答とジョブの起床は同一トランザクション
調査節ごとに事実を集める(ラウンド制)次のラウンドから再開
執筆一度に 1 節節を確保し、書き、次へ
仕上げ集計、通知、エクスポート可能に冪等

設計を最もよく説明するのは調査のステップです。ここだけは本当に冪等にできません。同じラウンドを 2 回走らせれば、検索が返すページも、抽出される文も変わりえます。そこで冪等性の代わりに 2 つを使います——事実は正規化後の (文書, 節, 事実) で一意なので重複は 2 行目に落ちない。そしてラウンドのカウンタは、そのラウンドの事実と同じトランザクションで増えます。

結論ははっきり書く価値があります。第 3 ラウンドで何度も落ちるジョブは、第 4 ラウンドから再開します。これが無ければ再起動のたびに第 1 ラウンドからやり直しで、しかも再起動の回数には上限がありません。クラッシュループで予算は焼けません。

なぜ一度に 1 節なのか

各節は compare-and-set(ready → writing)で確保され、本文・版・カウンタを同じトランザクションで書き切ります。「文書の途中でサーバーが再起動した」が事件でなくなるのはこのためです。書き終えた節は 1 文字も動かず、書きかけの節は半分だけ 2 回書かれるのではなく、確保し直されます。

同時にリクエストが小さく保たれます。1 節は、その節の指示・その節の事実・短い文脈から書かれます。文書全体からではありません。これは見た目より重要です。素材が増えるとツール呼び出しの信頼性がどう崩れるかを計測しました——小さい素材では 8 回中 7 回成功、それが 2/8 になり、5,000 語あたりで 0/8。すべてを詰め込んだプロンプトはより良い節を生みません。指示に従う代わりに素材を復唱する節を生みます。

編集:オフセットはコードが出す

キャンバスで一節を選び、何を変えたいか言うと、その部分だけが差し替わります。この仕組みは意図的に狭く作ってあります。

  1. クライアントが文字範囲 [start, end) を送る。
  2. サーバーが body[start:end] を変更対象の断片として取る。
  3. モデルはその断片と限られた文脈を受け取り、置換テキストだけを返す。
  4. サーバーが継ぎ合わせる:body[:start] + 置換テキスト + body[end:]

モデルに範囲を尋ねることはなく、モデルが diff を返すこともありません。編集範囲を出せと言われたモデルは、自信が無いときに大きな声で失敗したりしません——いかにも妥当に見える範囲を返し、あなたの文書から静かに一文が消えます。範囲はそのテキストを描画したブロック分割器から来るので、構造上つねに正確です。

編集中にその節が下から書き換わっていたら——ちょうどエージェントが書き直していた場合など——編集は上書きではなく競合を返します。どちらを残すかはあなたが決めます。それはあなたの権利であり、代わりに決めてしまう方式では、人は数日後にそれを知ることになります。

事実と、出典のない数字の行き先

各節は事実テーブルから書かれます。モデルの記憶からではありません。各事実は出どころと一緒に保存されるので、完成した文書のどの数字も、あるページや一節まで遡れます。

書き直しのあと、結果はコードが走査します。数値・コード・固有名詞のうち、引用素材に現れないものは印が付きます。見つかればその断片を 1 度だけ再生成し、それでも残るなら断片は印付きで返され、自動適用はされません。特筆すべきは、この確認をモデル自身に尋ねてはいない点です——「この記述に裏付けはあるか」と聞けば、矛盾する素材を目の前にしてもモデルは「ある」と答えます。

節への変更はすべて 1 つの版です:最初の草稿、あなたの手直し、依頼された各書き直し、各復元。古い版の復元は何かを消すのではなく版を足します。だから履歴は「実際に起きたこと」の記録であり、「生き残ったもの」の記録ではありません。

チャットに現れるもの

執筆は会話に 3 種類のメッセージを残します。そしてどれもモデルが書いたものではありません

  • 開始——タイトルと節数。リクエストが出された時点ではなく、ジョブが実際にトークンを使い始めた瞬間に投稿されます。
  • 更新——どの節が変わり、何が起きたか。あるターンが実際に文書を変更したときに投稿されます。
  • 完了——節数、語数、所要時間。

3 つとも、数字はデータベースから計算されます。これは好みの問題ではありません。「いま何をしたか報告して」と言われたモデルは、自信に満ちた、具体的で、ときに事実でない説明を出します——文書が 1 文字も変わっていないターンで「その図を文書に追加しました」と言うことも含めて。システムが何をしたかは、システムが言う。

約束する前に知っておく制限

  • 書き込み worker はデータベースごとに 1 つ動きます。ジョブは FOR UPDATE SKIP LOCKED で確保するので、確保の意味論自体は複数 worker に耐えますが、現在の配備は 1 つです。
  • キャンセルは 2 つの半分でできています。プロセス内の半分が現在のステップを素早く止め、ジョブ行の永続フラグが、プロセスより長生きしたジョブも止めます。
  • エージェントで公開ソースを切ってある場合、調査ステップはインターネットにまったく触れません。劣化ではなくスキップです。許可は狭める方向にしか効きません。