browser-flow の flow は一度だけ作成され、1つの content-addressed FlowSpec にコンパイルされ、どこでも同一に実行されます。「どこでも」を願望ではなく事実にしているのは、小さく明示的な可搬性契約です。環境ごとに変わる関心事はすべて、ローカルのデフォルトを持つ interface であり、ラップトップからクラウドへ移すことは、それらの interface の配線方法を変えることであって、flow 自体を変えることではありません。

可搬性契約

ラップトップとクラスターで異なる関心事はすべて、妥当なローカルデフォルトを持つ interface に閉じ込められます。エンジン内ではベンダー名を一切指定せず、ambient global も読みません。スケールアウトするには、launcher がこれらの interface の背後にある実装を差し替えます。
関心事Interfaceローカルデフォルトスケールアウト時の差し替え
run / journal storageStore.browserflow/store.sqlite の SQLitePostgres / object store
secretsSecretsfile / OS keychainVault / cloud secret manager
subprocess / spawnSpawnCapabilitylocal shellE2B / remote sandbox (--sandbox e2b)
filesystemFsCapabilitylocal filesystemE2B / remote sandbox
browserBrowserCapabilitylocal Chromium (Playwright)remote / cloud browser pool
artifactsStore (blobs)filesystemS3 / GCS
transport(launcher)CLI / HonoAPI gateway / queue
concurrencyconfigCPU-boundworker pool / autoscale
インメモリ store を使うには BROWSERFLOW_STORE=:memory: を設定します(テストや一時的な CI 実行に便利です)。デフォルトパスの .browserflow/store.sqlite は必要になった時点で作成されます。BROWSERFLOW_SANDBOX=e2b は E2B backend を事前選択するため、呼び出しのたびに --sandbox e2b を指定する必要がありません。
すべての行はローカルデフォルトを持つ interface であり、エンジン内ではベンダー名を一切指定しません。ラップトップからクラウドへ移すことは buildRunDeps の変更であり、書き換えではありません。そしてラップトップで検証済みの flow は、FlowSpec と Pack の digest が同一であるため、クラウドでも再現されます(tenet T3)。
これらのドキュメント全体で使う実行例は daily-price-watch です。browser tool の search-price@1.2.0 が価格を読み取り、transform が形を整え、条件付きの http.post がシークレット webhook に通知します。この flow は、ラップトップで browserflow run から実行されても、gateway の背後で POST /flows/daily-price-watch/runs として実行されても変わりません。異なるのは、これらの interface の背後にある配線だけです。front の全体像は Launcher を参照してください。

buildRunDeps で差し替えが起きる仕組み

エンジンである Rider は、注入された capability の集合に対して FlowSpec を実行し、それ以外には依存しません。cloud SDK を import せず、global を読まず、clock、RNG、network、filesystem、secrets には、渡された RunDeps を通じてのみ触れます(tenets T2 / T4)。
export interface Rider {
  run(spec: FlowSpec, inputs: JsonValue, deps: RunDeps): Promise<Run>;
  resume(runId: string, deps: RunDeps): Promise<Run>;
}

/**
 * エンジンが必要とするものはすべて注入される (T2/T4)。エンジンは
 * cloud SDK を import せず、global を読まず、clock/RNG/network/fs/secrets
 * にはこれらを通じてのみ触れる。実行場所を変えるには実装を差し替える。
 */
export interface RunDeps {
  store: Store;        // Run + Journal を永続化する (SQLite default)
  journal: Journal;    // append-only event sink
  tools: ToolHost;     // registry から ToolContracts を resolve + invoke する
  clock: Clock;        // now(): string — replay 中は決定論的
  random: Random;      // next(): number — seed 付き、replay 中は決定論的
  secrets: Secrets;    // resolve(ref): string — `secrets` 対応 tool のみ
  env: EnvView;        // allow-listed environment values
  logger: Logger;      // structured, secret-masked
  signal?: AbortSignal;// cancellation
}
環境によって変わる surface 全体が RunDeps に収まっているため、それを配線する launcher は小さくなります。どの launcher も同じ4つのことを行い、CLI launcher と queue-backed cloud worker の間で違うのは最初の1行だけです。
// pseudo-shape, どの launcher でも約30行
async function launch(spec: FlowSpec, inputs: JsonValue) {
  const deps = buildRunDeps(env);     // 1. この環境用に capability を配線する
  const rider = createRider(config);  // 2. エンジンを構築する
  const run = await rider.run(spec, inputs, deps); // 3. 実行する
  return present(run);                // 4. host 向けに結果の形を整える
}
そのため、新しい launcher は一度で読み切れるほど小さくなります(tenet T5)。SQLite store、filesystem artifacts、file / OS-keychain secrets、local Chromium というローカルデフォルトについては CLI launcher を参照してください。cloud-backed な実装については デプロイレプリケーション を参照してください。
Rider は createRider(config) という factory によって構築され、この factory が config を closure に保持します。class も singleton もなく、実行間で変更される global もありません。

Config knobs

小さな config object は createRider に渡され、capability ではなくエンジンに属する設定を保持します。

config.maxConcurrency

ready な手順は config.maxConcurrency を上限として並行実行されます。この値は launcher が選びます。ローカル実行でのデフォルトは CPU-bound であり、スケールアウト時の差し替え先は worker pool または autoscale tier です。
const rider = createRider({ maxConcurrency: 8 });
foreach を持つ手順は N 個の child invocation に fan out し、その concurrency も同じ方法で制限されます。concurrency が変えるのは wall-clock だけで、記録される結果は変えません。独立した手順は2つの実行間で異なる順序で commit されることがありますが、Journal は effect の total order を記録し、foreach の gather は index で key 付けされるため、どちらの実行も同一にリプレイされます(tenet T2)。したがって、maxConcurrency を上げることは再現性の観点では常に安全です。実行 を参照してください。
maxConcurrency は throughput を調整するもので、動作を変えるものではありません。flow の出力を変えられる concurrency 設定は存在しません。2つの configuration が異なる結果を生むなら、それは tuning choice ではなく bug です。

schema_version negotiation

FlowSpec は schema_version literal(現在は "1.0")を持ちます。forward-compatibility は lenient parsing ではなく、明示的な version negotiation によって扱われます。すべての schema が .strict() であるため、Rider は認識できない field を黙って無視しません。 Rider は、自分が support する schema_version range を宣言します。ルールは次のとおりです。
状況挙動
FlowSpec minor が Rider の知る最新 minor 以下(same major)実行されます。additive change は minor を上げます(1.0 → 1.1)。
FlowSpec minor が Rider の support する最新 minor より大きいはっきり拒否されます。coerce も partial parse もされません(tenet T7)。
major version bump保存済み FlowSpecs を書き換える migrate(1.x → 2.0) codemod が必要です。
Rider が support するより高い minor が刻印された FlowSpec は、推測で処理されるのではなく拒否されます。前に進む方法は常に、より新しい Rider を実行することです。schema_version を尊重できない Rider は、coerce せずに loud に失敗します。
higher-minor の FlowSpec が黙って coerce されたり best-effort parse されたりすることは決してありません。はっきり拒否するのが安全な挙動です。これにより、Run は、渡された spec を完全に理解する Rider 上でしか発生しないことが保証されます。
versioning model の全体像は FlowSpec IR を、schema definitions は スキーマ を、関連する compile-time catalogue は 診断 を参照してください。

差し替えが再現される理由

ラップトップで検証済みの flow が buildRunDeps の差し替え後にも再現されるのは、2つの事実が組み合わさるためです。
1

identity は digest

FlowSpec の identity は canonical form の sha256 であり、その digest は flow の構造 pinned requires を対象にします。それぞれの requires は解決済み tool contract digest を持ちます。flow digest を pin することは、dependency closure 全体を pin することです。クラウドで実行される daily-price-watch は、ラップトップで使ったものとまったく同じ search-price@1.2.0 contract digest を使います。
2

エンジンは環境を持たない

Rider は cloud SDK を import せず、ambient global も読みません。storage、transport、secrets、browser はすべて RunDeps を通じて届きます。SQLite Store を Postgres に、または local Chromium を remote pool に差し替えることは、作業が永続化され実行される場所を変えるだけであり、flow が計算する内容を変えるものではありません。
Secrets は、決して移動せず、記録もされない唯一の capability です。Secrets は各 Run で destination environment 自身の Secrets provider によって fresh に resolve され、secrets を宣言する tool にだけ注入されます。そのため、daily-price-watch の webhook secret は、それを実行する環境によって供給され、記録された Journal では安定した {secret:<slot>} placeholder として redact されます。シークレット実行 を参照してください。
いいえ。FlowSpec は安定した digest を持つ plain JSON であり、すべての launcher で一定です。変わるのは、launcher が渡す buildRunDeps 実装です。ラップトップで検証済みの flow は、FlowSpec と Pack の digest が同一であるため、クラウドでも再現されます(tenet T3)。
いいえ。独立した手順は並行実行されることがありますが、Journal は effect の total order を記録し、foreach は index で gather するため、並行した手順を異なる順序で commit した2つの Run も同一にリプレイされます。maxConcurrency が影響するのは wall-clock だけです。
Secrets は artifact の中を移動しません。isolated または remote environment が実行時に自身の Secrets provider を通じて供給し、secrets を宣言する tool にだけ注入されます。Secrets が Journal に記録されることはありません。すべての Run、resume、--rerun replay で live に再 resolve されます。
FlowSpec、pinned Pack、必要に応じて記録済み Journal を isolated environment に持ち込み、connectivity なしで再現します。Secrets は boundary 自身の Secrets provider によってローカルに供給されます。Air-gapped deployment を参照してください。

Launcher

1つの FlowSpec が背後で実行される front です。CLI、REST、MCP、cron、CI、container、embedded library を含みます。

実行

Rider がどのように schedule し、決定論的であり続け、Journal から Run を resume または replay するか。

スキーマ

schema_version literal と、version negotiation を駆動する strict schemas。

シークレット

Secrets が移動せず、記録されず、宣言した tool にだけ注入される理由。