このチュートリアルでは、単一のFlowである daily-price-watch のライフサイクル全体をたどります。ブラウザーToolが商品の価格を調べ、純粋なtransformがその形を整え、価格が下がったときだけ条件付きの http.post がシークレットのwebhookへ通知します。プロジェクトをスキャフォールドし、デモンストレーションからブラウザーToolを記録し、それらを接続し、content-addressedなFlowSpecへコンパイルし、実行し、実行をタイムトラベルし、オフラインでリプレイし、最後にRESTエンドポイントとして公開します。すべて同じアーティファクトを使い、実行時にモデルは介在しません。
このガイドは、すでにmonorepoをクローンし、workspaceをインストール済みであることを前提にしています。詳しくは インストール を参照してください。@browserflow/cli はまだnpmに公開されていないため、以下のコマンドは npx 経由ではなく、クローン済みrepoの中から実行します。

構築するもの

実行例は、標準的な daily-price-watch Flowです。
browser:search-price@1.2.0  →  transform (shape)  →  http.post (notify, conditional)
ブラウザーの読み取り、時刻、ネットワーク応答といった非決定性の発生源はすべてJournalに記録されるため、同じFlowSpecは、オフラインであってもどこでも同一に再現されます。

エンドツーエンドのウォークスルー

1

プロジェクトをスキャフォールドする

browserflow init はプロジェクトを配置します。flows/ ディレクトリ、tools/ ディレクトリ、そしてすべてのPackをバージョンとdigestで固定する browserflow.lock ロックファイルです。
browserflow init
生成されたアーティファクト(コンパイル済みFlowSpec、実行出力、Journal、Recording)は、デフォルトではgitignoreされ、再生成可能な out/ ディレクトリ配下に置かれます。たとえばテストフィクスチャとして保持するJournalのように、明示的に固定することを選ばない限り、生成物はコミットされません。
2

ブラウザーRecordingを作成する

browserflow record は、解決済みのPlaywright操作リストからcontent-addressedなRecordingを生成します。操作はJSONファイルとして指定します(navigate、fill、click、extractなど)。コマンドはそれを正規化し、digestを計算し、Recordingを書き込みます。
browserflow record search-price \
  --task "Search for a product and read the first result's price" \
  --ops ./ops/search-price.ops.json \
  --input query
--ops <file> は必須です。生成されたRecordingは recordings/search-price.json に書き込まれます。--secret <slot> で宣言されたシークレットはRecordingには保存されません。スロット名だけで参照され、実行時に解決されます。
対話的なPlaywrightライブキャプチャ(Chromiumを開き、クリック操作を進め、自動キャプチャする機能)は将来のリリースで予定されています。現時点では、--ops で事前解決済みの操作を指定してください。Recordingモデルとops形式については ブラウザーTool を参照してください。
3

Flowに接続する

次に、記録したToolを daily-price-watch Flowへ組み込みます。SDKとYAMLは1つの真実の2つの表現です(tenet T1)。どちらも同一の正準FlowSpecへコンパイルされ、相互に再生成できます。
import { flow, http, browser, transform } from "@browserflow/sdk";
import { z } from "zod";

export default flow("daily-price-watch")
  // 型付き入力コントラクト。`secret: true` のスロットは参照だけで運ばれます。
  .input("query", z.string())
  .input("threshold", z.number())
  .input("webhook", z.string().url(), { secret: true })

  // 手順1: バージョンで固定された決定論的なブラウザーToolを呼び出します。
  .step("lookup", browser.tool("search-price@1.2.0").with((c) => ({ query: c.input.query })))

  // 手順2: 純粋なtransform。決定論的なJSON → JSONで、capabilityはありません。
  .step("shape", transform((c) => ({ price: c.steps.lookup.output, at: c.now })))

  // 手順3: 条件付き通知。分岐述語がtruthyの場合だけ実行されます。
  .branch("dropped?", (c) => c.steps.shape.output.price < c.input.threshold, {
    yes: (f) =>
      f.step("notify", http.post(
        (c) => c.secret.webhook,
        (c) => c.steps.shape.output,
      )),
    no: (f) => f.noop("unchanged"),
  })

  // 型付き出力プロジェクション。
  .output((c) => ({ price: c.steps.shape.output.price }));
flow: daily-price-watch

inputs:
  - { name: query,     type: string }
  - { name: threshold, type: number }
  - { name: webhook,   type: string, secret: true }

steps:
  - id: lookup
    use: browser:search-price@1.2.0
    with: { query: ${{ input.query }} }

  - id: shape
    use: transform
    with:
      expr: { price: ${{ steps.lookup.output }}, at: ${{ now }} }

  - id: notify
    use: http.post
    when: ${{ steps.shape.output.price < input.threshold }}
    with:
      url:  ${{ secret.webhook }}
      json: ${{ steps.shape.output }}

output: { price: ${{ steps.shape.output.price }} }
この2つのサーフェスはいくつかのことを代わりに行っています。lookup → shape のedgeは c.steps.lookup.output への参照から推論されたため、通常は needs を手で書く必要はありません。.branch() の糖衣構文は notify 上の when 述語へloweringされ、no: noop("unchanged") のarmは効果を持たず後続からも参照されないため、dead noopとして取り除かれました。webhook 入力は参照だけで運ばれるシークレットです。c.secret.webhook は、secrets capabilityを持つ http.post Toolの内部でだけ実値に解決されます。
コンパイル時の型が必要で、TypeScriptコードベースで作業するならSDKを書きます。TypeScriptを書かない人がFlowをレビュー、diff、保守するならYAMLを書きます。browserflow emit flow.ts --to yaml または browserflow emit flow.flow.json --to yaml で自由に変換できます。詳しくは SDKとYAML を参照してください。
4

コンパイルし、dry-runしてから実際に実行する

日常的なinner loopは、1秒未満で、破壊的なことをしないように設計されています。まずは純粋な --check から始めます。これはFlowを型チェックし、副作用なしでFlowSpecのdigestを出力します。ネットワークに触れるのは固定済みToolを解決する場合だけです。CIやpre-commit hookに最適です。
browserflow compile flows/price.yaml --check
次に --dry で実行を計画します。DAGを解決し、入力を検証し、どの手順が実行されるはずか、どの効果が発生するはずかを表示します。ただし、実際の効果は一切発生させません。これは外部に影響する処理に対する「実行前に確認する」ための機能です。
browserflow run flows/price.yaml --input query="wireless headphones" --dry
問題なければ、実際に実行します。
browserflow run flows/price.yaml --input query="wireless headphones"
--yes なしの run は、実行しようとしている効果(ネットワークホスト、ファイル書き込み、spawn)を要約し、最初の外部向け効果の前に一度だけ確認を求めます。--dry と純粋なFlowでは確認は出ません。シークレットは、ログ、inspect、エラー、--dry の計画を含むすべてのstreamでマスクされます。
5

実行をタイムトラベルする

すべての実行はJournalを生成します。これは、すべての手順遷移と記録された効果の、追記専用でcontent-addressedなログです。browserflow inspect は実行のtimeline、手順、Journalを再構築するため、何が起きたのかを正確に確認できます。直近のrun idを出力する browserflow runs last と組み合わせます。
browserflow inspect $(browserflow runs last)
条件付き通知など、単一の手順へ直接移動するには --step を渡します。
browserflow inspect $(browserflow runs last) --step notify
完了済みの作業はJournal内でcontent-addressedになっているため、中断された実行も正確に再開できます。10手順中9手順目で失敗したFlowは、1手順目ではなく9手順目から再開され、1手順目から8手順目は再実行されません。詳しくは 実行 を参照してください。
6

オフラインでリプレイして再現する

記録済みのJournalは、ポータブルで実行可能なフィクスチャです。実行中に1つ記録しておけば、後からどこでもオフラインでリプレイできます。
browserflow run flows/price.yaml --input query="wireless headphones" \
  --record-journal ./fix/price.journal.json
# 後で、どこでも、オフラインで:
browserflow replay ./fix/price.journal.json --verify
Hermetic replayがデフォルトです。注入される netclockrandomfs-read、browser capabilityはすべてJournalだけを元に動作します。外部呼び出しは行われず、シークレットも不要です。--verify フラグは、すべての手順出力が引き続き一致することをassertします。Toolのアップグレードで挙動が変わった場合、リプレイは最初に分岐した手順で明確に失敗します。これにより、「自分のリファクタリングで何か変わったか?」を、1コマンドの決定論的なチェックにできます。
opt-inの --rerun モードは別物です。Journalから読み取りをリプレイしますが、効果はliveで再実行するため、http.post は実際に再度発火します。このモードには、実行時に再解決される関連シークレットが必要で、run と同じ効果の要約と確認の経路に従います。何にも触れないhermeticな再現には、通常の replay または --verify を使ってください。
7

同じFlowをRESTとして提供する

実行してリプレイしたものと同じFlowSpecを、Flowを一切変更せずにHTTPで公開できます。browserflow serve はREST Launcherを起動します。
browserflow serve
このFlowは、まったく同じ決定論的なRiderによって駆動されるHTTPエンドポイントになりました。実行時にAIは介在しません。リクエストとレスポンスの完全なsurfaceについては、REST APIとしてデプロイLauncher を参照してください。

inner loopの概要

プロジェクトをスキャフォールドした後の日常的なサイクルは、次の数個のコマンドです。
コマンド内容副作用
browserflow compile <flow> --check型チェックし、digestを出力するなし(純粋)
browserflow run <flow> --dryDAGを計画し、入力を検証するなし
browserflow run <flow>ローカルで実行し、Journalを書き込む効果を発火する(先に確認)
browserflow inspect <run-id>実行timelineをタイムトラベルするなし
browserflow replay <journal> --verifyオフラインで再現し、出力をassertするなし(hermetic)
人間向けの進捗はstderrへ、機械可読な結果(--json)はstdoutへ出力されるため、CLIはpipelineやscriptの中できれいに組み合わせられます。

1つの文から決定論的ワークフローへ

一度記録したデモンストレーションから始め、どこでも同一に実行、再現、再デプロイできるcontent-addressedなFlowSpecまで到達しました。AIはブラウザーToolの作成を支援しましたが、Flowの実行方法には関与しません。

次のステップ

SDKとYAML

2つの表現、1つの真実。TypeScriptまたはYAMLでFlowを作成し、相互にround-tripできます。

実行と再現性

RiderがDAGをスケジュールし、効果を記録し、決定論的にリプレイする仕組み。

Launcher

同じFlowSpecをCLI、REST API、MCPサーバー、またはスケジュールジョブとして実行します。

CLIリファレンス

browserflow binaryのすべてのコマンド、フラグ、例。