megálosワークフローのテスト
1. スコープと非目標
このスキルは、AIコーディングエージェントに、megálosワークフローYAMLファイルの動作を出荷前に検証する方法を教えます。主なインターフェースはdry-run CLI(python -m megalos_server.dryrun)で、実際のLLMの代わりにモック入力ソースを使用して、本番ランタイム上でワークフローを実行します。dry-runは「これらのモック応答が与えられたとき、このワークフローはステップ実行、分岐、ゲート処理、サブワークフローへの移動、終了を意図した通りに行うか?」という問いに答えます。これは高速で、オフラインで、決定論的です。LLM呼び出しもMCPラウンドトリップも行いません。実際のmegálosデプロイメントに対するライブ実行は、dry-runでは到達できないケース(実際のLLM出力分岐や実際のレジストリmcp_tool_callの逸脱)のための、制限付きのセカンダリインターフェースとして利用可能であり、§6(後のタスクで作成)で扱います。
スコープ内。 対話型dry-runレシピ(§3)。スクリプト化レスポンスフィクスチャ(§4)による回帰再生。ロード時のクロスワークフローエラー — unknown_call_target と call_cycle_detected — と、dry-runがそれらをcreate_app()ブートストラップ経由でどのように表面化するか(§5)。エージェントが読み取る必要のある終了コードとエラーバナーの契約。
スコープ外。 ファイル単位のワークフロー検証 — 関連スキルvalidating-workflowsでカバー。ワークフロー作成 — authoring-workflowsでカバー。ドメインリポジトリのパッケージ化、Horizonデプロイメント、レジストリセットアップ — deploying-workflowsでカバー。フレームワークレベルのテストと、megálosリポジトリ自体へのワークフローフィクスチャの貢献:pytest/conftestは、このスキルが教えるインターフェースではありません。 testing-workflowsスキルは、dry-runを使用してワークフローの動作を検証します。フレームワークテストの作成は、megálosへの貢献者向けの別個の活動であり、ここでは明示的にスコープ外です。実際のLLM呼び出し — dry-runは決してLLMを呼び出しません。ループ内に実際のLLMを必要とする作成者は、ライブランインターフェース(§6)またはデプロイされたインスタンスを使用します。
読者対象。 読者は、ワークフロー作成者に代わって行動するAIコーディングエージェントです。dry-runの設計は、この読者を明確に想定しています:作成者はテストエンジニアではありません。 ワークフロー作成者は、ディレクティブテンプレートがレンダリングされ、ゲートがリストされ、各ステップでスキーマフィードバックを確認する必要があります — pytestケースを作成する必要はありません。以下の対話型およびスクリプト化dry-runレシピは、その読者に合わせて形作られています。
2. プライマリモードとしてのdry-run
python -m megalos_server.dryrunはワークフローをロードし、本番実行パスを通して駆動し、モデルを呼び出す代わりに各ステップのモック「LLM応答」をstdin(またはスクリプト化YAMLファイル — §4参照)から読み取ります。dry-runは並列シミュレーターではありません。モック入力ソースを持つ本番ランタイムです。ライブサーバーが実行するのと同じstart_workflow、submit_step、分類、output_schema検証、リトライ処理、分岐選択、サブワークフロー降下が、dry-runによって実行されます。dry-runが受け入れるものはすべてサーバーも受け入れます。dry-runがブートストラップ時に拒否するものはすべて、サーバーも起動時に拒否します。
dry-runが実行するもの。 ステップレンダリング(バナー、前提条件、ゲート、ディレクティブ)。output_schema検証とリトライインターフェース(検証ヒント、残りリトライ回数、予算枯渇)。分岐選択と分岐デフォルト解決。前提条件スキップ検出 — ステップのprecondition:がランタイムで満たされない場合、dry-runはstdoutにSkipped: <step_id>を出力して進行します(§3にインターフェース契約、§9によくある間違いの形を参照)。サブワークフロー降下と親の再開。ターゲットの親ディレクトリ内の全ワークフローセットに対するcreate_app()ロード時チェック。これにはunknown_call_targetとcall_cycle_detectedが含まれます(§5)。
dry-runが実行しないもの。 実際のLLM生成品質。実際のMCPレジストリラウンドトリップ(mcp_tool_callステップはロード時のスキーマおよび構造チェックでカバーされますが、実際のレジストリ呼び出しはライブランの関心事です)。本番規模のセッション永続化、レイテンシ、または並行性。作成者がこれらのいずれかを検証する必要がある場合、ライブランインターフェースがツールです(§6)。
終了コード契約。 dry-runはworkflow_completeで0、その他の終了ステータスで1を終了します — 本番の_TERMINAL_STATUSES frozensetは{"workflow_complete", "error", "session_escalated", "workflow_changed"}です。非ゼロの終了は常に、stderrへのデコードされたエラーバナーとペアになります。dry-runをスクリプト化する場合(CIスモークテスト、プリコミットチェック)、終了コード0を唯一の成功として扱う必要があります。
3. 対話型dry-runレシピ
対話型モードが一般的なケースです。エージェント(またはその人間のオペレーター)が各ステップでモックLLM応答を入力し、ワークフローが展開されるのを監視します。呼び出し:
python -m megalos_server.dryrun workflows/my_workflow.yaml
dry-runはターゲットの親ディレクトリ内のすべての*.yamlファイルをロードし(サブワークフローcall:ターゲットを解決できるようにするため — §5参照)、その後ワークフローをブートストラップしてREPLに入ります。各ステップで、ランタイムはバナー、オプションの前提条件行、ゲート、およびステップのディレクティブテンプレートをレンダリングし、>でプロンプトを表示します。エージェントはモックLLM応答を入力してEnterキーを押します。ワークフローが進行します。
最小限の3ステップ線形ワークフローセッションは次のようになります:
$ python -m megalos_server.dryrun workflows/example.yaml
=== Step: alpha — First step ===
<directive template rendered here>
> ok
=== Step: bravo — Second step ===
<directive template rendered here>
> ok
=== Step: charlie — Third step ===
<directive template rendered here>
> ok
Workflow complete
終了コード0。3つのモック応答が3つのステップをworkflow_completeまで駆動しました。
エージェントが遭遇する可能性のあるプロンプト。
-
ステップ応答プロンプト(
>)。 すべてのステップでのデフォルトプロンプトです。エージェントが入力したものはすべて、LLMによって生成されたかのようにランタイムに渡されます。output_schemaを持つステップの場合、応答はスキーマに一致する有効なJSONである必要があります。無効なJSONペイロードは検証エラー(stderrにRetries remaining: N行が付随)をトリガーし、同じステップで再プロンプトが表示されます。リトライ予算を使い果たすと、Max retries (N) exceededバナーと終了コード1で実行が終了します。 -
分岐選択プロンプト。
branches:ブロックを持つステップに到達すると、dry-runはまずステップ応答を消費し、次に次のように表示します:Branches: 1. first_branch_target 2. second_branch_target [default] Choose branch [1-2, empty = default]:[default]タグは、セレクターがステップ応答に対して評価されたときに選択されたブランチを示します。空の入力はデフォルトに解決されます。 -
前提条件行。
precondition:を持つステップは、エントリ時にPrecondition: <ref> == "<value>"またはPrecondition: <ref> is presentとして、ディレクティブの前にレンダリングされます。 -
スキップ表示(
Skipped: <step_id>)。 ステップの前提条件がランタイムで満たされない場合、dry-runはそのステップのバナーやプロンプトをレンダリングしません。代わりにstdoutにSkipped: <step_id>を出力し、次のステップに進みます。前のステップで以前にレンダリングされたPrecondition:行が暗黙の説明になります。スキップ検出は前提条件を持つステップのみをフラグします。単に選択されなかった代替ブランチは「スキップ」されません — それらは到達されなかったパスです。意図せずにステップをスキップした実行は、依然として終了コード0でworkflow_completeで終了するため、意図しないスキップの唯一のシグナルはstdoutのSkipped:行です。すべてのSkipped:行を読み、そのステップを抑制した前提条件がそれを抑制することを意図していたことを確認してください。 -
EOF(Ctrl-D)。 ワークフロー途中でstdinを閉じると、stderrに
Dry-run aborted by user (EOF)、終了コード1で中断されます。
サブワークフロー降下。 ステップのcall:ターゲットが兄弟ワークフローに解決される場合、dry-runはEntering sub-workflow <name>を出力し、子のバナーをネストレベルごとに2スペースインデントします。子のworkflow_completeで、dry-runはReturned from sub-workflowを出力し、次のステップで親を再開します。
クリーンディレクトリの規律。 dry-runは親ディレクトリ内のすべての*.yamlをロードするため、壊れた兄弟YAMLはターゲットのロードをブロックします。ブートストラップ時のエラーバナーがターゲット以外のファイルを指定している場合、兄弟に問題があります — 修正するか、ターゲットをそれ自身とそのcall:ターゲットのみを含むディレクトリに移動してください。dry-runがロード失敗時に出力するフレーミング段落は次のとおりです:
Failed to load workflows from <parent_dir>: <exception>
Note: dry-run loads all *.yaml files in the parent directory (required
for sub-workflow 'call' target resolution). If the error above names a
file other than <target.yaml>, a sibling workflow has a problem — fix
it, or move <target.yaml> to a directory containing only it and its
call targets.
4. スクリプト化レスポンスフィクスチャ
回帰再生とCIスモークテストのために、dry-runは非対話的にワークフローを駆動するYAMLフィクスチャを指す--responses-fileを受け入れます:
python -m megalos_server.dryrun workflows/my_workflow.yaml \
--responses-file tests/responses/my_workflow_happy.yaml
レスポンスファイルは固定された形状を持ちます。必須のversion: 1キーと必須のentries:リストを持つトップレベルのマッピングです。各エントリは、必須のstep_id:を持ち、response:(ステップコンテンツ用)またはbranch:(分岐選択プロンプト用)の正確に1つを持つマッピングです。両方は不可、どちらもないことも不可です。最小限の3ステップハッピーパスフィクスチャ:
version: 1
entries:
- step_id: alpha
response: ok
- step_id: bravo
response: ok
- step_id: charlie
response: ok
順序付き消費。 ルートワークフローフレーム(降下深度0)では、エントリは順番に消費されます。最初のエントリは最初のステップのstep_idと一致し、2番目のエントリは2番目のステップと一致する必要があります。スペルミスや順序のずれは実行を中断します。サブワークフロー降下(深度 > 0)中、dry-runは一致が見つかるまで一致しないエントリをスキップするため、親フレームと子フレームのエントリはファイル順にインターリーブされる可能性があります。ただし、各フレームの独自のエントリは、そのフレームの実行順序で出現する必要があります。
ずれ検出バナー。 パーサーとスクリプト化消費レイヤーは、終了コード1で終了する前に、stderrにそのままのバナーを出力します。フィクスチャが間違っている場合、作成者はこれらの文字列を正確に確認します:
- バージョンフィールド欠落:
Responses file missing required 'version' field. Expected: version: 1 - 不明なバージョン:
Unknown responses-file version: <n>. Supported: [1] - 応答/分岐の排他違反(両方存在):
... must have exactly one of 'response' or 'branch', not both. - 応答/分岐の排他違反(どちらも存在しない):
... must have exactly one of 'response' or 'branch'. - ルート深度でのステップIDずれ:
Script entry expected step_id=<entry_step>, REPL at step_id=<repl_step> - エントリタイプの不一致(ステップが応答を期待、スクリプトが分岐を提供):
At step <repl_step>, expected step response but script provided branch selection(または、対称的にexpected branch selection but script provided response) - 枯渇(エントリが少なすぎる):
Responses file exhausted at step <repl_step> (expecting: <expected_label>) - 完了時に未使用のエントリ(エントリが多すぎる):
Responses file had <N> unused entries after workflow completion
未使用エントリのガードは、実行が終了する直前にworkflow_completeで実行されます。必要以上に長いフィクスチャは、黙過されるのではなく、スクリプトの誤りです。すべてのずれのケースは終了コード1で終了するため、終了コード0を成功と見なすCIは、上記のいずれかで明示的に失敗します。
インタラクティブモードとのカバレッジパリティ。 スクリプトモードは、インタラクティブモードと同じインターフェースを駆動します:output_schema検証とリトライバナー(無効なresponse:は同じ検証エラーの再プロンプトをトリガーします)、分岐選択(branch:エントリ経由)、およびサブワークフロー降下(インターリーブされた親/子エントリを使用)。ワークフローをエンドツーエンドでスレッド化するスクリプト化フィクスチャは、そのワークフローの標準的な回帰アーティファクトです。
5. ロード時のクロスワークフローエラー — unknown_call_target と call_cycle_detected
2つのクロスワークフローエラーは、ファイル単位の検証ではなく、ワークフローセットのロード時に表面化します。両方とも、validating-workflowsスキルが前方参照としてフラグしたのと同じパスから出力されます:
内在化する価値のあるニュアンス:これらの2つのエラーは、ワークフローセットのロード時に表面化し、ファイル単位の
python -m megalos_server.validate <file>実行時ではありません。ファイル単位のバリデータは、外部ワークフロー自身の構造的および意味論的なルールのみをカバーします。サーバーが最終的にロードする他のワークフローについて知ることはできません。call-targetとサイクルのチェックは、ワークフローディレクトリ内のすべてのワークフローYAMLが個別にロードされた後、サーバーのcreate_app()内で実行され、MCPアプリが構築される前に実行されます。実際には、これは以下を意味します:call:ステップを持つワークフローを作成または変更するエージェントは、unknown_call_targetとcall_cycle_detectedを表面化するために完全なワークフローセットをサーバーにロードするか、testing-workflowsでカバーされているテストインターフェースを使用する必要があります。ファイル単位の検証実行が成功しても、サーバーがロード可能であるための必要条件ではありますが、十分条件ではありません。
このセクションがそのテストインターフェースです。ローダーが出力するそのままのf-stringの形状は次のとおりです:
Workflow '<parent_name>' step '<step_id>' calls unknown workflow '<target>' (code: unknown_call_target)
call cycle detected: <wf_a> -> <wf_b> -> ... -> <wf_a> (code: call_cycle_detected)
dry-runがそれらを表面化する方法。 dry-runのブートストラップはcreate_app(workflow_dir=<target's parent directory>)を呼び出します。create_app()はそのディレクトリ内のすべての*.yamlをロードし、その後MCPアプリが構築される前にクロスワークフロー呼び出し解決パスを実行します。いずれかのcall:ターゲットがロードされたワークフローに解決されない場合、または呼び出しグラフにサイクルが含まれている場合、ロードは上記のエラーコードで失敗し、REPLが開始される前にdry-runはブートストラップ時に終了コード1で終了します。失敗は、§3に示されたフレーミング段落(「dry-runは親ディレクトリ内のすべての*.yamlファイルをロードします…」)でラップされ、作成者はどのファイルが関係しているかを確認できます。
実用的な意味。 ファイル単位のpython -m megalos_server.validate <file>実行は、unknown_call_targetやcall_cycle_detectedを表面化できません。これらのエラーをキャッチする唯一の方法は、ワークフローセット全体をロードすることです。testing-workflowsスキルは、これを実行するcreate_app()ベースの表面を提供します。dry-runは、ワークフローをテストするための主要な方法として、この表面を使用します。
6. ライブランインターフェース
このセクションは別のタスクで作成されます。
7. よくある間違い
このセクションは別のタスクで作成されます。
8. スキルテストケース
このセクションは別のタスクで作成されます。
9. 付録:スキップ検出のよくある間違い
このセクションは別のタスクで作成されます。