AIが書いたコードに動く証拠を
AIエージェントがサービスを変更したとき、その変更が動くことは、AIエージェント自身が書いたコードやテストとは別のもので示す必要があります。ProbeはGitHub ActionsのようなYAMLで定義したWorkflowを実行し、動いているサービスをHTTP、gRPC、データベース、メール、SSH、シェル越しに操作して、結果を検証します。WorkflowはAIエージェントが書き、Probeが実行前に検査し、許可していない書き込みや接続を防いで実行します。
サインアップのシナリオの例
最初にCreate accountがフォームを送信し、返ってきたidを後続へ渡します。これを待つのがDatabaseとMailboxで、この2つは互いに依存しないため同時に走ります。DatabaseはSQLiteにレコードの状態を問い合わせ、MailboxはIMAPでメールボックスを開いて確認メールを探します。Activateはその両方が終わるのを待ってから、確認リンクをたどります。
1name: Sign-up Flow2 3vars:4 api: https://api.example.com5 mail_user: ada@example.com6 mail_pass: "{{MAIL_PASS}}"7 8jobs:9- name: Create account10 id: signup11 defaults:12 http:13 url: "{{vars.api}}"14 steps:15 - name: Post the form16 id: create17 uses: http18 with:19 post: /signup20 test: res.code == 201 && res.body.status == "pending"21 outputs:22 user_id: res.body.id23 24- name: Database25 id: db26 needs: [signup]27 defaults:28 db:29 dsn: file:./app.db30 steps:31 - name: Row is pending32 uses: db33 with:34 query: SELECT status FROM users WHERE id = ?35 params: ["{{outputs.create.user_id}}"]36 test: res.code == 0 && res.rows[0].status == "pending"37 38- name: Mailbox39 id: mailbox40 needs: [signup]41 defaults:42 imap:43 host: imap.example.com44 username: "{{vars.mail_user}}"45 password: "{{vars.mail_pass}}"46 steps:47 - name: Confirmation arrived48 uses: imap49 with:50 commands:51 - name: select52 mailbox: INBOX53 - name: fetch54 sequence: "1"55 dataitem: ALL56 test: |57 res.code == 0 &&58 res.data.fetch.messages[0].subject == "Confirm your address" &&59 res.data.fetch.messages[0].to == vars.mail_user60 61- name: Activate62 needs: [db, mailbox]63 defaults:64 http:65 url: "{{vars.api}}"66 steps:67 - name: Follow the link68 uses: http69 with:70 get: "/users/{{outputs.create.user_id}}"71 test: res.code == 200 && res.body.status == "active"Sign-up Flow ⏺ Create account (Completed in 0.11s) ⎿ 0. ✓ Post the form ⏺ Database (Completed in 0.02s) ⎿ 0. ✓ Row is pending ⏺ Mailbox (Completed in 0.03s) ⎿ 0. ✓ Confirmation arrived ⏺ Activate (Completed in 0.01s) ⎿ 0. ✓ Follow the link Total workflow time: 0.15s ✓ All jobs succeeded
Workflowの構成要素
WorkflowはJobの集まりで、JobはStepの並びです。Stepは書いた順に1つずつ走ります。Jobは既定で並列に走り、needsを書いたものだけが依存先の完了を待ちます。Jobにrepeatを足せば、同じファイルが一定間隔で走り、成功率を返します。
サポートしているアクション
Stepのusesにアクション名を書くと、Probeはそのアクションを別プロセスとして起動し、gRPC越しにやり取りします。ビルトインのアクションも、自作のアクションとまったく同じ手順で接続されます。特別扱いされているものは1つもないので、必要なアクションが無ければ自分で追加できます。
共通処理は別のファイルに
シナリオの多くはログインから始まり、2つ目以降のシナリオでも同じ手順を書くことになります。その手順をジョブファイルに置けば、どのWorkflowからもuses: embeddedで実行でき、varsを渡してoutputsを受け取れます。呼び出したジョブのStepはレポートでも入れ子で表示されるため、失敗したときにどのStepかがわかります。
1name: Orders2 3vars:4 password: "{{PASSWORD}}"5 6jobs:7- name: Order history8 defaults:9 http:10 url: https://api.example.com11 12 steps:13 - name: Log in14 id: auth15 uses: embedded16 with:17 path: ./login-job.yml18 vars:19 username: ada20 password: "{{vars.password}}"21 test: res.code == 022 outputs:23 token: res.outputs.token24 25 - name: List orders26 uses: http27 with:28 get: /orders29 headers:30 authorization: "Bearer {{outputs.auth.token}}"31 test: res.code == 2001name: Log in2 3defaults:4 http:5 url: https://api.example.com6 headers:7 content-type: application/json8 9steps:10- name: Post credentials11 id: login12 uses: http13 with:14 post: /login15 body:16 username: "{{vars.username}}"17 password: "{{vars.password}}"18 test: res.code == 20019 outputs:20 token: res.body.access_tokenStepで使用できる便利な機能
Stepにはアクションの設定以外に、以下の共通機能があります。
test
式が真ならそのStepは成功です。レスポンスの中身はすべて参照できます。
1test: res.code == 200 && res.body.status == "ok"outputs
値に名前をつけておくと、あとのStepやJobから読めます。
1outputs:2 token: res.body.access_tokeniteration
変数の組ごとに、同じStepをコピーせず繰り返します。
1iteration:2- {name: Alice, role: admin}3- {name: Bob, role: user}retry
失敗したときに、間隔をあけてやり直します。
1retry:2 max_attempts: 33 interval: 5sskipif
条件に当てはまるときは、そのStepを飛ばします。
1skipif: vars.env == "local"wait
先に落ち着くのを待ちたいとき、実行前に待機します。
1wait: 5sAIエージェントのために
AIエージェントがコードとテストを書くなら、その両方を確かめる仕組みが要ります。Probeは動いているバージョンの使い方をAIエージェントに教え、Workflowの誤りを実行前に見つけ、レスポンスの正しさをAIエージェント自身のtestではなく仕様で判定し、書き込みや許可していないホストへの接続を送信前に拒否します。
1name: Profile2 3jobs:4- name: Profile5 defaults:6 http:7 url: https://api.example.com8 steps:9 - name: Log in10 id: login11 uses: http12 with:13 post: /login14 bdoy:15 username: ada16 test: res.code == 20017 outputs:18 token: res.body.token19 20 - name: Read profile21 uses: http22 with:23 get: /me24 headers:25 authorization: "Bearer {{outputs.login.tokn}}"26 tset: res.code == 200agent-workflow.yml:14: error: job 0 "Profile", step 0 "Log in": with: unknown key "bdoy" for the http action; did you mean "body"?agent-workflow.yml:20: warning: job 0 "Profile", step 1 "Read profile": nothing checks this step: it has no testagent-workflow.yml:25: error: job 0 "Profile", step 1 "Read profile": with: outputs.login.tokn is not published: step "login" publishes tokenagent-workflow.yml:26: error: job 0 "Profile", step 1 "Read profile": unknown key "tset"; did you mean "test"? 3 errors, 1 warning
probe checkはWorkflowを実行せずに読みます。Stepやwithのキーの綴りを間違えると、実行時には無視されて別の理由で通ってしまうことがあります。outputsの名前の誤りは、実行してそのStepに来るまでわかりません。ここではどれも何かを送る前に行番号つきのエラーになり、何も確かめていないStepは警告になります。
skill
Workflowの書き方、実行、失敗の読み方を教えるスキルを入れます。AIエージェントはバイナリに組み込まれたリファレンスをprobe guideで読むので、動いているバージョンに合ったYAMLを書きます。
1probe skill install2probe guide httpopenapi
リクエストとレスポンスをOpenAPIのドキュメントと照合します。正しさを決めるのはAIエージェントが書いたtestではなく仕様なので、間違ったtestが間違ったレスポンスを通すことはありません。
1uses: http2with:3 get: /users/14 openapi:5 spec: ./openapi.ymlproto
gRPCでも同じく.protoファイルと照合します。buf.validateのルールとgoogle.api.field_behaviorも確かめます。
1uses: grpc2with:3 method: GetUser4 proto:5 files: [./proto/user/v1/user.proto]coverage
実行したStepが仕様のどのオペレーション、レスポンス、メソッドを確かめたか、まだどれが残っているかを示します。
1probe --report json workflow.yml2probe coverage openapi.yml \3 probe-report.json--read-only
AIエージェントが書いたWorkflowを、書き込ませずに走らせます。読み取り以外の操作と、許可していないホストへの接続は、送る前に拒否します。
1probe --read-only \2 --allow-host api.staging.example.com \3 workflow.ymlexit code
終了コードで何を調べればよいかがわかります。testや仕様の違反か、Workflow自体の誤りか、相手が応答しなかったかを区別します。
1probe workflow.yml2echo $? # 1 test, 2 workflow, 3 actionインストール
Probeは実行時の依存がない、単体のバイナリです。Homebrewなら1つのコマンドで入ります。
$ brew install mozership/tap/probeGoでインストールする場合
go install github.com/mozership/probe/cmd/probe@latest