AI Agent WorkstationのTask GitとRunner契約を段階的に固める

4レイヤー構成の次に実行時契約を固める

前回の記事では、Todo Schedulerを単純なタスク管理アプリから、複数のAI Agent Workstationを管理する実行基盤へ移行する過程をまとめました。

主なテーマは、コードの責務を整理することでした。

  • BackendをDomain、Application、Infrastructure、Presentationへ分割する
  • Frontendも同じ依存方向を参考に整理する
  • main.goapp.jsをComposition Rootへ近づける
  • Infra Runnerを複数のmoduleへ分割する
  • Architecture Testで依存方向を固定する
  • AI Agent Workstationの権限と通信先を制限する

その後の開発では、単純なレイヤー分割よりも、

SchedulerとRunnerが何を約束し、どの状態を誰が所有するか

という実行時の契約が重要になってきました。

今回の変更は、この契約をTask Git、required outputs、Background Execution、terminal reporting、E2Eの各境界で明示するものが中心です。


前回からの主な変化

大きく整理すると次のようになります。

項目前回今回
Runner結果Codexの結果、validation、Git情報を報告Task Planが要求した任意evidenceも構造化して報告
required outputsScheduler側に検証はあるがRunnerから提出する正式経路がないjob-local CLI / Unix socketを追加
HTTP 409statusを中心に扱う部分が残るreason単位でterminal outcomeを分類
Background jobbackgroundという大きな分類phaseとsource modeをversioned contract化
Task Gitexecution中心のworktree管理Planning、Execution、Aggregationまで契約を明確化
terminal retryleaseとterminal結果の境界が曖昧terminal POSTの冪等性とreconciliationを整理
E2EApp内mockや個別contract testが中心実Runnerとclean clusterを使うTask Git suiteを追加
テスト運用suite単位の成功確認test lane、performance、verification manifest、review sliceを追加

差分自体も大きくなっています。

前回のソースから今回のソースでは、274ファイルが変更され、約26,000行が追加されています。

内訳はおおよそ、

App
  210 files
  +15,687
  -2,929

Infra
   64 files
  +10,324
     -948

です。

そのため、今回は単一機能の修正というより、Runner / Scheduler間の契約とTask Git周辺をまとめて固める変更になっています。


required_outputsを正式なRunner契約にする

今回の変更で特に大きいのが、Task Planのrequired_outputsです。

Task Planでは、例えば次のようなoutputを要求できます。

api_title_check
browser_title_check
changed_files
implementation_summary

以前のRunnerは、Codexの実行結果として、

  • final response
  • validation結果
  • Git commit
  • execution metadata
  • runner report

などはSchedulerへ送っていました。

しかし、Plannerが任意に定義したrequired_outputsを、AIが正式なevidenceとして提出する経路がありませんでした。

そのため、

Codex成功

validation成功

commit成功

Runnerがsuccessを送信

Scheduler
required_outputs_missing

HTTP 409

という状態が発生していました。

AIは作業を完了しているのに、RunnerとScheduler間の契約を満たせず、Taskが完了できない状態です。


final responseを解析しない

今回の実装では、AIの最終回答からrequired outputを推測する方式にはしていません。

代わりに、

Codex

todo-scheduler-report

job-local Unix socket

Runnerのin-memory evidence store

completion preflight

Scheduler

という経路を追加しています。

AIから利用するinterfaceはCLIです。

概念的には次のようになります。

todo-scheduler-report set api_title_check "デモタイトルB"

todo-scheduler-report set-json implementation_summary \
  '{"status":"passed","detail":"title.jsを更新"}'

CLIはScheduler URLやRunner credential、lease tokenを知りません。

AIが直接Scheduler APIを操作するのではなく、trusted Runner processが代理でcompletionを送ります。

これはWorkstation内のAIをsecurity boundaryとして信用しないという、これまでの設計方針とも一致しています。


required_outputsをclaim時点で渡す

Runnerがrequired outputを提出するには、そもそも何が要求されているのかをRunner自身が知る必要があります。

そこでclaim inputへversion付きcontractを追加しています。

{
  "completion_contract": {
    "schema_version": "runner.required-outputs.v1",
    "required_outputs": [
      {
        "key": "api_title_check",
        "kind": "evidence"
      },
      {
        "key": "commit_sha",
        "kind": "commit_sha"
      }
    ]
  }
}

重要なのは、Runnerがoutput名を見て独自に意味を推測しないことです。

Schedulerが、

evidence
commit_sha
test_report
artifact

などへ分類し、その結果をRunnerへ渡します。

これにより、Planner、Scheduler、Runnerでrequired outputの意味が分裂しにくくなります。


completion前にpreflightする

以前は、Runnerがsuccessを送ってからSchedulerに拒否されていました。

今回はSchedulerへ送る前にRunner自身が必要なevidenceを確認します。

Codex完了

required outputs preflight

不足あり
  ├─ yes → normal failure
  └─ no

     validation / Git result

     complete

required outputが不足している場合は、/completeを送信しません。

required_output_evidence_missingとして通常のfailure flowへ移ります。

これによって、

required_outputs不足

Scheduler 409

terminal_unknown

という経路を通常動作から外すことができます。


HTTP 409を理由ごとに扱う

以前のRunnerには、HTTP 409を広くlease競合として扱う部分がありました。

しかし409には複数の意味があります。

現在はreason単位で分類しています。

例えば、

required_outputs_missing
invalid_required_outputs
task_plan_required
task_plan_not_ready
task_plan_job_mismatch

は確定的なbusiness / contract rejectionとして扱います。

一方、

stale_lease
lease_conflict
idempotency_conflict

などは、terminal requestがSchedulerに受理されたかどうかを慎重に確認する対象になります。

つまり、

HTTP statusだけではterminal outcomeを判断しない

という形へ変更しています。

terminal_unknownは、本当に結果が不明な場合へ限定する方向です。


reserved_event_type問題も修正する

Background Runnerでは別の問題も発生していました。

Runnerがprogress eventとして送ったevent typeをSchedulerが、

400 reserved_event_type

で拒否し続ける状態です。

原因は、

runner.started
runner.completed
runner.failed

のようなScheduler自身がlifecycleから生成するeventを、Runner側からもprogressとして送っていたことでした。

現在はこれらを、

  • local operator logには残す
  • Schedulerへのstructured progressには送らない

という形へ分離しています。

ログとして必要な情報と、wire protocolとして送信してよい情報を別のcontractとして扱うようになりました。


Background jobにも実行モードを導入する

required outputs対応の後には、Task Git側でも別の契約不整合が見つかっています。

以前のRunnerは、

job.kind == background

であれば、基本的にinput.task_gitを要求していました。

しかしBackground AIには、Gitへ変更を加えるExecutionだけでなく、

  • Planning
  • Replanning
  • Aggregation

もあります。

これらはrepositoryを読む必要はありますが、Task worktreeを作ってcommitする処理ではありません。

そこで、Background claimへ新しいexecution contractを追加しています。

{
  "execution": {
    "schema_version": "runner.background-execution.v1",
    "phase": "planning",
    "source_mode": "repository_read_only"
  }
}

source modeは大きく次の2種類です。

source_modeTask Git用途
repository_read_only不要Planning / Replanning / Aggregation
task_worktree必須Execution / Integration

Runnerは単にbackgroundかどうかではなく、このversioned contractを見て動作します。

これによって、

Background = 必ずGit変更

という暗黙の前提をなくしています。


capabilityも互換性確認に使う

SchedulerとRunnerで同じ名前の機能を別の意味として理解していると、claim後に初めて失敗します。

現在は、

background-execution-v1
task-worktree-v1

というcapabilityを組み合わせ、非互換なRunnerへjobをclaimさせない方向へ進んでいます。

これは、これまでArchitecture Testでsource codeの依存方向を固定していた考え方を、runtime contractにも広げたものと考えています。


Task Gitの検証範囲を広げる

前回の記事では、Task Gitは今後のRunner分割やGit境界の課題として扱っていました。

現在はかなり具体的なstate machineになっています。

基本形は、

Root Task

detached persistent worktree

Child

Grandchild

段階的merge

Root result

明示Finalize

remote main

です。

Scheduler自身はGit操作を行いません。

Runnerが所有worktree上で、

  • worktree作成
  • commit
  • merge
  • validation
  • cleanup
  • finalize

を実行します。

特にrootは、作業完了だけではremote mainへ反映されません。

execution success

finalize_ready

管理操作

Finalize

remote main更新

という境界を維持しています。


clean-cluster E2Eを追加する

今回の差分では、Task Git用のE2E infrastructureが大幅に増えています。

新しいsuiteは、

infra.task-worktree-clean-cluster-e2e.v2

として定義されています。

単純なmock Runnerではなく、

  • Scheduler
  • 実Runner client
  • Runner PVC
  • git-bare
  • Task worktree
  • lease
  • validation
  • finalize

を実際に接続することを目的としています。

scenarioはTW-E2E-00からTW-E2E-12まで用意されています。

例えば、

Planning
Execution
Child / Grandchild merge
Pod restart
lease loss
terminal response drop
idempotent finalize
cleanup retry
cross-workspace delegation
required_outputs
stale finalize
fresh PVC

などを対象にしています。

特にTW-E2E-10では、Cross-workspace delegationとrequired outputsを同じscenarioで確認する設計になっています。

これは今回のrequired_outputs問題に対する、かなり直接的な再発防止テストです。


ただしclean-cluster acceptanceはまだ別

sourceとlocal fixtureが追加されたことと、実clusterでacceptanceが完了したことは分けています。

clean-cluster E2Eでは、最終的に20回連続実行し、

requested_repeats = 20
passed_repeats    = 20
failed_cases      = 0
flake_count       = 0

をacceptance条件としています。

今回確認したsourceでは、runbookとharnessは用意されていますが、実clusterでの20回連続acceptanceは別のoperator作業として残されています。

unit testが通ったことだけで「Kubernetes上でも動いた」と扱わない方針です。


terminal処理にも冪等性を入れる

RunnerとSchedulerの間では、

Schedulerはcompleteを受理した

responseだけRunnerへ届かなかった

というケースがあります。

この場合にRunnerがもう一度completeすると、同じ結果を二重登録してはいけません。

現在はcompletefailについて、

method
path
job ID
attempt
canonical body

からfingerprintを作り、terminal operationを冪等化しています。

同じoperationであれば保存済みresponseを返し、異なるpayloadを同じkeyで送った場合は、

409 idempotency_conflict

として扱います。

「通信に失敗した」と「処理そのものが失敗した」を区別するための仕組みです。


テストそのものの管理方法も変わった

前回の記事では、

App JavaScript tests
Infra tests
Codex Runner tests
Python workspace tests

というsuite単位の確認が中心でした。

今回はさらに、

fast
provider
task-git
runner
performance
E2E

といったtest laneが追加されています。

Go testについてもcacheを利用した日常開発用testと、-count=1で測定するperformance gateを分離しています。

また、実行結果を単にconsoleへ残すのではなく、boundedなverification artifactとして扱う仕組みも追加されています。


review単位も明示する

大きな変更を一つのcommitへまとめないため、独立review sliceというルールも追加されています。

機能変更を、

contract-schema

provider-db

ui

docs

evidence

の順に分離します。

commitには例えば、

Request-ID: INFRA-APP-TASK-WORKTREE-01
Change-Slice: provider-db
Verification: npm run test:provider
Verification: npm run test:task-git

というmetadataを持たせます。

検査scriptでは、

  • merge commitではない
  • sliceの順序が正しい
  • production領域が混在していない
  • 変更量が上限を超えていない
  • verificationが記録されている

などを確認します。

これはArchitecture Testと同じく、

手順書に書くだけでなく、守られていない状態を機械的に検出する

という方向です。


ファイルサイズは再び増えている

前回の記事では、巨大ファイルを分割した成果として、

main.go
約4,200行 → 約1,800行

app.js
約1,900行 → 約800行

codex-runner.mjs
約1,900行 → 約1,300行

と書きました。

今回のsourceでは、おおよそ次の状態です。

main.go             2,139行
app.js                863行
codex-runner.mjs    2,016行

直前のソースと比較しても、

main.go
1,955 → 2,139

app.js
850 → 863

codex-runner.mjs
1,804 → 2,016

と増えています。

特にRunnerは、required outputs、Task Git、Background Execution、reconciliationなどの処理が増えたことで、本体も再び大きくなっています。

つまり、前回の記事で残課題としていた、

Runnerのpoll loop、active job管理、heartbeat、post-processingをさらにCoordinatorへ分割する

という課題は、まだ解消したとは言えません。

module数は増えましたが、codex-runner.mjs自体を小さなComposition Rootへする作業は引き続き必要です。


前回レビューから変わった点

今回のsourceを前回の調査と比較すると、required outputs問題についてはかなり前進しています。

前回は、

Task Plan
  required_outputs

Runnerは提出方法を知らない

Scheduler 409

terminal_unknown

という状態でした。

現在は、

Task Plan

versioned completion contract

Runner

local reporting CLI

Unix socket

job-local evidence store

preflight

completion payload

Scheduler validation

までsource上の経路が実装されています。

また、以前のログで確認したreserved_event_typeについても、reserved lifecycle eventをScheduler progressへ送らない形に修正されています。

409についてもstatus一括判定からreason単位の分類へ変更されています。

そのため、以前確認した障害の原因に対する修正方向は妥当です。


一方で新しい契約差分も見つかっている

契約を厳密にした結果、新しいdriftも検出できています。

現在のsourceでApp OpenAPIとInfra Runnerのcontract testを実行すると、

ProgressRequest properties drifted
from the canonical flat request allowlist

で失敗します。

App側のProgressRequestには、

job_id

がありますが、Infra側canonical allowlistには含まれていません。

required output関連のtestは通っていますが、App / Infra間のcontract gate全体としてはgreenではありません。

つまり、

contract testを追加したことで、別の契約差分を早い段階で発見できている

という状態です。

これは修正が必要な問題ですが、設計上は以前より健全です。

実Runnerを動かしてから契約不一致に気付くより、sourceのgateで止められるためです。


JSON evidenceにも仕様差が残る

required output CLIはJSON objectを扱えますが、現在はarrayを拒否します。

例えば、

{
  "status": "passed",
  "files": ["title.js"]
}

のようなobjectは扱えます。

一方、

["title.js"]

というarrayそのものは受理しません。

当初の設計例ではchanged_filesをJSON arrayとして扱う案もあったため、この点は仕様をどちらへ固定するか決める必要があります。

現在の実装に合わせるなら、

{
  "files": ["title.js"]
}

のようにobjectへ包む方がcontractとして明確です。


現在の位置づけ

7月30日時点では、主なテーマは「巨大な実装を4レイヤーへ分ける」ことでした。

現在はそこから一段進み、

Source Architecture

Runtime Contract

Ownership / Lease

Structured Evidence

Idempotency

Real-cluster Verification

という方向へ移っています。

単にコードの置き場所を整理するだけではなく、

  • 誰がGit状態を所有するか
  • AIが何を提出できるか
  • Runnerが何を検証するか
  • Schedulerが何を最終判定するか
  • 通信結果が不明な場合にどう扱うか
  • ProviderとConsumerの契約が一致しているか
  • 実clusterで同じ状態遷移を再現できるか

を機械的に確認する段階です。


現在残っている課題

現時点で優先度が高いものは次のとおりです。

App / Infra contract gateをgreenにする

ProgressRequestのcanonical schemaをAppとInfraで統一します。

OpenAPI、Provider実装、Consumer contract testの三者で同じschemaを正本にする必要があります。

clean-cluster E2Eを実行する

Task Gitとrequired outputsについて、local fixtureだけでなく実Runner imageとKubernetes環境でacceptanceを確認します。

特に、

TW-E2E-10

のCross-workspace + required outputsは、今回の障害に対する重要な確認項目です。

JSON evidenceのarray方針を確定する

arrayを正式対応するのか、objectのみをversioned contractとして採用するのかを決めます。

codex-runner.mjsを再び縮小する

moduleへの分離は進んでいますが、本体は再び2,000行を超えています。

poll loop、claim coordinator、terminal coordinator、post-processingなどをさらに分離する余地があります。

Deployment provenanceを固定する

Scheduler source revision、Runner source revision、image digest、OpenAPI digestを実行証跡として結び付ける必要があります。

source checkoutと実際に動いているimageが同じものだと確認できなければ、contract mismatch発生時の調査が難しくなります。


まとめ

前回は、Todo Schedulerを4レイヤーへ分割し、AI Agent Workstationの責務を整理することが中心でした。

今回は、その境界を実際の実行時contractとして固定する作業が進んでいます。

現在の主な変化は次のとおりです。

  • Task Planのrequired outputsをversioned completion contractとしてRunnerへ渡す
  • AIはlocal CLIとUnix socketを通して構造化evidenceを提出する
  • Scheduler credentialやlease tokenをAIへ渡さない
  • success送信前にRunnerがrequired outputsをpreflightする
  • HTTP 409をreason単位で分類する
  • reserved lifecycle eventをScheduler progressから除外する
  • Background jobへphaseとsource modeを導入する
  • PlanningとExecutionでTask Gitの扱いを分離する
  • terminal POSTへ冪等性を導入する
  • Task Gitのclean-cluster E2E harnessを追加する
  • test lane、performance gate、verification manifest、review sliceを導入する
  • App / Infra contract driftをsource testで検出する

一方で、構造化が進んだことで新しい課題も見えています。

特にApp / InfraのOpenAPI contract mismatch、clean-cluster acceptance、Runner本体の再肥大化は、次の改善対象です。

4レイヤー構成への移行そのものが目的ではなく、その上でAI Agent Workstationを安全に継続実行できる契約を作ることが、現在の中心課題になっています。

今後は、Task Gitのclean-cluster E2E、deployment provenance、Runner Coordinatorの分離を進めながら、source上の設計と実際にKubernetes上で動くsystemの差をさらに減らしていく予定です。