Tool は Flow が組み合わせる原子であり、Pack はそれらをデプロイし、複製する方法(このプロジェクトの「SDK」)です。このページでは、Tool コントラクト、標準Tool種別(ゼロから実装されたブラウザーToolを含む)、Pack形式を規定します。

Tool コントラクト

capability-gated な effect と型付き I/O で、Tool を組み合わせ可能かつ再現可能にする最小のインターフェイス。

ブラウザーTool

作成時に一度だけ賢く、実行時は永遠に決定論的な Playwright ベースの自動化。

Pack

Tool と flow を digest で固定し、検証可能な形でデプロイ・複製する単位。

1. Tool コントラクト

Tool は、ToolContract01 §2で説明するインターフェイス)にrun実装を加えたものです。
export interface Tool<I = JsonValue, O = JsonValue> {
  readonly contract: ToolContract;
  /**
   * (input, ctx) に対して pure。すべての effect は ctx に注入された
   * capabilities を通り、ambient ではない。contract.output に一致する value を返す。
   */
  run(input: I, ctx: ToolCtx): Promise<O>;
}

export interface ToolCtx {
  readonly net?: NetCapability;       // contract が "net" を宣言する場合のみ存在
  readonly fs?: FsCapability;
  readonly browser?: BrowserCapability;
  readonly clock: Clock;
  readonly random: Random;
  readonly env: EnvView;
  readonly secrets?: SecretsCapability; // contract が "secrets" を宣言する場合のみ存在
  readonly idempotencyKey: string;
  readonly logger: Logger;
  readonly signal: AbortSignal;
}
2つの不変条件により、Tool は組み合わせ可能で再現可能になります。
  • Capability-gated。 capability は、コントラクトが宣言した場合にのみctx上に存在します。transform Toolは文字どおりネットワークに到達できません。呼び出すべきctx.netが存在しないからです。これは構造によって強制される原則T2であり、エンジンがどの手順をpure(自由に再実行可能)とみなし、どの手順を記録すべきかを判断できる理由です(04 §5)。
  • 型付きI/O。 contract.inputscontract.outputはスキーマなので、コンパイラはすべてのwireを型チェックし、Rider は境界で検証します。
Tool はファクトリとして作成します。createHttpGet(): Toolがオブジェクトを返します。class も共有された可変状態もありません。

2. 標準Pack

core には、ファーストパーティの小さな Pack セットが同梱されています。これらは通常の Pack であり、エンジンが特別扱いするものは何もありません。そしてコントラクトのリファレンス実装として機能します。
PackToolsCapabilities
std/httphttp.get, http.post, http.request, http.paginatenet, secrets?
std/transformtransform, jq, templatenone(pure)
std/shellshell.runspawn, fs?
std/fsfs.read, fs.write, fs.globfs
std/browserbrowser.tool(決定論的リプレイ)browser, secrets?
std/controlnoop, assert, delay, gate, uuidclock?, random?
std/modelmodel.completenet, secrets
?は、effectful な入力が要求する場合にのみ Tool が宣言する capability を示します。std/httpは、シークレットを含む入力(認証情報付きURL、認証ヘッダー、トークン本文)がバインドされた場合にだけsecretsを追加します。そのため、http.postへ流れ込むsecret.参照は、secret_leakを起こすのではなく境界ルールを満たします(03 §7)。シークレット入力がバインドされていない場合、httpnetのみです。 std/controluuidは、新しい識別子を得るための正規のソースです。seeded なrandom capabilityから値を引き、その出力はJournalに記録されるため、生成されたidはリプレイ時にも安定します。式言語にuuid()ビルトインが存在しないのはこのためです。非決定性は記録された Tool の中にあり、式の中にはありません(02 expression language)。
std/modelmodel.complete本体は現在スケルトンです。Tool コントラクトと capability 宣言は用意されていますが、LLM連携はまだ実装されていません。
std/modelについては補足が必要です。model call は完全に正当な Tool ですが、それは他のものと同じeffectです。リクエストとレスポンスは Journal に記録され、それ単独で制御フローを動かすことはなく、リプレイではネットワークなしで記録済みレスポンスを返します。AI は固定できるデータソースであり、意思決定者ではありません。

3. カスタムToolの作成

コントラクトは小さいため、有用な Tool は数行で書けます。
import { z } from "zod";
import { defineTool } from "@browserflow/core";

export const slugify = defineTool({
  name: "slugify",
  version: "1.0.0",
  kind: "transform",
  description: "Lowercase, hyphenate, strip punctuation.",
  inputs: { text: z.string() },          // → InputDecl[] + 型付き input schema
  output: z.string(),
  capabilities: [],                        // pure
  run: async ({ text }) => text.toLowerCase().replace(/[^\w]+/g, "-").replace(/^-|-$/g, ""),
});
defineToolは Zod スキーマからToolContractInputDecl[]とコントラクトの content digest を含む)を導出するため、データ型とコントラクトがドリフトすることはありません(ハウススタイル: データ型は Zod で、z.inferによって導出します)。

4. 参照とバージョニング

手順はkind:name@rangeまたはname@rangeでToolを参照します。
  • browser:search-price@1.2.0 — exact。
  • http.post — std Tool。固定されたstd Pack内の最新互換バージョン。
  • search-price@^1.2 — range。コンパイラがそれをFlowSpec内のexact versionへ解決して書き換えるため(03 §2)、保存されるflowは常に完全に固定されます。
手順の参照から実行可能な Tool までを、コンパイラと Rider がどう解決するかは次の通りです。 Tool バージョンは semver です。Tool のcontract digestは、その入力、出力、capability が変わるたびに変化するため、互換性のない変更が patch を装うことはできません。digest mismatch はコンパイル時に表面化します。

5. ブラウザーTool

ブラウザー自動化は、Playwright の上にゼロから構築された主役の Tool 種別です。これはプロジェクトの中核的な思想を反映する2つの半分で構成されています。作成時に一度だけ賢く、実行時は永遠に決定論的に
1

browser.record — 一度だけ作成

browser.recordはデモンストレーションからブラウザー Tool を生成します。実際の Chromium セッションを一度だけ駆動し(作成時に任意で model によるガイドを受けることもできます)、Recordingをキャプチャします。Recording には、順序付けられ解決済みの Playwright 操作(ナビゲーション、role/text ベースのロケータ、fill、click、抽出)と、安定したロケータの選択に使われた DOM evidence が含まれます。Recording は canonicalize され、content-addressed になります。その digest が Tool のprovenance.source_digestになります。
browserflow record \
  --name search-price \
  --task "Search for a product and read the first result's price" \
  --input query:string \
  --secret apiToken
これはauthor-timeの操作です。Recording のキャプチャに使われたどんな賢さ(model や人間のクリック)も、Tool が実行される時点では残っていません。
2

browser.tool — 永続的に実行

コンパイルされたブラウザー Tool は、プレーンで決定論的な Playwright です。model も agent も実行ごとの推論もなしに、記録された操作をリプレイします。ロケータは記録時に DOM evidence から選ばれたものなので(getByRole/getByTextを優先し、構造セレクタにフォールバック)、adaptive ではないまま resilient です。
// flow 内
.step("lookup", browser.tool("search-price@1.2.0").with((c) => ({ query: c.input.query })))
browser capabilityの下で、Rider は Tool に管理されたブラウザーコンテキスト(ローカル Chromium、または Launcher が提供するリモート/クラウドブラウザー)を渡します。Tool のreads(抽出された値)は Journal に記録されるため、その手順は他と同じようにオフラインでリプレイされます(04 §5)。

5c. ブラウザー内のシークレット

Cookie、トークン、認証情報はsecret: true入力です。これらは実行時にsecrets capabilityを通じてブラウザーコンテキストへ注入され、Recording、FlowSpec、Journal、ログには決して書き込まれません(境界の不変条件、01 §7)。ログイン Flow は一度だけ記録し、実行ごとに提供される新しいセッション Cookie でリプレイできます。

6. Pack — デプロイ可能なSDK

Packは、Tool と flow を1つのバージョン付き content-addressed artifact にまとめます。Pack は、このプロジェクトが「SDKをデプロイする」方法であり、「サービスが複製する」単位です。
@acme/pricing@2.3.0/
├── pack.json                 # PackManifest (name, version, tool/flow digests, deps, digest)
├── tools/
│   ├── search-price@1.2.0/   # ToolContract + body (recording or code) + contract digest
│   └── …
├── flows/
│   └── daily-price-watch/    # FlowSpec + flow digest
└── README.md
PackManifest(スキーマは01 §8)は、含まれるすべての Tool と flow をdigestで列挙し、すべての依存関係を固定済みとして列挙します。これにより、Pack 全体が再現可能になります。一度 fetch し、各 digest をその content に照らして検証すれば、以後はオフラインで実行し続けられます。改ざんされた Pack や不完全な Pack は、何かが実行される前に検証に失敗します。

公開

browserflow pack init @acme/pricing
browserflow pack add ./tools/search-price       # Tool を追加し、その contract digest を計算する
browserflow pack add ./flows/daily-price-watch.yaml
browserflow pack publish --registry https://registry.acme.dev
publishはすべての artifact を canonicalize し、digest を計算し、manifest に署名し(任意、07を参照)、アップロードします。バージョンは semver と Changesets 風の規律に従います。manifest digest が不変の identity です。

利用

browserflow pack add @acme/pricing@^2.3      # resolve + pin して local lockfile に入れる
browserflow install                          # 固定されたすべての Pack を fetch + verify する
browserflow.lockはすべての Pack について、解決された正確なバージョンとdigestを記録するため、同じ lockfile に対してbrowserflow installした2台のマシンは byte-identical な Tool を得ます。

7. なぜ「npmだけ」ではなくPackなのか

Pack は npm/OCI/object storage 経由で配布できます(それらは transport です。07 §3を参照)。Pack がその上に追加するのは、汎用 Registry が提供しないもの、つまり実行可能な振る舞いの content-addressed verificationです。Pack の identity は、その Tool のコントラクトと本体、そして flow の spec の digest です。変更可能な tag ではありません。それが、複製を信頼できるものにし、air-gapped な再現を可能にします。

Launcher とポータビリティ

検証済み Pack を、local、remote、cloud のどこでも、runtime AI なしで実行します。

コントロールプレーン

サービスがPackを複製し、manifestに署名し、transport経由で配布する方法。