プラットフォーム別のビルド
$ dandori build <file.flow> --target temporal|temporal-python|temporal-go|asl|durable|argo|pydantic-graph [--out <dir>]
ビルドは、一つのプラットフォーム向けのコードを書き出します。そのプラットフォームにできないこと(E050)と、一回の実行がプラットフォームの上限を超えうるワークフロー(E040)はエラーにします。dandori の主なプラットフォームは Temporal です。
Temporal(TypeScript)
--target temporal は、次のファイルを書き出します。
workflow.ts:ワークフローtypes.ts:型activities.ts:makeActivities(own, transport)。生成したタスクと、自分で書くタスクをまとめますio.ts:Transportrules.ts:rulec の TypeScript を包んで、規則をアクティビティにしたもの。use ruleの下にconnectを書いた規則はここに入りません。そのアクティビティはactivities.tsにあり、規則のサービスに送ります(規則をサービスとして呼ぶ)runtime.tsworker.ts:makeWorker(own)client.ts:クライアント
バージョン
ワークフローの型とタスクキューの名前には、.flow のバージョンが入ります(hotel_stay_v1)。そのため、新しいバージョンを古いバージョンと並べて動かせます。makeWorker(own, { deployment }) を使えば、Worker Deployment Versioning も使えます。ビルド ID はコードのハッシュで、実行は始まったときのビルドに固定されます。Worker Deployment Versioning を使わずに同じバージョンのコードを変えるときは、走っている実行の履歴(client.ts の histories で取り出せます)を worker.ts の replay にかけ、再生できることを先に確かめてください。
クライアント
client.ts には次の関数があります。
start:実行を始めます。冪等キーをワークフロー ID から作るので、一度使った ID は使い回しません。answer:コールバックへの応答を Update で送ります。待っていないコールバックへの応答や二度目の応答は、ワークフローが拒否します。status:クエリdandori.statusを読みます。ワークフローが待っている行、案件ごとの状態、待っているイベントが分かります。send:イベントを送ります。
{ searchAttributes: true } を付けて始めると、ワークフローは案件の状態が変わるたびに Search Attribute の DandoriCases("pi=requires_capture" など)を書き換えます。これで、案件の状態から実行を検索できます。
サービスを実装するワークフローの client.ts には、サービスのメソッドごとの関数(fulfill、answerPacking)と、.proto のメッセージの名前を付けた型(FulfillRequest)も入ります。詳しくはサービスを実装するを見てください。
長い実行
フローの一番外にある repeat や、並列でない for は、履歴が長くなると、次のイテレーションの始めで新しい実行に引き継ぎます(Continue-As-New)。目安は 10,000 件で、サーバーが勧めればそれより早く引き継ぎます(dev server は 4,096 件を過ぎると勧めてきました)。引き継ぐ先には、変数、何回目のイテレーションか、ループのリスト、それまでに yield した値を渡します。ワークフロー ID は変わらず、冪等キー、コールバックと子ワークフローの ID も同じままです。Worker Deployment Versioning を使っていれば、引き継いだ実行も同じビルドで動きます。
タイムアウト
timeout を書かないタスクには、ほかのプラットフォームと同じだけの時間を与えます。http・agent・jev は HTTP Task と同じ 60 秒、lambda は 900 秒で、それ以外には独自のタイムアウトを付けません。ワークフロー自身のワーカーが受け持つアクティビティ(queue の無いタスク)はどれもハートビートを送るので、ワーカーがいなくなっても 30 秒以内に気づきます。規則のアクティビティのタイムアウトは 10 秒で、タイムアウトしたらリトライします。
Temporal(Python)
--target temporal-python は、同じものを Temporal の Python SDK 向けに、ワークフローの名前を付けたパッケージとして書き出します。中身は workflow.py(ワークフローと、ワーカーに渡す workflows)、types.py、activities.py(make_activities(own, transport))、io.py、rules.py(rulec の Python を包んだもの)、runtime.py、worker.py(make_worker)、client.py(start、answer、status。サービスを実装するワークフローなら、メソッドごとの関数も入り、名前はスネークケースの answer_packing のようになります)です。ワークフローの型、アクティビティ、コールバックの Update とシグナル、クエリ、ID の名前を TypeScript 版とそろえてあるので、ワークフローとアクティビティを別々の言語のワーカーで動かせます。
Temporal(Go)
--target temporal-go は、同じものを Temporal の Go SDK 向けに、ワークフローの名前を付けた Go のパッケージ一つとして書き出します。go.mod は付けないので、自分のモジュールの中に置き、doc.go に書いてあるモジュールを go get してください。Go の import パスには ASCII の文字しか使えないので、ワークフローの名前が ASCII でないときは .flow のファイル名から名前を付けます(hotel.ja.flow なら hotel_ja)。それも使えなければ workflow にします。
workflow.go:ワークフロー(Workflow)types.go:型と、値が型に合うかを確かめる関数activities.go:自分で書くタスクのインターフェースOwnTasksと、Activities(own, transport)io.go:Transportrules.go:rulec の Go を包んで、規則をアクティビティにしたものruntime.go、values.goworker.go:NewWorker(client, own, transport, deployment)とReplayclient.go:Start、Answer、Send、Status、Histories。サービスを実装するワークフローなら、メソッドごとの関数(Fulfill、AnswerPacking)も入ります
ワークフローの型やアクティビティなど、外から見える名前は TypeScript 版とそろえてあります。
値は JSON のまま運ぶ
ワークフローは、値を JSON の値(map[string]any、[]any、float64、string、bool、nil)のまま運びます。Python 版と同じです。構造体に読むと、フィールドが無いことと null であることの区別がなくなり、構造体の知らないフィールドは落ち、形の違う結果は読む段階で失敗してしまいます。そうした結果はワークフローが自分で見て拒否しなければならないので、構造体には読みません。types.go には、レコードごとの構造体と、列挙ごとの文字列の型もあります。Decode を使えば、タスクの引数をそれらに読めます。自分で書くタスクは、引数を map[string]any で受け取り、SDK が JSON に書ける値なら何でも返せます。
規則
rulec gen <rule> --out <パッケージのディレクトリ>/rulec は、規則ごとの Go を、それぞれ独立したモジュール(rulec/go/holdamount)として書き出します。rules.go の先頭に書いてあるとおり、自分の go.mod でそのモジュールを require し、replace でそのディレクトリを指してください。
既定の Transport
HTTP には net/http、Lambda と AWS の API には AWS SDK for Go v2、OpenAI のエージェントには OpenAI の Go のクライアント、Claude には Anthropic の Go の SDK を使います。パッケージが import するのは、フローが使う SDK だけです。OpenAI には Go の Agents SDK が無いので、Go のクライアントで、Step Functions と同じリクエストを Responses API に送ります。
Go のバージョン
生成したコードが前提にしている Temporal の Go SDK 1.49.0 は、Go 1.26 を求めます。手元の Go がそれより古ければ、go コマンドが Go 1.26 を自分で取ってきます。
AWS Step Functions
--target asl は、JSONata を使う ASL のステートマシンを書き出します。Lambda 関数として呼ぶ規則ごとに、rulec が生成する Python を包んだ Lambda のハンドラーも書き出します。connect を書いた規則は、HTTP Task で規則のサービスを呼びます。connection と、HTTPS の URL が要り、Lambda 関数は書きません。タスクは Lambda 関数、EventBridge の接続を通した HTTP の API、SDK の統合を通した AWS のサービスを呼び、コールバックにはタスクトークンを渡します。ステートマシンには自分で書くコードを置けないので、そのどれにも当たらないタスクはエラーにします(E050)。Temporal でしか意味を持たない機能(on cancel、event、実装するサービスの、実行がいまどこにいるかを聞くメソッド)も同じくエラーにします。
AWS Lambda durable functions
--target durable は、workflow.ts(makeHandler(own, transport) を持つ)、types.ts、tasks.ts、io.ts、runtime.ts を書き出します。Lambda 関数として呼ぶ規則ごとに asl と同じ Lambda のハンドラーも書き出し、durable function はそれを invoke します。connect を書いた規則は、規則のサービスに送るステップになります。自分で書くタスクは、そのコードを動かすステップになります。
Argo Workflows
--target argo は、<workflow>.argo.yaml と caller/ を書き出します。
<workflow>.argo.yamlは WorkflowTemplate です。入力をパラメータinputで受け取り、出力をグローバルなパラメータdd_outputに残します。フローの変数もグローバルな出力パラメータに持ち、どの変数がどのパラメータかは、YAML の先頭のコメントに書いてあります。caller/は、ワークフローのコンテナの中でlambda・http・aws・agent・jevのタスクと規則を動かすプログラムで、package.jsonとDockerfileが付きます。
自分で書くタスクは、自分のイメージ(image)のコンテナで動きます。
タスクの timeout は、Pod の activeDeadlineSeconds になります。Kubernetes は、Pod を受け付けた時点から数えるので、コンテナが起動するまでの時間も含まれます。混んでいるクラスターでは、Pod の起動に数秒かかることがあるので、その分の余裕を見込んだ timeout にしてください。期限が過ぎるのと同時に終わった Pod は、期限を過ぎたものとして扱われ、Argo はタスクが返したエラーの名前を読めません。そのエラーに対する retry は行われません。
pydantic-graph
--target pydantic-graph は、graph.py(graph と、その State と Deps)、types.py、tasks.py(make_tasks(own, transport))、io.py、rules.py、runtime.py を持つパッケージを書き出します。文の一つ一つがノードで、各ノードの戻り値の型に次のノードが書いてあるので、graph.render() でフローの図を描けます。実行の状態は、それを動かすプロセスの中にしかありません。待つときは Deps.clock で眠り、コールバックへの応答は Deps.callbacks に届きます。pydantic-graph 2.x は実行の状態をどこにも残さないので、プロセスが落ちればその実行も失われます。試作や、エージェントの中の短いフローに向いています。
日付のファイルの日付と帳簿の操作は、どのプラットフォーム向けにもビルドできます。日付は規則と同じ呼び出しになり、帳簿の操作は、Step Functions では Lambda 関数、ほかでは渡された chobo のクライアントを Transport を通して呼ぶものになります。Step Functions と Lambda durable functions では呼ぶ日付の lambda が、Step Functions では帳簿の lambda が要ります(E050)。プラットフォームごとに何を書き出し、横に何を置くかは、日付と帳簿にあります。
Lambda durable functions、Argo Workflows、pydantic-graph も、event のタスクと、サービスの、実行がいまどこにいるかを聞くメソッドはエラーにします(E050)。サービスを実装するワークフローは、どのプラットフォームでも、入力と、サービスのメソッドが送ってくるコールバックの応答に、protobuf の JSON が省いたゼロ値を埋めてから読みます。
シナリオと参照インタプリタ
$ dandori scenarios <file.flow> [--out <dir>]
$ dandori run <file.flow> --scenario <file.json> [--target reference|asl|temporal|temporal-python|temporal-go|durable|argo|pydantic-graph]
scenarios は、入力と、各呼び出しが受け取る結果を並べたシナリオを書き出します。全部を合わせると、すべての分岐と、エラーを処理するすべての箇所を通り、案件の状態の変わり方もすべて試します。リストには、空のもの、短いもの、ループの上限より長いものを入れます。run は、シナリオを一つ参照インタプリタで動かし、呼び出しをターゲットが出す形で出力します。ビルドしたコードをこれとどう突き合わせているかは、どうやって確かめているかを見てください。