@browserflow/agent です。これは Browserflow monorepo 内のオープンソースパッケージであり、ホスト型プロダクト専用に作られた特別なものではありません。これは RecordDriver を実装しています。つまり、手書きのテストドライバーから record() が受け取るのと同じ接続点を、Claude と実際の Playwright ページを使う ReAct ループで裏付けたものです。
このページは、デモンストレーションを実行するエンジン側パッケージ(
@browserflow/agent, Apache-2.0)について説明します。実行中に人間が見るもの、つまり2ペインのコックピット、引き継ぎプロンプト、レビュー手順については、Build Studio: 記録、レビュー、公開 を参照してください。どこに接続されるか
createAgentDriver は、packages/browser/src/replay.ts のスクリプト化されたドライバーとまったく同じように RecordDriver を構築します。これは record({ driver }) に渡され、demonstrate() は record() が Recording に正規化する解決済みの Operation[] を返します。これをサポートするためにエンジンコードを変える必要はありません。モデルは、人間やfixtureが供給できるのと同じデモンストレーションを供給する、より賢い入力元にすぎません。
ループが触れるものはすべて注入されます。page(オーサリング時は実際の Chromium、テストではfake)、model(ModelClient、テストではスクリプト化)、onEvent、そして ask です。ループモジュール自体は @anthropic-ai/sdk も playwright もimportしません。そうするのは anthropic-model.ts だけで、しかも遅延して行います。これにより、ドライバーのspec suiteは Chromium なし、モデル呼び出しなしで実行できます。
ReAct ループ
demonstrate() の各ターンは、maxIterations で制限された同じ4拍のサイクルを実行します。
observe
observe(page) はライブページを Observation としてスナップショットします。内容は、base64 PNGスクリーンショット(vision)、テキストとして描画されたアクセシビリティツリー(Locator.ariaSnapshot() 経由)、現在のURLです。これに加えて、タスク、宣言済みの入力/シークレットスロット名、ここまでにキャプチャされた操作、現在の手順に対する recentFailures があれば、それらがモデルの ObservationInput になります。model.next
opts.model.next(input, { signal }) は ModelClient に Decision を要求します。これは、(ナレーションとしてstudioへstreamされる)thought と、1つの Action で構成されます。Action は act(操作を実行)、need_login / need_input(人間のために一時停止)、または done です。具体的な実装である createAnthropicModelClient は、スクリーンショットとプロンプトを Claude(デフォルトでは claude-opus-4-8)へ送り、単一の decide tool call を強制します。これはループに届く前に zod で検証されます。apply
act のdecisionでは、captureOp が applyToPage(page, op, samples) を呼びます。操作(クリック、入力、ナビゲーション)は実際にライブページで起こります。samples により、事前に宣言された入力パラメータは空のプレースホルダーではなく代表的なサンプル値を入力できるため、最初のデモンストレーションから実データでFlowを試せます。done はループを正常終了します。need_login と need_input は await イベントをemitし、人間が応答するまで opts.ask(...) でblockします(Build Studio の引き継ぎ手順を参照)。
ロケータの信頼境界
Action.act は op: Operation を保持し、その locator は要素の見つけ方に関するモデルの推測です。それはもっともらしいが壊れやすいCSS selectorかもしれませんし、ページ上の3要素にたまたま一致するrole/nameの組かもしれません。ドライバーは次のように動きます。
- そのopをライブページに適用します(モデルの意図を尊重するため)。
resolveLocatorを呼び、同じ要素にも解決される安定したロケータを実際のDOM(page.candidatesFor)からすべて調べ、最も優先度の高いものを選びます。- 解決済みロケータと完全な
LocatorEvidence(chosen,candidates,match_count)を記録します。モデルの生selectorがRecordingに入ることはありません。
driver.ts の withResolvedLocator は、opが history にpushされる前に、必ず op.locator を resolveLocator が返した値で上書きします。唯一の例外は navigate です。これは解決すべきロケータを持たないため、url はモデルが与えたとおりに記録されます。
同じパターンは need_input にも拡張されます。ドライバーは typeInto で人間の回答をページに入力し、その後も fill を記録する前に、モデルが提案したフィールドロケータに対して resolveLocator を呼びます。human-in-the-loop の回答であっても、この境界を迂回しません。
シークレットの二重防御
モデルが認証情報を入力することは実際のリスク面です。system promptは{from:"input",slot} / {from:"secret",slot} を優先し、literalを使わないよう指示しますが、promptは保証ではありません。コードベースはこれを、2つの異なるパッケージがパイプライン上の2つの異なる地点で二重に強制する境界として扱います。
Stream時: マスク
packages/agent/src/describe.ts — looksLikeSecretLiteral / maskSecretsInText は、すべてのSSE emission(thought, recovering, error、および redactOpForStream 経由の構造化された step op)を、オーサリングbusに届く前にscrubします。studioから見えるものでは、シークレットらしいliteralは «redacted-secret» になります。永続化時: 拒否
packages/browser/src/record.ts — assertNoSecretLeak は、record() がドライバーの出力を正規化するときに実行されます。既知の認証情報の形状に一致する、高エントロピーである、または呼び出し元が供給した既知のシークレット値を含む {from:"literal"} 値は、完全に拒否されます。つまり、record() はそのバイト列を含む Recording を永続化するのではなくthrowします。looksLikeSecretLiteral を共有します(加えて、その CREDENTIAL_PATTERNS list。PEM blocks、sk-…、AWS access keys、GitHub tokens、Slack tokens、JWTs、Google API keys、そして不透明な高エントロピーtoken向けのShannon entropy fallback)。どちらのパッケージも相手側のcopyをimportできません(browser packageはpublic entryだけをexportし、agent packageはbrowser internalsを取り込んではいけません)。そのため、この関数は両方のファイルで逐語的に重複しています。
agent driverの describe.ts copyは、永続化時のcopyには不要な追加入力を1つ受け取ります。knownSecrets、つまりthis session中に人間が need_input/need_login promptへの応答として入力したplaintext値の集合です。これらの値は純粋にメモリ上で追跡され(createAgentDriver の knownSecrets set)、エントロピーに関係なくすべてのSSE emissionからscrubされ、それ自体が永続化されることはありません。
暴走防止ガード
モデルがブラウザーを操作すると、スクリプト化されたドライバーでは起きない失敗の仕方をすることがあります。行き詰まる、ページを読み間違える、不正な内容を返す、といったものです。4つの独立したガードが被害を制限し、それぞれ何かをabortする前にrecovering イベントとして表面化します。
反復アクション検出(no-progress guard)
反復アクション検出(no-progress guard)
opSignature(op) は、valueを無視して操作をop-kind + targetに折りたたみます。同じフィールドへの2回のfillは、slot名が異なっていても衝突します。解決済みopのsignatureがすでに seenSignatures にある場合、ドライバーは重複を記録しません。代わりに、モデルへrecovery hint(“interact with a DIFFERENT element … or return done”)を渡し、consecutiveRepeats をincrementします。真に新しいtargetはcounterをzeroにresetします。MAX_CONSECUTIVE_REPEATS(3)に達するとthrowしてデモンストレーションを終了します。これにより、自分の効果を読み戻せないモデル(例: まだ目に見える結果がないsearch box)が Recording を大量の不要なrepeatで埋めることを止めます。decode失敗の許容
decode失敗の許容
tool callがschema validationに失敗すると、
model.next() はthrowすることがあります。これは、寛容な normalizeDecision/normalizeOp coercionでも救えなかったドリフトです(anthropic-model.ts で処理されている extract の kind/read collision、GitHub #39を参照)。単発のbad turnは、session全体をkillするのではなく recovering として表面化し、新しいobservationでretryされます。turnのdecodeに成功した瞬間、consecutiveDecodeFailures はresetされます。MAX_CONSECUTIVE_DECODE_FAILURES(3)に達するとthrowします。maxIterations
maxIterations
外側のループは
DEFAULT_MAX_ITERATIONS = 64 turnsで制限されます(AgentDriverOpts.maxIterations で設定可能)。そのbudget内にモデルが {kind:"done"} を返さない場合、demonstrate() は永遠にloopするのではなくthrowします。AbortSignal による即時停止
AbortSignal による即時停止
AgentDriverOpts.signal は停止ボタンです。ループは各iteration境界で signal.aborted を確認しますが、境界だけのpollingでは十分ではありません。turnの途中で停止をクリックしても、進行中の model.next() callやすでに実行中のPlaywright actionは中断されないからです。raceAbort() は、すべての awaited step(モデル呼び出し、ページ操作)をsignalとraceさせます。そのため、停止が発火した瞬間にループは待機をやめ、DemonstrationCanceled でrejectします。たとえ基盤となるcallがbackgroundで完了しつつあってもです。さらに model.next は、実際のAnthropic HTTP requestへ signal をthreadするため、そのcall自体がabandonされるだけでなくabortされます。cancelは単一の {type:"canceled"} eventをemitします(error ではありません。意図的な停止はfailureではないためです)。また、呼び出し元が特に DemonstrationCanceled をcatchすることで、他のすべてのfailure modeと区別されます。モデルの接続点
ModelClient は1メソッドのinterfaceです。model.ts で定義された next(input: ObservationInput, opts?): Promise<Decision> で、SDKはまったくimportしません。コードベース内で @anthropic-ai/sdk のimportを許されているモジュールは anthropic-model.ts だけで、それも next() 内で遅延して行います。そのため、clientを構築するだけ、あるいは @browserflow/agent をimportするだけでは、@anthropic-ai/sdk はloadされません。
createAnthropicModelClient(実際のClaude、強制された decide tool call、デフォルトは claude-opus-4-8、より安価なoptionとして claude-sonnet-4-6 が利用可能)と、ドライバー自身のtest suiteが供給する任意のスクリプト化された ModelClient です。ループは常にinterfaceだけに依存するため、driver spec(driver.spec.ts)は、上記すべてのガードを含む完全なReActループを、Chromiumなし、モデル呼び出しなしでexerciseします。
SDKからはまだ利用不可 / まだ配線されていません。モデル選択は
AnthropicModelConfig 内の静的default(claude-opus-4-8、cheapModel として claude-sonnet-4-6)であり、driver内でsessionごとにcheap modelを自動選択するものはまだありません。sessionごとのコスト調整が必要な場合は、それをfeatureとして文書化する前に、現在の挙動をownerに確認してください。関連ページ
Build Studio
同じループの人間向け側面です。2ペインのコックピット、ライブブラウザー表示、そして
need_login / need_input の引き継ぎプロンプトを扱います。シークレットと境界
Recordingが公開された後、シークレット参照がFlowSpec、Journal、Packの外に留まる仕組みです。このページのstream/永続化防御がオーサリング時に保護する境界の、runtime側の半分です。
SDK & YAML
Recordingが最終的に
browserTool() 経由でコンパイルされる先である、2つのオーサリングsurfaceです。トラブルシューティング: サイトが変更された
このループがキャプチャした解決済みロケータが、リプレイ時にライブページと一致しなくなった場合に何が起きるかを説明します。