結論
same-VM の result transport は ADR-DOE-AGENTS-005 により L2 LaunchSession / AwaitResult 経路へ実装済みです。残る負債は transport ではなく、構造化結果を得る標準的なリトライ状態機械を各 caller が組み立てなければならないことです。
2026-07-16 ユーザー指示により解決形状を確定: リトライは doeff-agents が effect / handler として所有します。caller は「エージェントを起動して検証済み結果が欲しい」という意図を一つの高水準効果として発行し、方針値(締切・再試行予算・督促文)を効果の payload で渡すだけにします。リトライのループ・状態遷移・後始末は handler 側の機構であり、caller の Program には現れません。
ADR R5 は維持します。同期的な AgentHandler.handle_agent に MCP server loop を持たせるのではなく、L2 effects を合成する協調的な handler がリトライ状態機械を所有します。
設計比較(2026-07-16 監査で追加)
案A(採用): doeff-agents の高水準効果 + L2 handler がリトライ状態機械を所有
新しい効果(名前は実装時に確定。候補: InvokeAgent(spec, retry_policy) — 現在 AgentEffect の alias である v2 InvokeAgent に実体を与える形)を doeff-agents が定義し、その handler が L2 効果(LaunchSession → AwaitResult → 検証 → FollowUp → ReleaseSession)を協調的に合成する。
移植元は既存実装: packages/doeff-agents/src/doeff_agents/handlers/production.py の _run_agent_task(296行目付近)が必要な意味論をすでに同期 Python で実装しています:
- 壁時計権限は node 締切ただ一つ(L-K4-3、k8s
activeDeadlineSeconds 意味論)
- heartbeat 失効は輸送イベントであり、semantic failure にも retry 消費にもしない
continuable=False(terminal session)へは FollowUp しない — 即座に attempt-exhausted
- 検証失敗時のみ FollowUp 督促を発行し、attempts を加算
finally で必ず ReleaseSession
ただし _run_agent_task は handler メソッドを直接呼ぶ同期関数なので、same-VM MCP server loop を飢餓させる(= R5 が L1 schema 起動を fail-fast にしている理由そのもの)。この意味論を L2 の効果ベース協調ループへ移植する。現在の L2 effectful.hy の AwaitResultEffect は1回分の協調待機(cooperative timer + PRIORITY_IDLE Wait)しか持たないため、その上位に検証・FollowUp・attempts・deadline を所有する層が不足している — それがこの効果の handler。
利点: sim / test は handler 差し替えだけで no-retry・即時 retry・台本 outcome を注入できる(monkeypatch 不要)。リトライ規則の変更が全 caller へ同時に効く。
案B(却下): doeff-core-effects の汎用 Retry(program, policy) 効果
汎用リトライ効果はサブ Program の再実行を意味するが、エージェントの結果契約リトライは再実行ではなく「生きている同一 session への FollowUp 督促」である(再 Launch すると session 状態と作業内容が失われる)。意味論が一致しない。また汎用再実行リトライは非冪等操作への誤用を招く(proboscis-ema の ADR-NAK-006 Idempotency-retry law は、リトライを冪等読み取り型の完全一致 allowlist に限定し、注文・任意コマンドのリトライを禁止している)。冪等読み取り向けの汎用 Retry は将来別 issue にはなり得るが、本件の手段にはしない。
責任分界
caller が決める方針(効果 payload の typed value として渡す):
- 全体の業務期限
- 許容する再試行回数
- schema 不正・入力待ち時の再依頼文
- 最終 outcome を domain error、安全停止、gate へどう写像するか
- domain MCP tools、prompt、result schema
doeff-agents が所有する機構(handler 内部):
- LaunchSession
- report_result sink と MCP server lifecycle
- AwaitResult の協調待機
- schema validation と typed outcome
- heartbeat / awaiting-input / terminal / continuable の状態遷移
- 必要時の FollowUp dispatch
- 成功・失敗・取消時の ReleaseSession
- retry count と deadline の一貫した適用
caller は方針値を渡しますが、timer、CreateExternalPromise、Wait、状態 switch、cleanup loop を再実装しません。
現状の根拠
- ADR-DOE-AGENTS-005 R1 / R2 は same-VM report_result transport を L2 defhandler に実装済みです。
- R5 は同期的な L1 agent() 経路が server loop を飢餓させるため fail-fast し、schema session は L2 を使うと決定しています。この決定は維持します。
- R4 の「solicitation 方針は caller 所有」は、方針の選択を caller に残す規則であり、待機・再試行機構の複製を要求するものではありません。
production.py の _run_agent_task は必要なリトライ意味論を同期 L1 側で実装済み(上記・案Aの移植元)。
effectful.hy の AwaitResultEffect handler は1回分の協調待機のみを所有し、検証・再試行の層が存在しません。
- packages/doeff-agents/src/doeff_agents/programs.py は、非構造化 session について run_agent_to_completion という高水準 Program を既に提供しています。構造化 invocation に同等の深い module がありません。
- doeff AGENTS.md は、schema validation、missing-result handling、retry loop を doeff-agents の所有と明記しています。
- v2 InvokeAgent は現在 AgentEffect の alias であり、same-VM L2 composition ではありません。
目標 API
caller policy (typed) + AgentInvocationSpec
-> 高水準効果を1つ発行(例: InvokeAgent(spec, retry_policy))
-> doeff-agents の L2 handler がリトライ状態機械を所有:
LaunchSession
-> AwaitResult(協調待機)
-> 検証 OK -> ReleaseSession -> validated result
-> 検証 NG:
heartbeat 失効 -> 再 await(attempt 消費なし・deadline のみ確認)
continuable=False -> attempt-exhausted(FollowUp しない)
deadline 超過 -> deadline gate(新規 FollowUp を発行しない)
attempts < budget -> FollowUp 督促 -> 再 await
attempts 尽きた -> attempt-exhausted
-> いずれの終端でも session / sink / MCP server を解放
-> validated result または typed terminal outcome
段階移行のため、狭い変種 AwaitValidatedResult(handle, policy)(起動済み handle に対し検証付き待機のみを効果化する)を先に切り出してもよい。Nakagawa の現行境界関数は handle を受け取る形のため、この変種は移行の中間段階として互換性がある。最終形は全 lifecycle を所有する高水準効果とする。
完了条件
下流
proboscis/proboscis-ema#629 で Nakagawa の独自 mechanism をこの effect / handler へ移行します。業務期限、再依頼文、安全停止への写像は Nakagawa に残します。
結論
same-VM の result transport は ADR-DOE-AGENTS-005 により L2 LaunchSession / AwaitResult 経路へ実装済みです。残る負債は transport ではなく、構造化結果を得る標準的なリトライ状態機械を各 caller が組み立てなければならないことです。
2026-07-16 ユーザー指示により解決形状を確定: リトライは doeff-agents が effect / handler として所有します。caller は「エージェントを起動して検証済み結果が欲しい」という意図を一つの高水準効果として発行し、方針値(締切・再試行予算・督促文)を効果の payload で渡すだけにします。リトライのループ・状態遷移・後始末は handler 側の機構であり、caller の Program には現れません。
ADR R5 は維持します。同期的な
AgentHandler.handle_agentに MCP server loop を持たせるのではなく、L2 effects を合成する協調的な handler がリトライ状態機械を所有します。設計比較(2026-07-16 監査で追加)
案A(採用): doeff-agents の高水準効果 + L2 handler がリトライ状態機械を所有
新しい効果(名前は実装時に確定。候補:
InvokeAgent(spec, retry_policy)— 現在AgentEffectの alias である v2InvokeAgentに実体を与える形)を doeff-agents が定義し、その handler が L2 効果(LaunchSession → AwaitResult → 検証 → FollowUp → ReleaseSession)を協調的に合成する。移植元は既存実装:
packages/doeff-agents/src/doeff_agents/handlers/production.pyの_run_agent_task(296行目付近)が必要な意味論をすでに同期 Python で実装しています:activeDeadlineSeconds意味論)continuable=False(terminal session)へは FollowUp しない — 即座に attempt-exhaustedfinallyで必ず ReleaseSessionただし
_run_agent_taskは handler メソッドを直接呼ぶ同期関数なので、same-VM MCP server loop を飢餓させる(= R5 が L1 schema 起動を fail-fast にしている理由そのもの)。この意味論を L2 の効果ベース協調ループへ移植する。現在の L2effectful.hyのAwaitResultEffectは1回分の協調待機(cooperative timer + PRIORITY_IDLE Wait)しか持たないため、その上位に検証・FollowUp・attempts・deadline を所有する層が不足している — それがこの効果の handler。利点: sim / test は handler 差し替えだけで no-retry・即時 retry・台本 outcome を注入できる(monkeypatch 不要)。リトライ規則の変更が全 caller へ同時に効く。
案B(却下): doeff-core-effects の汎用
Retry(program, policy)効果汎用リトライ効果はサブ Program の再実行を意味するが、エージェントの結果契約リトライは再実行ではなく「生きている同一 session への FollowUp 督促」である(再 Launch すると session 状態と作業内容が失われる)。意味論が一致しない。また汎用再実行リトライは非冪等操作への誤用を招く(proboscis-ema の ADR-NAK-006 Idempotency-retry law は、リトライを冪等読み取り型の完全一致 allowlist に限定し、注文・任意コマンドのリトライを禁止している)。冪等読み取り向けの汎用 Retry は将来別 issue にはなり得るが、本件の手段にはしない。
責任分界
caller が決める方針(効果 payload の typed value として渡す):
doeff-agents が所有する機構(handler 内部):
caller は方針値を渡しますが、timer、CreateExternalPromise、Wait、状態 switch、cleanup loop を再実装しません。
現状の根拠
production.pyの_run_agent_taskは必要なリトライ意味論を同期 L1 側で実装済み(上記・案Aの移植元)。effectful.hyのAwaitResultEffecthandler は1回分の協調待機のみを所有し、検証・再試行の層が存在しません。目標 API
段階移行のため、狭い変種
AwaitValidatedResult(handle, policy)(起動済み handle に対し検証付き待機のみを効果化する)を先に切り出してもよい。Nakagawa の現行境界関数は handle を受け取る形のため、この変種は移行の中間段階として互換性がある。最終形は全 lifecycle を所有する高水準効果とする。完了条件
_run_agent_taskの意味論(deadline 単独権限・heartbeat 非消費・terminal 非督促・finally 解放)を L2 協調 handler に移植し、L1 実装と意味の同値性をテストで固定する。InvokeAgentを本効果の実体とするか、新名称を追加して alias を維持するかを実装 PR で決定し、互換方針を記録する。下流
proboscis/proboscis-ema#629 で Nakagawa の独自 mechanism をこの effect / handler へ移行します。業務期限、再依頼文、安全停止への写像は Nakagawa に残します。