AI coding agentを「1チャット」ではなく「1変更セッション」で運用する
coding agentから「実装しました。テストも通っています」と返ってきたのに、翌朝見たら現在地が分からない。
- どのcommitを起点にしたのか
- どこまで変更してよかったのか
- 途中で何に詰まったのか
- そのテストは別processでも通るのか
情報はchatに残っています。ただ、長いtranscriptから毎回拾い直すのはつらい。background実行やremote agentを増やすと、chatを開いている人とreviewする人が別になるので、さらに追いづらくなります。
自分なら、chatはagentを操作するUIとして使います。仕事の単位はrepository側に置く「変更セッション」です。
ここでいう変更セッションは、base commit、作業branch、変更scope、完了条件、checkpoint、review evidenceを一つのtask IDへ結びつけたものです。特定のcoding agent専用機能ではありません。Claude CodeでもGitHub Copilotでも、repoへ変更を届けるなら同じ外枠を使えます。
coding agentの進化を「セッション管理」として見る
最近のcoding toolは、生成モデル以外の部分がかなり厚くなっています。
Claude Code 2.1.232のchangelogには、subagent forkの既定化、非team agentのbackground実行、セッション間メンションなどが並んでいます。VS CodeのAgents windowも、remote agent、複数セッション、session syncを扱います。v0は既存GitHub repoのimportからsandbox、chat単位のbranch、PR、merge後のdeployまでを一つの経路にしています。
これらが同じ内部architectureを採用している、という話ではありません。fork、chat branch、Git branchも別物です。
製品ごとの仕組みは違っても、reviewerが困る場所は似ています。実行場所やsessionが増えるほど、「どの変更を、何を根拠にmergeするか」をtoolの外側で揃えたくなる。chat transcriptだけでは、その境界が弱いからです。
repositoryに変更セッションを置く
最小構成なら、taskごとに次のdirectoryを作ります。
.agent-runs/
ACCT-241/
task.yaml
checkpoint.json
evidence/
acceptance.tsv
lint.log
typecheck.log
test.log
diff-check.log
review.md
ACCT-241はissueやticketと対応するtask IDです。agentを起動する前にdirectoryを作り、最終報告にも同じIDを出させます。
このdirectoryをcommitするか、CI artifactや外部storeへ逃がすかはteam次第です。processが消えてもtaskの入口と出口を再構成できるなら、保存先は自由です。
まずtask contractを固定する
.agent-runs/ACCT-241/task.yamlには、agentへのお願いではなく、変更の契約を書きます。
id: ACCT-241
base_commit: "<base-sha>"
branch: agent/acct-241-checkout-empty-state
scope:
allow:
- src/checkout/**
- tests/checkout/**
deny:
- infra/**
- .env*
acceptance:
- pnpm typecheck
- pnpm test --run tests/checkout
stop_when:
- scope外の変更が必要
- public APIの変更が必要
- credentialまたはproduction操作が必要
最初から長いspecを作る必要はありません。自分が外したくないのは次の項目です。
| 項目 | 何を固定するか |
|---|---|
id |
taskとrunを結ぶ識別子 |
base_commit |
diffの起点 |
branch |
変更を回収する場所 |
scope |
触ってよいpathと触らないpath |
acceptance |
外部processから再実行するcommand |
stop_when |
agentが人へ戻す条件 |
「checkoutのempty stateを直す」だけでは、完了判定がagentの解釈に寄ります。pnpm typecheckのようなcommandを併記すると、少なくとも同じ判定を人やCIが再実行できます。
task.yamlを書いただけでscope違反を防げるわけではありません。wrapperやCIでgit diff --name-onlyを検査して、契約を実行可能なgateへつなぐ必要があります。YAMLは正本であって、sandboxではない。この区別は残しておきます。
1セッションを1 worktreeへ分ける
並列agentへ同じworking treeを渡すと、片方のformatやpackage installがもう片方の観測へ混ざります。branchを分けただけでは、同じdirectoryを同時に触るprocessまでは隔離できません。
git fetch origin
git worktree add ../agent-acct-241 \
-b agent/acct-241-checkout-empty-state \
origin/main
agentは../agent-acct-241で起動します。task ID、branch名、worktree名を揃えておくと、止まったrunを探しやすいです。
Gitは通常、同じbranchを複数のworktreeへ同時にcheckoutさせません。開始時の衝突検知として使えるので、ここで--forceして回避しないほうがいいです。ただし、同じworktreeへ複数processを入れれば普通に競合します。launcher側でも「1 worktreeにつき実行中sessionは1つ」を守ります。
agent製品側のforkやremote sessionは、その製品が持つ実行機構です。worktreeはrepository上の変更境界。この二つを同一視せず、外側のGit境界だけは自分たちで握ります。
chatの生死とtaskの状態を分ける
chatが閉じても、taskが終わったとは限りません。承認待ちなら、むしろ「止まっているが再開可能」が正しい状態です。
| state | 意味 | 次の担当 |
|---|---|---|
planned |
contract作成済み、未着手 | agentを起動する人 |
running |
scope内で作業中 | agent |
blocked |
承認、情報、scope変更待ち | human |
needs-review |
gateとhandoffを作成済み | reviewer |
accepted |
review済み | mergeまたはcloseする人 |
checkpointは小さなJSONで足ります。
{
"sessionId": "ACCT-241",
"status": "blocked",
"baseCommit": "<base-sha>",
"headCommit": "<head-sha-or-null>",
"completed": [
"empty state component added"
],
"next": "public propsを増やしてよいか確認する",
"evidence": []
}
更新するタイミングは、少なくとも次の4か所です。
- agentを起動したとき
- commitまたは意味のある作業単位を終えたとき
- 承認や追加情報が必要になったとき
- reviewへ渡すとき
Cloudflare Agents SDK v0.16.1が扱うapproval待ちからの再開やdurable executionも、中断とtask終了を分ける例として分かりやすいです。自前のcoding workflowでも、blockedを失敗や完了へ丸めないほうが復旧しやすい。
再開時にchat transcriptを全部読ませる必要はありません。まずtask.yaml、Gitのbase/head、checkpoint.jsonを読む。transcriptは判断理由を調べたいときの補助資料に下げます。
完了判定はagentの外側で回す
agent自身が実装と採点を両方やると、最終メッセージだけきれいに成功することがあります。acceptance commandは別processで実行し、exit statusとlogを残します。
たとえば、次のscriptをworktreeのrootから実行します。
#!/usr/bin/env bash
set -u -o pipefail
RUN_DIR=".agent-runs/ACCT-241/evidence"
BASE="${BASE:-origin/main}"
mkdir -p "$RUN_DIR"
: > "$RUN_DIR/acceptance.tsv"
overall=0
run_gate() {
local name="$1"
local code
shift
if "$@" 2>&1 | tee "$RUN_DIR/$name.log"; then
code=0
else
code=${PIPESTATUS[0]}
fi
printf '%s\t%s\n' "$name" "$code" >> "$RUN_DIR/acceptance.tsv"
if [ "$code" -ne 0 ]; then
overall=1
fi
}
run_gate lint pnpm lint
run_gate typecheck pnpm typecheck
run_gate test pnpm test --run tests/checkout
run_gate diff-check git diff --check "$BASE...HEAD"
git diff --name-only "$BASE...HEAD" > "$RUN_DIR/changed-paths.txt"
git diff --stat "$BASE...HEAD" > "$RUN_DIR/diff-stat.txt"
exit "$overall"
これは最小例です。実際のrepositoryでは、task.yamlからbaseとacceptance commandを読み、allow/deny pathも検査します。pnpm test --runが存在しないprojectなら、そのrepoのscriptへ置き換えます。
frontendの変更では、commandの成否だけでは足りません。次もevidenceへ追加します。
- preview URLと対象route
- viewportやtest accountなどの再現条件
- 変更前後のscreenshot
- console error
- Playwright traceなどの実行artifact
「画面を確認した」ではなく、reviewerが同じrouteを開ける形にします。
review packetは会話の要約にしない
PR本文をchatの長い要約にすると、結局どこを確認すればよいか散らかります。.agent-runs/ACCT-241/review.mdは、reviewに必要な項目だけに絞ります。
# ACCT-241 review
- session_id: ACCT-241
- base_commit: <base-sha>
- head_commit: <head-sha>
- changed_paths: evidence/changed-paths.txt
- acceptance_results: evidence/acceptance.tsv
- preview: <preview-url-or-none>
- open_risks: <known-risk-or-none>
- rollback: revert <head-sha>
- review_status: needs-review
見る順番も固定します。
-
base_commitが想定した起点か - scope外のfileが混ざっていないか
- acceptance commandを独立実行して通るか
- diffとpreviewがtaskの意図に合うか
- open riskとrollbackが書かれているか
- 人が
acceptedへ進めるか決める
model名やagentの自信度は、このpacketの主役ではありません。base、head、diff、gate、previewが揃っていれば、別の人でも変更を追えます。
happy pathより先に「止めても戻れるか」を試す
仕組みを入れたら、最初に成功runを眺めるよりfailure pathを試したほうが早いです。
- agent processを作業途中で止め、branchとcheckpointだけで残作業を説明できるか
- public API変更が必要になったら
blockedで止まり、勝手にscopeを広げないか - 同じbranchを別worktreeへcheckoutしようとしたとき、開始を拒否できるか
- testが落ちたrunを
needs-reviewやacceptedへ進めないか - remote sessionを失っても、base/headとevidenceを作り直せるか
- previewが必要なUI変更で、URLやartifactが空のままacceptedにならないか
ここで復旧できなければ、通常時に速く見えても運用はまだchat依存です。
最初は1種類のtaskだけでよい
すべてのcoding taskへ巨大な管理layerを入れると、先に運用が嫌になります。自分なら、まずfrontendの小さなUI修正だけを対象にします。
- 1 task ID
- 1 worktree
- 1つのpath scope
- typecheckと対象test
- preview URLとscreenshot
- 1枚のreview packet
この構成で、途中停止とscope超過を一度ずつ試す。困った項目だけcontractへ足します。
coding agentは、forkやbackground実行によって何度でも作業を並べられるようになりました。だからこそ、chatを保存するだけでは足りません。変更の起点、停止位置、完了証拠をrepository側へ残しておけば、sessionが消えても仕事は消えません。生成速度を落とすための管理ではなく、その速さをmerge可能な変更へ変えるための足場です。