1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ralph Loop で AWS Step Functions を「自律的に直して最後まで通す」

1
Last updated at Posted at 2026-06-24

突然ですが皆さん、Step Functionsの疎通テストってどうやってます?
私は「直す → デプロイ → 落ちる → ログ睨む → また直す」のトライアンドエラーです。
あの作業わりと苦行なんですよね。なので最近やたら耳にする ループエンジニアリング に丸投げしてみました。


この記事でやったこと

  • Ralph Loop(自己修正ループ)1 に、わざとバグを仕込んだStep Functionsを渡す
  • AIが自律的に・段階的にバグを直して「ローカルGREEN → AWSデプロイ → 実行SUCCEEDED」まで通す

そして、ただ闇雲にループを回しても収束しません。AIって放っておくと「さっきと同じ直し方」を平気でもう一回やったりするんですよ...(マジで)

自律的にちゃんと収束させるためのカギは、この3つ。

  1. ローカルGREENゲート … ローカルが通るまでAWSに進ませない
  2. ERROR_DETAIL.md による記憶 … 失敗・試行履歴をイテレーション間で引き継ぐ
  3. 根本原因を直す規律 … 症状の場当たり修正を禁止する

この「ゲート・記憶・規律」の3点セットが効くんですが、それは後ほど。


1. そもそもRalph Loopって何?

簡単に言うと 「エージェントが終わろうとすると、同じプロンプトをもう一回ぶち込む」 ことで自己修正を延々と回し続ける手法です(Claude Codeのプラグインです)。

仕組みはこんな感じ。

  1. /ralph-loop "<プロンプト>" --max-iterations N --completion-promise DONE で起動
  2. エージェントがターンを終えようとすると Stopフック2が発火して、同じプロンプトを再注入
  3. エージェントが <promise>DONE</promise> を出力するか、max-iterations に達したら終了

ポイントは 「完了条件が本当に真になるまで、嘘ついて抜けちゃダメ」 というルール。ここ、地味に大事なんですよね。

AIって「もう直りました!(直ってない)」みたいな雰囲気で帰ろうとすることがあるので、ちゃんと首根っこを掴んでおく必要があります。今回の完了条件は「AWS実行がSUCCEEDEDになったら <promise>DONE</promise>」。これ以外では絶対に帰してもらえません。スパルタです。

HLgSz80aIAAfqk8.jpeg

こちらの記事がとてもわかりやすくまとめられていました!!


2. 自律ループの全体像

各イテレーションで、ループはこのフローをぐるぐる回します。ローカルが通るまでAWSには進ませないのがミソ。

AWSにデプロイするたびに時間もお金もちょっとずつ溶けます。
安いローカルで失敗を吸収してもらおう、という作戦です。

④の「ローカルGREENゲート」が今回の主役です。ローカルがSUCCEEDEDにならない限り、AWSには一歩も進ませません。RED/FAILEDはぜんぶ ERROR_DETAIL.md に記録されて、Stopフックの再注入で次のイテレーションに引き継がれます。

直してもらう対象のステートマシンは、Lambdaを2個直列に呼ぶだけの超ミニマム構成です。

構成はAWS SAM(Lambda×2 + StateMachineを template.yaml で定義)。ローカル検証はStep Functions Local + sam local、本番は sam deploy です。


3. わざと仕込んだ2つのバグ

ループが「段階的に」直していく様子が見たいので、性質の違うバグを各Lambdaに1個ずつ埋めました。# BUG みたいな親切なネタバレコメントはなしです。

ファイル バグ(修正前) 正解 どう失敗するか
バグA functions/first/app.py result = value + "1" value + 1 int + strTypeError
バグB functions/second/app.py return {"status": "ng", ...} "status": "ok" Choiceが "ok" を要求 → FailState

バグAは「型を間違えて落ちる」古典中の古典、バグBは「処理は通るけど中身が間違っててワークフローが失敗判定になる」やつ。AIが1個ずつ順番に潰していけるかを見たかったわけです。


4. 自律収束の心臓部:ERROR_DETAIL.md とドライバープロンプト

さてここが今回いちばん伝えたいところです。

AIはただループを回すだけだと収束しません。
同じ修正を延々繰り返したり、症状だけを場当たり的にいじって根本原因を放置したり…。なので記憶と規律を仕込んでやる必要があります。

ERROR_DETAIL.md(イテレーション間の引き継ぎ役)

AIには記憶がないので、外付けのメモ帳を持たせます。3セクション構成で、毎イテレーションの冒頭に必ず読ませます。

  • 現在の状態(毎回上書き): iteration / phase / status / failed_state / error_type / root_cause / attempted_fix / next_hypothesis
  • 禁止リスト: 「失敗ステート+エラー型+試した修正」が再登場したら追記して、二度と同じ手を打たせない(=同じ過ちを繰り返させないための反省ノート)
  • 試行履歴(追記のみ・絶対に消さない): 各イテレーションの記録

「あ、それさっきやってダメだったやつだ」をちゃんとファイルに刻んでおくイメージです。

ドライバープロンプトの主なルール

  • 同じ修正を2回繰り返さない修正前に必ず仮説を1つ書くnext_hypothesisは前回と変える
  • ローカルがREDのままAWSへ進まない
  • 1イテレーションでは、観測された失敗が示す箇所だけを最小修正
  • 症状じゃなくて根本原因を直す/エラーをマスクしない

要するに「焦るな・嘘つくな・前回と同じことするな」を、ひたすらプロンプトで言い聞かせている感じです。


5. 実際のループ実行ログ(3イテレーションで完走)

実際に走らせたログがこちらです。

ERROR_DETAIL.md(全文)

ERROR_DETAIL

運用ルール: 毎イテレーション冒頭でこのファイルを読む。試行履歴に同一の「失敗ステート名+エラー型+試した修正」が既出なら、その修正は禁止リスト扱いとし別の仮説を立てる。next_hypothesis は必ず前回と変える。

現在の状態(毎イテレーション上書き)

  • iteration: 3
  • phase: AWS
  • status: GREEN ✅
  • failed_state: -(なし)
  • error_type: -(なし)
  • root_cause: -(bug A / bug B とも解消済み)
  • attempted_fix: -(追加修正なし)
  • next_hypothesis: -(完了。AWS 実行が SUCCEEDED に到達)

禁止リスト(同じ修正の再試行を防ぐ)

(なし。各バグは1回の修正で解消したため再試行なし)

試行履歴(追記のみ・絶対に消さない)

iteration-1 (phase: LOCAL, status: RED)

  • failed_state: FirstTask
  • error_type: TypeError ("unsupported operand type(s) for +: 'int' and 'str'")
  • root_cause: functions/first/app.py:4 result = value + "1"(int に str を加算)
  • attempted_fix: value + "1"value + 1
  • result: 修正適用済み。iteration-2 の run-local で FirstTask が {"value": 2, "step1": "done"} を正常出力することを確認 → 解消

iteration-2 (phase: LOCAL, status: RED)

  • failed_state: CheckResult → FailState
  • error_type: UnexpectedStatus ("$.status was not 'ok'")
  • root_cause: functions/second/app.py:4 return {"status": "ng", ...}(Choice が "ok" を要求)
  • attempted_fix: "status": "ng""status": "ok"
  • result: 修正適用済み。iteration-3 の run-local で解消を確認 → 解消

iteration-3 (phase: LOCAL→AWS, status: GREEN)

  • ローカル実行: SUCCEEDED(LOCAL GREEN)
  • AWS デプロイ: sam deploy でスタック aituber-char-sfn 作成成功(FirstFunction / SecondFunction / StateMachine + IAM Roles)
  • AWS 実行: executionArn ...StateMachine-qPLIvdGm96Wl:032a8f6d-... → status SUCCEEDED(AWS GREEN)
  • result: 完了。<promise>DONE</promise> を出力してループ終了

【抜粋版】

Iter フェーズ 観測した失敗 根本原因 修正 結果
1 LOCAL FirstTask で TypeError first/app.py value + "1" value + 1 バグA解消
2 LOCAL CheckResultFailState$.status was not 'ok' second/app.py "status": "ng" "ok" バグB解消
3 LOCAL→AWS なし ローカルGREEN → AWSデプロイ → 実行SUCCEEDED

今回の「動き」を図にすると、失敗箇所が1段ずつ前進しながら収束していくのが分かって、ちょっと気持ちいいです。

イテレーション1(バグA発見・修正)

▶ iteration-1:ローカル実行履歴(全文 / タイムスタンプ・inputDetails 等は省略)
{
  "events": [
    { "id": 1, "type": "ExecutionStarted",
      "executionStartedEventDetails": { "input": "{\"value\":1}", "roleArn": "arn:aws:iam::012345678901:role/DummyRole" } },
    { "id": 2, "type": "PassStateEntered", "stateEnteredEventDetails": { "name": "PrepareInput", "input": "{\"value\":1}" } },
    { "id": 3, "type": "PassStateExited",  "stateExitedEventDetails":  { "name": "PrepareInput", "output": "{\"value\":1}" } },
    { "id": 4, "type": "TaskStateEntered", "stateEnteredEventDetails": { "name": "FirstTask", "input": "{\"value\":1}" } },
    { "id": 5, "type": "LambdaFunctionScheduled",
      "lambdaFunctionScheduledEventDetails": { "resource": "arn:aws:lambda:us-east-1:123456789012:function:FirstFunction", "input": "{\"value\":1}" } },
    { "id": 6, "type": "LambdaFunctionStarted" },
    { "id": 7, "type": "LambdaFunctionSucceeded",
      "lambdaFunctionSucceededEventDetails": {
        "output": "{\"errorMessage\": \"unsupported operand type(s) for +: 'int' and 'str'\", \"errorType\": \"TypeError\", \"requestId\": \"fc081a01-...\", \"stackTrace\": [\"  File \\\"/var/task/app.py\\\", line 4, in handler\\n    result = value + \\\"1\\\"\\n\"]}"
      } },
    { "id": 8, "type": "TaskStateExited",
      "stateExitedEventDetails": { "name": "FirstTask", "output": "<id:7 と同じ TypeError JSON>" } },
    { "id": 9, "type": "TaskStateEntered",
      "stateEnteredEventDetails": { "name": "SecondTask", "input": "<id:7 と同じ TypeError JSON>" } },
    { "id": 10, "type": "LambdaFunctionScheduled",
      "lambdaFunctionScheduledEventDetails": { "resource": "arn:aws:lambda:us-east-1:123456789012:function:SecondFunction", "input": "<id:7 と同じ TypeError JSON>" } },
    { "id": 11, "type": "LambdaFunctionStarted" }
    //  SecondFunction  bug A の異常出力("value" キー無し)を受けて停滞し、ポーリングがタイムアウト
  ]
}

イテレーション2(バグB発見・修正)

▶ iteration-2:ローカル実行履歴(全文 / タイムスタンプ・inputDetails 等は省略)
{
  "events": [
    { "id": 1, "type": "ExecutionStarted",
      "executionStartedEventDetails": { "input": "{\"value\":1}", "roleArn": "arn:aws:iam::012345678901:role/DummyRole" } },
    { "id": 2, "type": "PassStateEntered", "stateEnteredEventDetails": { "name": "PrepareInput", "input": "{\"value\":1}" } },
    { "id": 3, "type": "PassStateExited",  "stateExitedEventDetails":  { "name": "PrepareInput", "output": "{\"value\":1}" } },
    { "id": 4, "type": "TaskStateEntered", "stateEnteredEventDetails": { "name": "FirstTask", "input": "{\"value\":1}" } },
    { "id": 5, "type": "LambdaFunctionScheduled",
      "lambdaFunctionScheduledEventDetails": { "resource": "arn:aws:lambda:us-east-1:123456789012:function:FirstFunction", "input": "{\"value\":1}" } },
    { "id": 6, "type": "LambdaFunctionStarted" },
    { "id": 7, "type": "LambdaFunctionSucceeded",
      "lambdaFunctionSucceededEventDetails": { "output": "{\"value\": 2, \"step1\": \"done\"}" } },
    { "id": 8, "type": "TaskStateExited",
      "stateExitedEventDetails": { "name": "FirstTask", "output": "{\"value\": 2, \"step1\": \"done\"}" } },
    { "id": 9, "type": "TaskStateEntered",
      "stateEnteredEventDetails": { "name": "SecondTask", "input": "{\"value\": 2, \"step1\": \"done\"}" } },
    { "id": 10, "type": "LambdaFunctionScheduled",
      "lambdaFunctionScheduledEventDetails": { "resource": "arn:aws:lambda:us-east-1:123456789012:function:SecondFunction", "input": "{\"value\": 2, \"step1\": \"done\"}" } },
    { "id": 11, "type": "LambdaFunctionStarted" },
    { "id": 12, "type": "LambdaFunctionSucceeded",
      "lambdaFunctionSucceededEventDetails": { "output": "{\"status\": \"ng\", \"value\": 2, \"step2\": \"done\"}" } },
    { "id": 13, "type": "TaskStateExited",
      "stateExitedEventDetails": { "name": "SecondTask", "output": "{\"status\": \"ng\", \"value\": 2, \"step2\": \"done\"}" } },
    { "id": 14, "type": "ChoiceStateEntered",
      "stateEnteredEventDetails": { "name": "CheckResult", "input": "{\"status\": \"ng\", \"value\": 2, \"step2\": \"done\"}" } },
    { "id": 15, "type": "ChoiceStateExited",
      "stateExitedEventDetails": { "name": "CheckResult", "output": "{\"status\": \"ng\", \"value\": 2, \"step2\": \"done\"}" } },
    { "id": 16, "type": "FailStateEntered",
      "stateEnteredEventDetails": { "name": "FailState", "input": "{\"status\": \"ng\", \"value\": 2, \"step2\": \"done\"}" } },
    { "id": 17, "type": "ExecutionFailed",
      "executionFailedEventDetails": { "error": "UnexpectedStatus", "cause": "$.status was not 'ok'" } }
  ]
}

イテレーション3(ローカルGREEN → AWS)

▶ iteration-3:ローカル GREEN 確認
=== [8/8] 実行完了を待機中 ===
    [1] status: RUNNING
    [2] status: SUCCEEDED

==========================================
  LOCAL GREEN
==========================================
▶ iteration-3:sam deploy ログ(CloudFormation、AWSアカウントIDはマスク)
=== [2/4] sam deploy ===
	Deploying with following values
	===============================
	Stack name                   : aituber-char-sfn
	Region                       : ap-northeast-1
	Capabilities                 : ["CAPABILITY_IAM"]

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------
Operation        LogicalResourceId      ResourceType                        Replacement
-------------------------------------------------------------------------------------------------
+ Add            FirstFunctionRole      AWS::IAM::Role                       N/A
+ Add            FirstFunction          AWS::Lambda::Function                N/A
+ Add            SecondFunctionRole     AWS::IAM::Role                       N/A
+ Add            SecondFunction         AWS::Lambda::Function                N/A
+ Add            StateMachineRole       AWS::IAM::Role                       N/A
+ Add            StateMachine           AWS::StepFunctions::StateMachine     N/A
-------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Key    StateMachineArn     Value  arn:aws:states:ap-northeast-1:xxxxxxxxxxxx:stateMachine:StateMachine-qPLIvdGm96Wl
-------------------------------------------------------------------------------------------------
Successfully created/updated stack - aituber-char-sfn in ap-northeast-1
    OK: sam deploy PASSED
▶ iteration-3:AWS ワークフロー実行(SUCCEEDED)
=== [4/4] AWS ワークフロー実行 ===
    executionArn: arn:aws:states:ap-northeast-1:xxxxxxxxxxxx:execution:StateMachine-qPLIvdGm96Wl:032a8f6d-...
    実行完了を待機中...
    [1] status: RUNNING
    [2] status: SUCCEEDED

==========================================
  AWS GREEN
==========================================

image.png
AWS上でも成功:v:


6. まとめ

わざとバグを2個仕込んだStep FunctionsをRalph Loopに渡したところ、3イテレーションで段階的にバグを潰して、AWS実行SUCCEEDEDまで自律的にたどり着いてくれました。自分のミスの尻拭いをAIに丸投げする時代、来てますね。

ここで一番伝えたいのは「AIすごい」(小並感)ではなく、自己修正ループを"ただ回す"だけではなく、設計に組み込んだ3つのルールの重要性です。

「ゲート・記憶・規律」の3点セット

  1. ローカルGREENゲート … 失敗は安いローカルで吸収。AWSにはGREENになってからしか進ませない(コスト&時間の節約)
  2. ERROR_DETAIL.md … 状態と試行履歴を引き継いで、同じ失敗の繰り返しを防ぐ(AIの外付け反省ノート)
  3. 根本原因を直す規律 … 症状の場当たり修正を禁止し、上流のコードを直させる

AIに自走させたいなら、能力に期待するより 環境(ガードレール)を整えるほうが効く ということですね。

少しでも参考になれば嬉しいです。
いいねを押してもらえると泣いて喜びます!最後まで読んでいただきありがとうございました🙌

  1. 「エージェントが終わろうとすると同じプロンプトをもう一度ぶち込む」ことで自己修正を回し続けるClaude Codeのプラグイン。

  2. エージェントがターンを終了しようとしたタイミングで発火するフック。ここで同じプロンプトを再注入することでループが成立する。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?