core には、小さなファーストパーティPack一式が同梱されています。これらは通常のPackであり、エンジンが特別扱いするものは何もありません。そして Tool contract の参照実装として機能します。カスタムToolでできることは、標準Toolも同じ方法で行います。型付きI/Oを宣言し、capabilityを宣言し、(input, ctx) に対してpureに実行します。

Packの概要

各Packは、関連するToolをいくつかまとめています。Capabilities 列は、各Toolが自身の ctx で要求できるものを示します。capabilityが存在するのは、contractがそれを宣言した場合だけです(原則 T2、構造上強制されます)。? は、効果を持つ入力がそれを要求する場合にだけToolが宣言するcapabilityを示します。
PackToolsCapabilitiesStatus
std/httphttp.get, http.post, http.request, http.paginatenet, secrets?✅ 実装済み
std/transformtransform, jq, templateなし(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✅ 実装済み
std/browser は、完全に実装済みのTool factoryとして browserTool()(Recordingからの決定論的なPlaywrightリプレイ)を提供します。browser.record@browserflow/browser にあるオーサリング時のキャプチャ関数です。そのライブPlaywright driverはthrowするstubです。現在の決定論的な継ぎ目では、browserflow record --ops <file> で事前に解決済みのoperationsを渡してください。
std Toolは、http.posttransform のような裸のkindで参照されます。これは、pinされたstd pack内の互換性がある最新バージョンへ解決されます(references & versioning)。daily-price-watch Flowは、そのうち4つを使います。価格を調べる std/browser Tool、それを整形する std/transform 手順、そしてsecret webhookへ条件付きでpostする std/http です。

身につけておくべき3つのルール

capability表には、これらのPackを相手にオーサリングするとき重要になる3つの挙動が隠れています。どれも原則から直接導かれ、どれもFlowの再現性を保つものです。

std/http と secrets? capability

std/http はデフォルトでは net のみです。認証情報付きURL、認証ヘッダー、トークン本文のような、シークレットを含む入力がbindされたそのときだけ secrets を追加します。これが、secret. 参照が secret_leak diagnosticに引っかからず http.post に流れ込める理由です。bindされたシークレット入力が secrets capabilityを宣言し、それによって境界ルールに違反するのではなく、境界ルールを満たします(validation & diagnostics)。 daily-price-watchでは、notify 手順が secret.webhook をURLとしてbindします。
.step("notify", http.post(
  (c) => c.secret.webhook,        // シークレット入力 → http が `secrets` を宣言
  (c) => c.steps.shape.output,
))
- id: notify
  use: http.post
  when: ${{ steps.shape.output.price < input.threshold }}
  with:
    url:  ${{ secret.webhook }}   # シークレット入力 → http が `secrets` を宣言
    json: ${{ steps.shape.output }}
シークレット入力がbindされていない場合、同じ http.postnet のみです。このcapabilityは需要駆動です。設定で secrets を有効化するのではなく、シークレットをbindすることで得ます。
secret. 参照は、secrets capabilityを持つToolの内部でのみ解決されます。参照はFlowSpecとJournalを通って運ばれますが、平文の値は実行時に注入され、どちらにも書き込まれません。シークレット — 境界 を参照してください。

std/control の uuid - 新しいidの認可された生成元

std/controluuid は、新しい識別子を生成する唯一の認可された方法です。seedされた random capabilityから値を引き、その出力をJournalに記録します。そのため、生成されたidはリプレイ時にも安定します。新しい値ではなく、記録済みの値が返されます。 これこそが、expression languageuuid() built-inがない理由です。非決定性が存在してよい場所は、記録されるToolという、ただ1か所だけです。expressionの内部には決して置きません。expressionは小さく、pureで、副作用のないサブ言語です。もし uuid() がexpression関数だったなら、static checkerは、expressionが非決定性を隠していないことをもはや証明できません。
transform の内部で、ambientな crypto.randomUUID() やタイムスタンプをidとして使う小技に手を伸ばさないでください。transform には持ち込める randomclock もなく、記録されるToolの外で生成されたidはリプレイ時に分岐します。idには uuid を、時刻には now 参照(注入されたclock)を使ってください。

std/model - 意思決定者ではなくeffect

モデル呼び出しは完全に正当なToolですが、std/modelmodel.complete他と同じeffectです。model.complete は3つの入力を取ります。endpoint(provider URL)、request(passthrough JSON object。messages、model、paramsなど、providerが期待する任意のbody形状)、そして secret 入力として宣言された任意の authorization 値です。そして、注入された net capability経由で、requestendpoint にPOSTします。
1

リクエストとレスポンスは記録される

Toolは fetch を直接呼び出しません。呼び出すのは ctx.net.fetch だけです。Riderがそのcapabilityをwrapするため、http 呼び出しやbrowser extractionと同じように、リクエストとそのレスポンスの両方がJournalに書き込まれます。authorization の値は、他のシークレット入力とまったく同じように、journalingの前にredactされます。
2

すべてのリクエストは冪等性キーを持つ

model.completectx.idempotencyKey から Idempotency-Key ヘッダーを付与します。これはrun id、step id、手順の入力から導出されたdigestです。そのため、providerに対するcompletionがretryされても、二重課金されるのではなくdedupeできます。
3

それ自体がcontrol flowを動かすことはない

モデル出力はデータです。branch predicateや when: 条件は、記録済みの値に対するpure expressionです。次にどの手順が実行されるかについて、モデルに決定権はありません。
4

リプレイは記録済みレスポンスを返す

リプレイ時、model.complete はすでにJournal内にあるレスポンスを返します。ネットワーク呼び出しはありません。モデルに到達可能かどうかに関係なく、Runはbyte-identicalです。
このToolは意図的にvendor-neutralです。provider SDKを埋め込まず、request を特定vendorのschemaへ整形せず、返ってきたものをそのまま { status, response } として記録します(response はpassthrough JSONです)。provider固有のrequest/response整形はcallerの責務です。通常は上流の transform 手順で行います。 このPackのスローガンは、AIはpinできるデータソースであり、意思決定者ではない、です。これはオーサリング時のAI支援(authoring with AI at the edges)と一貫しています。モデルはFlowを書いたりToolを記録したりするのを助けることがありますが、compileされpinされた後のFlowは、記録される手順として明示的に model.complete を呼び出さない限り、loop内にモデルを置かずに実行されます。

表の残りを読む

transformjqtemplate はcapabilityを宣言しません。これらはnetwork、filesystem、clock、randomnessに到達できません。呼び出せる ctx.netctx.fs などは文字通り存在しません。これによってエンジンはtransform手順をpureで再実行可能なものとして扱えます。transform はexpression(with.expr)を持ち、jq はjq programを適用し、template はtemplateをrenderします。いずれも決定論的なJSON-to-JSONまたはJSON-to-stringです。
shell.run はsubprocessを起動するために spawn を宣言し、入力が読み書き対象のpathにbindされた場合だけ fs を宣言します。すべてのeffectful toolと同じく、観測可能な結果が記録されるため、その手順は再spawnせずにリプレイされます。
fs.readfs.writefs.globfs を宣言します。読み取りはJournalに記録されるため、Flowはofflineでリプレイできます。path入力は、他の入力と同じように境界で型付けされvalidateされます。
browser.tool は、記録済みの決定論的なPlaywright sessionをリプレイします。browser.record はdemonstrationからそれを作成します。どちらも browser(および net)を宣言します。このPackには専用ページがあります。下を参照してください。browser automationは主要なTool kindであり、「一度作成する」(record)と「ずっと実行する」(tool)に分かれているためです。
noop は何もしない手順です(downstream referenceを持たない no: branch armは、compile時にdead noopとして省略されます)。assert はinvariantを検査します。delay は待機します。注入されたclockが必要なときに clock を宣言します。gate はguardします。uuidrandom から記録済みidを生成します。clockrandom? は、それらを必要とするToolだけが宣言することを反映しています。

関連

カスタムTool

defineTool で独自のToolを作成します。標準Packが実装しているものと同じcontractです。

ブラウザーTool

一度記録し、ずっとリプレイする。std/browser がdemonstrationを決定論的なPlaywrightへ変換する仕組み。

シークレット — 境界

secret. 参照が漏洩するのではなく、std/http の境界ルールを満たす理由。

ToolとPack

完全なTool contract、versioning、そしてPackがbundleされ複製される仕組み。