最初のファクトリーを書く

宣言的なグラフ形式と JavaScript オーケストレーター形式のどちらでも、自分の you-agent-factory ファクトリーを手順を追って作成します。

これは何か

ファクトリー定義は 3 つのものを名付けます。ワークタイプは、そこを流れる作業の種類で、初期状態・終了状態・失敗状態を持ちます。ワーカーは、実際に実行するランタイムです。エージェント、スクリプト、推論呼び出しなどです。ワークステーションは、ある状態の作業を取り込み、ワーカーへディスパッチし、次の状態へ送り出す工程です。リトライ、レビューループ、ガード、ファンアウト、共有リソースといったものは、すべてこの骨組みに後から足していきます。

使いどころ

パッケージ済みファクトリーが望みに近いことをするようになったが、まだ十分ではない、と感じたら自分で書く番です。最後まで動く最小の定義から始めて、そこから育ててください。動かないファクトリーは何も教えてくれませんが、うまく動かないファクトリーは次に直すべき箇所を正確に教えてくれます。

形式を選ぶ

orchestrator フィールドがどのエンジンでファクトリーを実行するかを決め、その選択が書き方を決めます。グラフファクトリーはトポロジーを記述し、その中を作業が移動するのをエンジンに任せます。JavaScript ファクトリーは手続きを記述し、ランタイムを直接呼び出します。どちらか一方がもう一方の部分集合というわけではありません。グラフ形式は項目ごとの永続的な状態と並行性を無料で与え、JavaScript 形式は通常の制御構文を与えます。
問いグラフファクトリーJavaScript ファクトリー
何を書くかワークタイプ、ワーカー、ワークステーションを JSON か YAML で。JavaScript の関数 1 つと orchestrator ブロック。
制御はどう流れるか作業が状態間を移動し、入力状態に作業があるワークステーションが動く。上から下へ。通常の await、ループ、条件分岐で。
状態はどこにあるか作業項目そのものの中に、項目ごとに永続的に。スクリプトの変数の中に。再開が必要なら明示的なチェックポイントで。
何を宣言する必要があるか作業項目が取りうるすべての状態。引数スキーマと実行ポリシー。
orchestrator ブロックの省略は正当で、グラフを意味します。既存の定義は互換のための既定値で Petri エンジンとして読み込まれます。それでも明示的に宣言してください。JavaScript ファクトリーで書き忘れると、分かりにくい形で失敗します。

動く例から始める

最も速く確実な出発点は、すでに実行したことのあるパッケージ済みファクトリーです。プロジェクト内のルートにインストールすれば、検証を通ることが分かっていて、実行できることが分かっていて、一度に読み切れるほど小さい定義が手に入ります。@you/subagent が最小です。ワークタイプ 1 つ、ワーカー 1 つ、ワークステーション 1 つだけです。
you init --package @you/subagent --dir ./factory --replace
変更する前に、インストールした定義を読んでください。ワークステーションの名前を変え、自分のプロンプトを指すようにして、もう一度実行する。これはファイルをゼロから書くより小さな最初の編集であり、定義を有効にしているフィールドをすべて保てます。

グラフファクトリーを書く

エンジンが読む順序で定義を組み立てます。作業を人より先に、人をそれを使う工程より先に宣言します。以下の各ステップで、ファイルにトップレベルの配列を 1 つずつ足していきます。

ステップ 1 — ファクトリーに名前を付ける

ファクトリーに名前を付けます。これが唯一の必須トップレベルフィールドであり、後で名前付きファクトリーのコマンドが参照する対象になります。

ステップ 2 — ワークタイプとその状態を宣言する

3 つの状態を持つワークタイプを 1 つ宣言します。投入された作業が到着する INITIAL 状態、完了を意味する TERMINAL 状態、そしてグラフが完了させられなかった作業のための FAILED 状態です。handlingBehavior に DEFAULT を付けておくと、ポータブル実行が呼び出し入力の置き場所を判断できます。

ステップ 3 — ワーカーを宣言する

ワーカーを 1 つ宣言します。type によって、そのワーカーで使えるフィールドが決まります。AGENT_WORKER はエージェントツールと権限設定を、SCRIPT_WORKER はコマンドと引数を、INFERENCE_WORKER は操作とモデルの実行場所を受け付けます。model と provider を未設定のままにすると、インストール時に設定した運用者の既定値を継承します。

ステップ 4 — ワークステーションを宣言する

ワークステーションを 1 つ宣言します。inputs が取り込むワークタイプと状態を、outputs が成功時に送り出す状態を、onFailure がワーカー失敗時に送り出す状態を指定します。body はプロンプトです。呼び出し引数を埋め込んだり、処理中の作業項目を参照したりできます。

ステップ 5 — 呼び出しシグネチャを宣言する

呼び出し側が何を渡せばよいか分かるよう、invocation signature を宣言します。各パラメーターは、内部名、コマンドラインで使う外部名、そして受け付けるバインディング(位置引数テキスト、パイプした標準入力、名前付きフラグ)を持ちます。

ファイル全体

{
  "name": "summarize",
  "description": "Summarize one submitted request in a single agent pass.",
  "workTypes": [
    {
      "name": "task",
      "handlingBehavior": ["DEFAULT"],
      "states": [
        { "name": "init", "type": "INITIAL" },
        { "name": "complete", "type": "TERMINAL" },
        { "name": "failed", "type": "FAILED" }
      ]
    }
  ],
  "workers": [
    {
      "name": "summarizer",
      "type": "AGENT_WORKER",
      "agentTools": { "policy": "READ_ONLY" },
      "skipPermissions": true
    }
  ],
  "workstations": [
    {
      "name": "summarize-request",
      "type": "AGENT_RUN",
      "worker": "summarizer",
      "inputs": [{ "workType": "task", "state": "init" }],
      "outputs": [{ "workType": "task", "state": "complete" }],
      "onFailure": [{ "workType": "task", "state": "failed" }],
      "body": "Read the request below in full and return a self-contained summary with the key claims, the evidence behind them, and anything you could not verify.\n\nRequest:\n${input}\n"
    }
  ],
  "invocationSignature": {
    "parameters": [
      {
        "name": "input",
        "externalName": "to",
        "description": "Text request to summarize.",
        "required": true,
        "bindings": [{ "kind": "POSITIONAL", "position": 1 }, { "kind": "STDIN" }, { "kind": "NAMED" }]
      }
    ]
  }
}
8 種類のワークステーションと 6 種類のワーカーは、それぞれ異なるフィールド集合を受け付けます。スキーマリファレンスには、どのフィールドが共通で、どれが 1 つの種類だけに選択され、どれが拒否されるかが載っています。

JavaScript ファクトリーを書く

JavaScript ファクトリーは、ワークステーションのグラフをスクリプトで置き換えます。宣言すべきワークタイプもワーカーもワークステーションもありません。orchestrator ブロックがソース、引数スキーマ、安全ポリシーを持ち、スクリプト自身が書いた順序でエージェントを呼び出します。

ステップ 1 — オーケストレーターの種類を宣言する

orchestrator.kind を JAVASCRIPT にします。指定しないと、定義は互換のための既定値でグラフファクトリーとして読み込まれ、エンジンは存在しないワークステーションを探しにいきます。

ステップ 2 — ワークフローのソースを指定する

ワークフローのソースを指定します。スクリプトを定義の隣に置く場合はファクトリー相対パスの sourceRef を、定義自体にスクリプト本文を持たせたい場合は inlineSource を使います。パッケージ済みの JavaScript ファクトリーは、単一ファイルとして配布できるようインライン形式を使っています。

ステップ 3 — 引数スキーマを宣言する

argsSchema を JSON Schema オブジェクトとして宣言します。これはスクリプト開始前に引数を検証するものであり、スクリプト内の args バインディングに値を入れる元にもなります。

ステップ 4 — 既定ポリシーを設定する

defaultPolicy を設定します。ランタイム側の上書きがないときに、そのワークフローに何を許すかを制限します。エージェント呼び出しの総数、同時実行数、子ディスパッチのネストの深さ、ネットワークアクセスの可否、書き込み可能なルートです。READ_ONLY と空の書き込み可能ルートが安全な出発点です。

ワークフローのソース

return (async function () {
  phase("draft");
  const draft = await agent.run({
    label: "drafter",
    prompt: "Write a first draft that answers the request in full.\n\nRequest:\n" + args.request,
  });
  if (draft.status !== "COMPLETED") {
    throw "drafting failed";
  }

  phase("review");
  const reviews = await parallel([
    {
      label: "review-accuracy",
      prompt: "Judge this draft for factual accuracy. List every unsupported claim.\n\n" + draft.output.text,
    },
    {
      label: "review-completeness",
      prompt: "Judge this draft for completeness. List every part of the request it does not answer.\n\nRequest:\n" +
        args.request + "\n\nDraft:\n" + draft.output.text,
    },
  ]);

  phase("revise");
  const final = await agent.run({
    label: "reviser",
    prompt: "Revise the draft so it survives both reviews. Return only the revised text.\n\nDraft:\n" +
      draft.output.text + "\n\nReviews:\n" + JSON.stringify(reviews.map((r) => r.output.text)),
  });
  if (final.status !== "COMPLETED") {
    throw "revision failed";
  }
  return final.output.text.trim();
})();

それを読み込む定義

{
  "name": "draft-review-revise",
  "description": "Drafts an answer, reviews it from two angles, and revises once.",
  "orchestrator": {
    "kind": "JAVASCRIPT",
    "javascript": {
      "sourceRef": "workflows/draft-review-revise.js",
      "argsSchema": {
        "type": "object",
        "required": ["request"],
        "additionalProperties": false,
        "properties": {
          "request": { "type": "string", "minLength": 1 }
        }
      },
      "defaultPolicy": {
        "mode": "READ_ONLY",
        "maxAgents": 8,
        "concurrency": 2,
        "maxDepth": 1,
        "maxRetries": 0,
        "allowNetwork": false,
        "writableRoots": []
      }
    }
  },
  "invocationSignature": {
    "parameters": [
      {
        "name": "request",
        "externalName": "to",
        "description": "Request to draft, review, and revise.",
        "required": true,
        "bindings": [{ "kind": "POSITIONAL", "position": 1 }, { "kind": "STDIN" }, { "kind": "NAMED" }]
      }
    ]
  }
}
スクリプトは決まったランタイムバインディングの上で動きます。呼び出し入力のための args と meta、子を 1 つディスパッチする agent.run、ファンアウトのための parallel と pipeline、進捗のための phase と log、そして予算・チェックポイント・成果物・最終結果のための workflow 名前空間です。

検証して実行する

検証は実行とは別の手順であり、先に行う価値があります。ランタイムを起動せずに、HTTP のファクトリー検証エンドポイントと同じ契約でペイロードを確認します。通ったら、そのファイルをポータブルファクトリーとして直接実行します。

実行前に検証する

you factory config validate ./factory/factory.json

ポータブルファクトリーとして実行する

you run --factory ./factory/factory.json "Summarise this repository"
ポータブル実行は、呼び出し入力をどのワークタイプが受け取るかを知る必要があります。--factory で実行するファクトリーは、handlingBehavior DEFAULT をちょうど 1 つのワークタイプに宣言しなければなりません。多くても少なくてもいけません。

あるいは名前を付けてインストールし、どこからでも実行する

you factory create summarize --from ./factory/factory.json --set-current
you run --named summarize "Summarise this repository"

ファイルを育てる

1 ファイルで快適なのは、ワークステーション 3 つ目あたりまでです。分割レイアウトは、正規の factory.json と workers・workstations ディレクトリを並べて書き出すため、各工程が独立したファイルになり、差分が読みやすくなります。
you factory config expand ./factory/factory.json
逆方向の操作は、正規の単一ファイル形式を標準出力に書き戻します。分割レイアウトを、誰かから受け取った定義と差分比較したいときに便利です。
you factory config flatten ./factory

よくある落とし穴

最初のファクトリーの失敗は、ほぼ 3 つの間違いで説明できます。1 つ目は、どの作業も到達しない入力状態を持つワークステーション。実行がアイドルのまま何もせずに終了します。2 つ目は、workers 配列のどの項目とも一致しない名前でワーカーを参照すること。3 つ目は、別のワーカータイプのフィールドを借りてくること。スクリプトワーカーにエージェントツール、ポーラーにモデルルーティングなどはスキーマが拒否します。ワーカータイプごとに使えるフィールドの集合が決まっているためです。

ファクトリーに書かせる

パッケージ済みファクトリーの中には、ファクトリーを作るものがあります。@you/factory-builder は平易な文章の依頼を受け取り、指定した形式で検証済みの定義を 1 つ生成し、実行できる名前でインストールします。
you init --package @you/factory-builder
you run --named @you/factory-builder --factory-name release-note-review --orchestrator graph \
  --to "Review submitted release notes and return an approved summary."
orchestrator 引数は、ビルダーが書く形式を選びます。graph なら YAML のトポロジー、javascript なら JavaScript オーケストレーターです。既定は graph です。

タグ