本文へスキップ
mcpd
デーモンなし
ソース

実行

実行とは、1 行のレコード、カーソル、試行のジャーナル、期限の集まり、中断の集まりです。このページは、実行に対して何ができ、実行が何を返してくるかを説明します。

実行の開始

実行は、定義の id と入力から始まります。定義にスキーマがあればデーモンは入力を それに照らして検証し、保存されている最も高いバージョンを固定し、実行とその トレースを作り、保存されたとおりの実行を返します。そこから先、実行は自分で 進みます。デーモンはそのステップをワーカーに提示し、スリープと待機をたどり、 起きたことをジャーナルに書きます。

開始、応答、追跡

// Start a run from anywhere that holds an execute key, answer the wait from a
// webhook, then follow the journal to the end and read the run.
const run = await mcpd.runs.start({
  definitionId: 'support_ticket',
  input: { ticket: 'Q-42' },
})

await mcpd.events.publish({
  event: 'approved',
  key: 'Q-42',
  payload: { by: 'ops' },
  eventId: 'hook-1', // a repeat of this id is a duplicate, not a second event
})

for await (const event of mcpd.runs.watch(run.runId)) {
  console.log(event.seq, event.kind)
}
const detail = await mcpd.runs.get(run.runId)
console.log(detail.run.status, detail.run.output)

リトライされる可能性のある呼び出し元からは idempotency_key を 送ってください。これは実行が保持されている間プロジェクト内で一意で、同じ 呼び出しのリトライは 2 つ目の実行を始めるのではなく同じ実行を返します。 webhook が二度発火しても、ジョブランナーが再起動しても、それで余計な実行が 生まれることはありません。

The cursor

The daemon holds the run between steps: where it is, what each step answered, what it is waiting for and until when. A worker holds one step at a time, and only while it runs. During a wait no process is alive at all.

classify task your process draft agent your process approval wait_event the daemon alone send task · at_most_once your process retry

ジャーナル

実行へのすべての変更は、ゼロから始まり欠番のない連番が付いたイベントです。 実行が始まった、カーソルが動いた、ステップがリースされた、開始した、終了した、 失敗した、実行が待機して再開した、実行が終わった、といったものです。それぞれが 種類に応じた形のペイロードを持ちます。ジャーナルは実行の履歴であり、 インターフェースが描いているものです。

after からページ単位で読むか、イベントのエンドポイントを Accept: text/event-stream で開いたままにして、コミットされた順に 各イベントを受け取ってください。止まってから再開したクライアントは、最後に見た 連番を渡せば何も取りこぼしません。どの SDK の watch もこれを 代わりに行い、実行が終わると終了します。wait はジャーナルを最後まで たどって出力を返し、成功しなかった場合は実行を含む型付きの失敗を返します。

イベントと待機

wait_event ステップは、その名前と展開されたキーを持つイベントが 届くまで実行を止めます。イベントは 1 つの入口から入ります。

待機に応える

curl -X POST http://localhost:4317/v1/api/events \ -H 'content-type: application/json' \ -d '{"project":"support","event":"approved","key":"Q-42","payload":{"by":"ops"},"event_id":"hook-1"}'

応答は 3 つの結果のいずれかです。delivered は、待機していた実行が 同じトランザクションでイベントを取り込んだという意味で、受領を読む時点で実行は すでに進んでいます。parked は、待機していた実行がなく、あとで開く 待機のためにイベントが保持されている、という意味です。保持されたイベントを 受け取れるのは、保留イベントを受け付けると宣言した待機だけです。 duplicate は、その event_id を以前に見たので何も 書かれなかった、という意味です。二度発火しうるものからは、自分の id を送って ください。

期限に達した待機は、その on_timeout の指定どおりに動きます。 失敗して on_error に判断を渡すか、出力なしで続行するかの どちらかです。ある実行に対して、その実行自身の開いている待機を通じて直接 送る場合は POST /v1/api/runs/{run}/signal を使います。 実行そのものが宛先なので、キーなしで同じ本文を受け取ります。

キャンセル

POST /v1/api/runs/{run}/cancel は、実行とその下のすべての子 実行を終わらせます。実行中のステップが通信の途中で中断されることはありません。 ワーカーは次のハートビートで実行が終わったことを知り、そのコンテキストが本体に 合図を送り、本体がそのあとに送るものは run_terminal として拒否 されます。すでに終わった実行をキャンセルしても何も変わらず、そのままの実行が 返ります。

タイマーと上限

スリープ、待機の期限、リトライのバックオフ、リースの失効は、すべてデーモンが 持つタイマーです。これらはデーモンの時計で毎秒発火するので、100 ミリ秒の retry_after_ms は「ちょうどそれだけ」ではなく「少なくともそれだけ」 として守られます。デーモンを再起動してもタイマーは失われません。それらは行で あり、実行はその行から再開します。どの期間も 3650 日より長くはできず、 ワーカーから届いたそれより長い retry_after_ms は 400 で拒否 されます。2262 年より先の sleep_until は、期限が来ないものと して保持されます。

定義の max_steps、run_timeout_ms、 max_cost_usd は、上限に達したところで、その上限をエラーとして 名指しながら実行を終わらせます。コストは、各ステップが報告した使用量を値付け した合計で、スパンに値付けするのと同じ価格表を使います。したがってトークンを 報告するエージェントのステップは、完了した時点で実行の予算に計上されます。

トレースの中の実行

すべての実行はトレースでもあります。デーモンは実行のルートスパンと、試行ごとに 1 つのスパンを、ステップの入力と出力とともに開きます。ステップコンテキストを 通じて自分のスパンを記録する本体は、それらをその下に置きます。したがって エージェントのステップの中のモデル呼び出しは、そのステップの下、その実行の下に 位置し、任意のトレースに対して働く検索、コスト計算、スコアラーが、実行の トレースに対してもそのまま働きます。それらのスパンは SDK のスパンと同じ 取り込み経路を通って書かれるので、同じようにサンプリングされ、秘匿化され、 値付けされ、転送されます。トークンは、それを使ったモデルのスパンに 載ります。ステップのスパンは値付け済みの合計を属性に持つので、トレース全体の 合計は各トークンを一度だけ数えます。実行は自分の trace_id を持ち、 インターフェースが両者をつなぎます。

デーモンはそれらのスパンを、元になった行がコミットされたあとに、自分の時計で アウトボックス経由で書きます。ステップの完了直後に読んだトレースには、その ステップのスパンがまだ載っていないことがあります。実行自身の詳細には常に 載っています。

実行の読み出し

GET /v1/api/runs は、プロジェクトの実行を更新の新しい順に並べ、 状態や定義で絞り込みます。GET /v1/api/runs/{run} は、その実行 と、それについて永続化されているすべてです。リースの保有者、時刻、エラー、 使用量を伴う各試行、まだ発火していないタイマー、まだ開いている待機です。 インターフェースの ワークフロー画面 は、このエンドポイントを描いたもので、その横にジャーナルが実時間で並びます。

どちらも実行が受け取ったものを返すので、どちらも監査ログに list_runs と get_run として記録されます。 ジャーナルの読み取りは list_run_events として、ストリームも 含めてリクエストごとに 1 回、原因の追跡は get_step_cause と して、キャンセルは cancel_run として、同じく記録されます。 拒否された読み取りは、その実行を所有するプロジェクトに対して記録されます。 ワーカーの呼び出しは記録されません。 ワーカーを参照してください。

ストレージ

実行は、トレースのストアの隣にある自分の SQLite ファイルに入ります。 --runs-data が別を指さない限り <data>.runs.<ext> です。--runs-data off にすると実行の面全体が止まり、その場合はすべての実行エンドポイントが 503 を 返します。トレースのストアと一緒にバックアップしてください。 mcpd doctor はこれを独立した項目として評価します。

実行ストアは、ペイロードを送られたとおりに保持します。実行の入力、状態、 出力、相関、各ステップの入力、出力、エラー、すべてのイベントのペイロード、 そしてトレースのストアへの書き込みを待っているスパンのペイロードは、秘匿化 されずに保存され、実行の相関にある user_id はハッシュ化されずに 保存されます。これは意図的です。それらはワークフローの作業状態であり、あとの ステップがそれを読み返すので、秘匿化処理が書き換えた値は実行の動きを変えて しまいます。デーモンがトレースのストアに書くスパンは、SDK が送るスパンと同じ 秘匿化と識別子のハッシュ化を通るので、トレースのストアにあるのは秘匿化された 写しです。実行のファイルは、ステップに渡したすべてを持つものとして扱い、 それに見合うように保護し、バックアップしてください。

保持期間は実行ストアにも及びます。トレースのストアを削る 1 時間ごとの処理は、 プロジェクトごとの同じ期間で、ルートが保持期間の境界より前に終わったすべての 実行も、その子の実行と、それらについて保存されたすべてとともに削除します。 試行、イベント、期限、待機、キューにあるスパン、そしてそれらの実行が取り込んだ イベントです。終わっていない実行は、どれだけ古くても削除されません。定義は 残ります。

消去も実行ストアに及びます。DELETE /v1/api/subjects/{user_id} は、プロジェクト内の終わった実行のうち、相関がその user_id を、 実行を開始したときの形のまま持つものをすべて、子の実行の木全体と、それらに ついて保存されたすべてとともに削除し、その件数を runs_erased として返します。その個人の実行のうち終わっていないものは、まるごと残され、 runs_in_flight に数えられ、応答の note で名指し されます。それをキャンセルするか終わるのを待ってから、消去をもう一度送って ください。まだ待機に達していない実行のために保持されたイベントは、個人では なく相関キーに結びついているので、消去は届きません。それは期限が切れたとき、 つまり 24 時間後に削除されます。応答の残りについては マスキングと保持を参照 してください。

次に