Harness の基本定義:モデル外部の制御システム
ここまでで、Agent をいくつかの最小部品へ分解しました。
Harness の基本定義:モデル外部の制御システム
ここまでで、Agent をいくつかの最小部品へ分解しました。
Model:次の一手を判断する
Loop:プロセスを前へ進める
Tools:現実世界に触れる
State:タスクのつながりを保つ
ここまで来ると、多くの人は自然に次の疑問を持ちます。
Agent にはすでにモデル、ループ、ツール、状態があるのに、なぜさらに Harness を語る必要があるのか?
さらに混同しやすい言い方があります。
Harness は、より外側にいて、より賢く、Agent をよりうまく管理する Agent なのか?
この言い方は正しそうに聞こえます。しかし実際には、アーキテクチャの捉え方を歪ませます。Harness は別の Agent ではありません。より大きな prompt でも、特定のフレームワーク名でもありません。モデルの外側にある制御システムに近いものです。ここでも同じ小さな CLI Agent の例を使います。
ユーザー:このプロジェクトのテストがなぜ失敗しているか見て、直してください。
この CLI Agent が demo にすぎないなら、とても単純にできます。
ユーザー入力をモデルへ送る
モデルがファイルを読みたいと言う
プログラムがファイルを読む
結果を prompt に戻す
モデルがファイルを変更したいと言う
プログラムがファイルを変更する
モデルがテストを走らせたいと言う
プログラムがテストを走らせる
この経路が一度通ると、すでに Agent らしく見えます。しかし他人に実際に使わせた瞬間、問題がすぐ出てきます。モデルが rm -rf を実行したいと言ったらどうするのか。モデルがユーザーの home ディレクトリにある私的ファイルを読みたいと言ったらどうするのか。モデルが十分間走り続け、ユーザーが中断したくなったら、現場をどう保存するのか。ツールがエラーを返したあと、次のラウンドのモデルは完全なログを見るべきか、要約だけを見るべきか。同じタスクを明日続けるなら、session はどこから復旧するのか。ある変更は成功したように見えるが、テスト検証がなければ、システムは本当に完了したとどう知るのか。本番のユーザーが「Agent がファイルを壊した」と言ったとき、何が起きたかをどう復元するのか。
これらの問題はモデル自身には属しません。モデル自身に決めさせるべきでもありません。モデルは各ラウンドで、現在のコンテキストに基づき次の判断を生成しているだけだからです。権限、実行環境、セッションライフサイクル、観測ログ、検証基準、ガバナンス戦略は、モデル外部のエンジニアリング責任です。これらの責任を合わせたものが、この記事で扱う Harness です。まず一文で押さえます。
Agent はタスク内で次の一手を判断し、Harness は各ステップを実環境で実行可能、制約可能、観測可能、復旧可能、検証可能、ガバナンス可能にします。
フレームワークは Harness の一部能力を提供できます。しかし Harness は、パッケージ名やプロダクト名というより、モデル外部のエンジニアリング責任の集合です。
この記事では Harness を大きくて万能な用語箱として扱いません。核心となる問いだけに答えます。
Harness と Agent はどんな関係にあるのか?なぜ Harness は別の Agent ではないのか?
問題の連鎖

まずこの記事の問題の連鎖を固定します。
Agent がツールを呼べるようになる
-> 「モデルの提案」と「システム実行」を区別しなければならない
-> システム実行には権限、サンドボックス、予算、エラー処理が必要
-> タスクが長くなる
-> session、ライフサイクル、中断、復旧が必要
-> プロダクトを他人が使うようになる
-> trace、評価、回帰、ガバナンスが必要
-> これらのモデル外部責任を合わせたもの
-> それが Harness
Harness が現れるのは、アーキテクチャを高度に見せるためではありません。Agent が実エンジニアリング環境へ入ったあと、現実に強いられて生まれる制御面です。七層の地図として覚えるなら、まず次の略語を覚えます。
ETCLOVG
Execution
Tools
Context
Lifecycle
Observability
Verification
Governance
図にすると次の通りです。

この図で最も重要なのは、七つの名詞そのものではありません。中央にある責任境界です。
Agent は次の一手を提案する
Harness は、その一手を実行できるか、どう実行するか、どう記録するか、どう検証するかを決める
モデルは依然として推論の中心です。ユーザー目標を理解し、コンテキストを読み、次のアクションを提案します。しかしモデルはファイルシステムを直接所有しません。shell を直接所有しません。権限を直接決めません。長期記憶や監査記録を直接書き換えることもありません。これらはすべて Harness に属します。
一、なぜツールが現れると Harness も現れるのか
最小のチャットアプリケーションには Harness は要りません。messages を管理できれば十分です。
ユーザー入力
-> モデル回答
-> 結果表示
この場面では、モデルの出力はただのテキストです。テキストが間違っていればユーザーは無視できます。テキストが不完全なら、ユーザーは追質問できます。テキストには副作用がありません。しかし Agent は違います。ツールを呼べるようになると、モデル出力はもはや「回答」だけではなくなります。「行動提案」になり始めます。たとえばモデルが次を出すとします。
{
"tool": "bash",
"input": {
"cmd": "npm test"
}
}
これは普通のテキストではありません。実環境へ入る申請書です。システムは一連の問いに答えなければなりません。
このツールは存在するか?
引数は合法か?
現在の session は shell 実行を許可しているか?
コマンドはファイルを変更しないか?
ユーザー確認が必要か?
どの作業ディレクトリで実行するべきか?
タイムアウトはいくつか?
結果が長すぎるとき、どう切り詰めるか?
失敗後、どうモデルへ書き戻すか?
Harness がないと、これらの問いは簡単に一文へ丸められます。
モデルがやりたいことを、私たちが代わりにやる。
これが多くの Agent demo の危険な点です。モデルの行動意図をシステムコマンドとして扱ってしまいます。短いタスクでは問題がないかもしれません。しかし本物のコードベースへ接続すると、この仮定は事故の入口になります。CLI Agent では、モデルは次のような提案をできます。
package.json を読む
失敗したテスト名を検索する
関連ソースファイルを開く
実装を変更する
テストを実行する
これらのアクションはどれも妥当に見えます。しかしリスクはそれぞれ違います。ファイル読み取りとファイル書き込みは違います。npm test 実行と任意 shell 実行は違います。現在リポジトリの変更とユーザー home ディレクトリの変更は違います。ローカルコマンド実行と外部ネットワークアクセスも違います。Harness の第一の価値は、これらのアクションを「モデルが言った」から「システムが審査して実行した」へ変えることです。この違いは非常に重要です。
エンジニアリング実装では、モデル出力は intent、つまり意図として見るのが望ましいです。Harness は intent を受け取り、それを action、つまり制御されたアクションへ変えます。最小の疑似コードはおおよそ次のようになります。
while (!session.done) {
const modelInput = harness.context.build(session);
const intent = await model.next(modelInput);
const decision = await harness.policy.review(intent, session);
if (decision.type === "deny") {
session.appendObservation(decision.reason);
continue;
}
if (decision.type === "ask_user") {
session.pauseForApproval(decision.prompt);
continue;
}
const observation = await harness.execution.run(decision.action);
session.appendObservation(observation);
}
ここでツールを呼んでいるのはモデルではない点に注意してください。モデルは intent を生成するだけです。Harness は intent を policy、execution、session、observation というシステム境界へ置きます。これが Harness が別の Agent ではない最初の理由です。
Harness はモデルの代わりに次の一手を考えるのではなく、モデルの次の一手をエンジニアリング制約の中へ置きます。
二、Agent と Harness の境界はどこにあるのか
概念を混ぜないために、まず実行境界を一本引きます。同じ「失敗したテストを直す」でも、一つの完全なラウンドはおおよそ次のようになります。

この図では、Model -->> Harness の矢印が非常に重要です。モデルが返すのはツール意図であって、ツール結果ではありません。本当にプロジェクト環境に触れるのは Tool Runtime と Execution です。本当に事実の過程を保存するのは Session Store です。実行を許可するかどうかを決めるのは Harness 内のポリシー層です。この境界が混ざると、システムには三種類のよくある問題が起きます。
第一の問題は、Agent を「モデル + 裸の実行器」として書いてしまうことです。疑似コードはたいてい次のようになります。
while (true) {
const output = await model(prompt);
if (output.includes("bash")) {
const result = await exec(output.command);
prompt += result;
}
}
このコードは短く見えますが、すべての重要問題を隠しています。権限がありません。構造化ツールプロトコルがありません。中断復旧がありません。監査がありません。検証がありません。コンテキスト戦略がありません。「モデルが一度外部アクションを駆動できる」ことは示せても、「システムが実タスクをホストできる」ことは示せません。
第二の問題は、Harness を「Agent を監督する別の Agent」だと考えることです。たとえば外側のモデルに、内側のモデルがあるコマンドを実行してよいか判断させるような設計です。これは特定の場面ではポリシーの一部になり得ますが、Harness の本質ではありません。Harness の鍵は「もう一度推論する」ことではなく、決定的なエンジニアリング制御です。たとえば:
パスは workspace 内でなければならない
ファイル書き込みは patch 経由でなければならない
shell コマンドにはタイムアウトが必要
危険なコマンドはユーザーに尋ねなければならない
すべてのツール呼び出しはイベントログへ落とさなければならない
テスト検証は最終完了状態と結びつかなければならない
これらのルールを別モデルの自由判断へ完全に委ねるべきではありません。システムポリシー、型制約、Runtime チェック、監査記録であるべきです。
第三の問題は、Harness をあってもなくてもよい「プロダクト層」と見なすことです。これも違います。Harness は UI、デプロイ、アカウント、課金だけではありません。最小 CLI 段階から存在します。次を区別した時点で:
モデル提案
システム実行
実行結果の状態書き戻し
次ラウンドのコンテキスト再構成
あなたはすでに Harness を書いています。初期の Harness が薄いだけです。Agent が実タスクに向くにつれ、Harness は徐々に厚くなります。
Harness は wrapper ではなく制御ループである
Harness を「Agent の外側に包む一層の wrapper」とだけ理解しても、まだ一層見落とします。
Wrapper は薄い適配コードという印象を与えます。入力を受け取り、Agent を呼び、出力を返すものです。しかし本当の Harness は制御ループに近いものです。モデル行動の前にフィードフォワード制約を与え、モデル行動の後にフィードバック信号を収集し、その信号で次の入力、ツール可視性、権限戦略、予算、検証要求を修正します。
CLI Agent の一回の実行に置くと、この制御ループはおおよそ次の通りです。
フィードフォワード制約:
system instruction、可視ツール、作業ディレクトリ、予算、権限モード、プロジェクトルール
モデル判断:
テキストまたは tool intent を生成する
実行フィードバック:
ツール結果、エラー種別、ファイル変更、コスト、レイテンシ、ユーザー承認結果
状態更新:
session event、context projection、trace、verification evidence
次ラウンド制約:
可視ツールを減らす、コンテキストを圧縮する、先に検証を要求する、ユーザー待ちで一時停止する、タスクを終了する
これは、Harness の価値が「モデルの外側にもう一つモデルをかぶせる」ことではないと示しています。価値は、モデルの動的判断を、センサー、制約、フィードバック、状態を持つエンジニアリングシステムへ置くことです。
この制御ループがなくても、システムは動いているように見えます。
model -> tool -> model -> tool -> final
しかしそれでは、どのツール選択が悪化しているのか、なぜコストが上がったのか、ある失敗が権限ブロック、ツールエラー、コンテキスト汚染、モデル判断ミスのどれなのか、次ラウンドで何を変えるべきか分かりません。
したがって Harness と Agent の境界は、さらに一文へ圧縮できます。
Agent は行動意図を生成し、Harness は行動条件を調節する。
この「調節」が重要です。Harness は実行だけでなく、制約、感知、フィードバックも担うという意味だからです。
Session、Harness、Sandbox:一つのオブジェクトに混ぜない

より成熟した Agent システムは、通常三つのものを分けます。
Session:事実源。このタスクで何が起きたかを記録する。
Harness:制御ループ。次の一手をどう実行するかを決める。
Sandbox:実行手。ファイル、コマンド、ネットワーク、外部システムへ本当に触れる。
この三つを一つのプロセスオブジェクトへ混ぜると、初期実装は速いですが後で非常につらくなります。
たとえば最小 demo では、プロセス内変数へ同時に次を入れるかもしれません。
messages
cwd
tool results
current plan
permission state
temporary files
running process handles
final answer
一回きりの実行では動きます。しかしプロセスがクラッシュした瞬間、すべての事実が失われます。sandbox が掃除されると session も消えます。ユーザーが翌日続きをしたいとき、システムは昨日何が起きたかを圧縮要約から推測するしかありません。
分けると、責任はずっと明確になります。
Session は messages と同じではありません。messages はモデルが次のラウンドで見る投影にすぎません。Session はより完全なイベント台帳を記録するべきです。
UserMessage
ModelIntent
ToolValidated
PolicyReviewed
ApprovalRequested
ApprovalGranted
ToolStarted
ToolFinished
ObservationAppended
ContextCompacted
VerificationRun
TaskCompleted
TaskBlocked
Harness は session を中心に実行を復旧できます。たとえ制御プロセスが落ちても、session log を読めば、ユーザー目標、実行済みツール、権限決定、ファイル変更、検証結果、未完了事項が分かります。
Sandbox は交換可能な実行手です。ローカル作業ディレクトリ、一時 git worktree、コンテナ、リモート VM、ブラウザ環境、ホストされた実行プールになり得ます。Sandbox のクラッシュは、タスク消滅と同じであってはいけません。記録可能な実行失敗となり、Harness がリトライ、環境変更、ロールバック、ユーザー確認を決めるべきです。
この三分法は、「Agent が実行中のプロセス」をシステム事実源と見なすという、よくある誤りを避けます。プロセスは死にます。事実源は session であるべきです。実行手は交換できます。制御ループは再起動できます。
三、Execution:モデルを OS の上に直接立たせない
ETCLOVG の第一層は Execution です。答える問いは素朴です。
モデルが提案したアクションは、結局どこで、どの身分で、どんな制限のもとに実行されるのか?
CLI Agent では、Execution は少なくとも次を知る必要があります。
現在の作業ディレクトリ
アクセス可能なファイル範囲
利用可能な環境変数
コマンドタイムアウト
最大出力長
ネットワークを許可するか
ファイル書き込みを許可するか
バックグラウンドプロセス起動を許可するか
Execution 層がないと、ツール呼び出しは OS に直接貼り付いて走ります。自分の PC で遊ぶ demo ならまだ我慢できるかもしれません。しかし他人に使わせる Agent としては危険すぎます。たとえばユーザーは Agent に現在リポジトリの修正だけを望んでいます。ところがモデルが次を読みたいと提案します。
/Users/alice/.ssh/id_rsa
このときモデルが自分で「読むべきではない」と気づくことに期待してはいけません。Harness は Execution 層で止めなければなりません。別の例として、モデルが次を実行したいと提案します。
npm test
これは安全そうに見えますが、テストスクリプト自体がサービスを起動したり、キャッシュを書いたり、ネットワークへアクセスしたり、長時間走ったりするかもしれません。Execution 層は少なくとも、タイムアウト、出力切り詰め、プロセス掃除、作業ディレクトリ隔離を提供するべきです。そうしないと普通のテスト一回で Agent が固まる可能性があります。最小 Execution インターフェースは次のようにできます。
type ExecutionRequest = {
kind: "read_file" | "write_file" | "shell";
cwd: string;
args: unknown;
timeoutMs: number;
allowedPaths: string[];
sessionId: string;
};
type ExecutionResult = {
ok: boolean;
stdout?: string;
stderr?: string;
changedFiles?: string[];
exitCode?: number;
truncated?: boolean;
};
ここで重要なのは型名ではありません。システムが「実行」を独立したガバナンス可能なオブジェクトにしていることです。モデルはこれを迂回できません。ツールも勝手に迂回できません。UI も直接迂回するべきではありません。現実環境へ触れるすべてのアクションは Execution を通る必要があります。これが Harness が現実世界へ向けて持つ第一のゲートです。
四、Tools:ツールは関数ではなくプロトコル入口である
第二層は Tools です。前節の Execution は OS に近い層です。Tools はモデルに近い層です。答える問いは次です。
モデルはどの能力を見ることができるか?
それらの能力はどんな構造で提出されるべきか?
システムはツール結果をどう observation に変えるか?
多くの人は最小 Agent を書くとき、ツールを普通の関数として書きます。
async function readFile(path: string) {
return fs.readFile(path, "utf8");
}
関数自体に問題はありません。しかし Agent に公開するなら、関数だけでは足りません。プロトコル説明も必要です。
ツール名は何か
入力 schema は何か
読み取りか書き込みか
確認が必要か
並行実行できるか
エラーをどう表すか
結果をどう切り詰めるか
結果はコンテキストへ入るか
そうでなければ、モデルとシステムは自然言語で推測し合うしかありません。ツールプロトコルの価値は、「ファイルを読みたい」を構造化リクエストへ変えることです。Tool Runtime の価値は、構造化リクエストを制御された実行へ変えることです。完全なツールパイプラインはおおよそ次のようになります。

この図で最も見落とされやすいのは Observation です。ツール結果は stdout の一段落だけであってはいけません。システムへ次を伝える必要があります。
今回の呼び出しは成功したか
出力は切り詰められたか
どのファイルが読まれたか
どのファイルが変更されたか
復旧可能エラーが発生したか
次のラウンドでモデルは何を見るべきか
UI は何を表示すべきか
監査ログは何を保存すべきか
ツール結果を文字列だけにすると、短期的には楽です。長期的には後続の仕組みをすべて難しくします。Context は何を残すべきか分かりません。Lifecycle はどう復旧するか分かりません。Observability はどう問題を調べるか分かりません。Verification は何を検証するか分かりません。Governance は越権したかどうか分かりません。だから Tools 層は「能力が多いほどよい」ものではありません。本当に解くべきことは、能力入口のプロトコル化です。小さな CLI Agent なら、最初は四つのツールで十分です。
read_file
search
apply_patch
run_command
ただし、この四つのツールはいずれも同じプロトコルを通るべきです。ツール数は少なくて構いません。ツール境界を曖昧にしてはいけません。
五、Context:各ラウンドでモデルが見るものは Harness が組み立てる
第三層は Context です。答える問いは次です。
このラウンドでモデルは結局何を見るべきか?
これは prompt の連結に見えます。しかし Agent では、prompt 連結よりはるかに複雑です。長いタスクでは情報が増え続けるからです。
ユーザーの元の目標
プロジェクトルール
読み取り済みファイル
検索結果
テストログ
変更記録
権限拒否
ユーザー確認
モデル自身の計画
前ラウンドのツール結果
すべてを無造作に prompt へ詰めると、三つの問題にぶつかります。第一に token が爆発します。第二に古い情報と新しい情報が互いに汚染します。第三に、モデルが無関係な細部に引っ張られます。そのため Context 層は「すべての状態を保存する」ものではありません。状態からこのラウンドのモデルが必要とする作業台を投影するものです。関係は次のように書けます。
State は事実倉庫。
Context はこのラウンドの視野。
Memory は会話横断の経験。
Prompt は最終入力形式。
これらの言葉はよく混ざります。Harness はそれらを分けなければなりません。CLI Agent では、Session Store が完全なテストログを保存しているかもしれません。しかし次のラウンドのモデルは完全ログを必要としないかもしれません。必要なのは次のようなものだけかもしれません。
テストコマンド:npm test
失敗ファイル:src/parser.test.ts
エラー要約:expected 3 but received 2
最近の変更:src/parser.ts の 42 行付近
制約:現在 workspace だけを変更できる
Context 層はこれを行います。モデルの代わりに考えるのではありません。モデルのために、清潔で、関連性があり、制約された判断現場を準備します。図にすると次のようになります。

この図で最も重要なのは Context Policy です。多くの Agent 失敗は、モデルが推論できないからではありません。Harness が見せた現場が乱れすぎているからです。たとえば三十分前にすでに棄却されたエラーログを最新観測より前に置く。ユーザーに否定された案を高優先度コンテキストに入れ続ける。依存関係インストールログ全文を入れて、本当に関連するコード片を押し出す。こうしたことはすべてモデルの判断を悪化させます。したがって Context 層のエンジニアリング目標は「情報が多ければ多いほどよい」ではありません。目標は次です。
十分に完全である
十分に新しい
十分に関連している
十分に説明可能である
Context 層がなければ、Agent は長いタスクの中で少しずつ視力を失います。Context 層があって初めて、モデルは毎ラウンド整理された作業台へ戻ってこられます。
六、Lifecycle:長いタスクは永遠に続く while true ではない
第四層は Lifecycle です。答える問いは次です。
一つの Agent タスクは開始から終了まで、途中でどんな状態を経るのか?
最小 demo では、多くの人が次のように書きます。
while (true) {
const intent = await model.next(context);
const result = await run(intent);
context.push(result);
}
このコードはもちろん動きます。しかし実タスクは永遠に続く while true ではありません。ユーザーに中断されます。ユーザー確認を待ちます。ツール失敗により復旧へ入ります。予算が尽きて一時停止します。テストが通ったことで完了します。権限不足でブロックされます。ネットワーク、ファイル衝突、並行変更によって再判断が必要になります。だから Harness はタスクライフサイクルを明示的にモデル化する必要があります。重要なのは状態名の美しさではなく、タスクが途切れることを認めることです。他人に使わせる Agent は、ユーザーが画面の前で一度の完走を待ってくれると仮定できません。すべてのツールが成功するとも仮定できません。モデルが毎ラウンド正しい方向へ進むとも仮定できません。Lifecycle 層が保存すべきなのはプロセス境界です。
タスクはいつ始まったか
現在どのステップで詰まっているか
なぜ一時停止しているか
ユーザーは何を承認したか
すでにどのアクションを実行したか
どのアクションはリトライできるか
どのアクションはリトライできないか
完了条件は何か
ここで自然に Session が出てきます。Session はチャット履歴の別名ではありません。Session は長いタスクの事実源です。中には prompt だけでなく、イベントを保存するべきです。
UserMessage
ModelIntent
PolicyDecision
ToolStarted
ToolFinished
FileChanged
ApprovalRequested
ApprovalGranted
VerificationPassed
TaskCompleted
これらのイベントがあって初めて、システムは replay できます。replay できるから debug できます。debug できるから改善できます。復旧できるから長いタスクをホストできます。Lifecycle がなければ、Agent の各実行は賭けのようになります。成功すると魔法のように見えます。失敗すると復盤が困難です。
七、Observability:事実ログがなければ、改善可能な Agent はない
第五層は Observability です。答える問いは次です。
Agent が間違えたとき、どこで間違えたのかどう知るのか?
普通のプログラムでは、ログ、メトリクス、trace はすでに常識です。しかし多くの Agent demo には、かえってこれらの基盤がありません。最後の会話だけを保存します。ユーザーが「ファイルを変に変更した」と言っても、開発者は曖昧な transcript しか見られません。それでは足りません。Agent の失敗は多くの層で起こり得ます。
モデルがユーザー目標を誤解した
Context が古い情報を入れた
ツール schema が緩すぎた
権限戦略が危険なアクションを通した
shell コマンドがタイムアウトしたがマークされなかった
ツール出力が切り詰められたのにモデルへ伝えていない
テストが失敗したが最終回答で完了と言った
ユーザーが拒否したアクションが再び実行された
Observability がなければ、これらの問題はすべて一文へ圧縮されます。
モデルが不安定。
しかしこの文にはほとんどエンジニアリング価値がありません。Harness の観測層は、一つのタスクを確認可能なイベントチェーンへ分解する必要があります。少なくとも次へ答えられるべきです。
ユーザーの元の目標は何か
モデルは各ラウンドで何を見たか
モデルは各ラウンドで何を提案したか
システムは何を許可または拒否したか
ツールは実際に何を実行したか
ツールは何を返したか
どの出力が切り詰められたか
どのファイルが変化したか
検証コマンドは何か
最終完了判断は何に由来するか
これが trace の価値です。きれいな dashboard を作るためではありません。Agent 失敗後に責任層を特定するためです。たとえばテストが直っていない場合、原因はまったく異なるかもしれません。
モデルが正しいファイルを読んでいない
検索ツールがテスト名を見つけていない
Context が重要ログを切っている
apply_patch が誤った位置を変更している
run_command が誤ったテストコマンドを実行している
Verification が失敗終了コードを失敗として扱っていない
原因ごとに修正方法は違います。観測がなければ、盲目的に prompt を調整するしかありません。観測があれば、Context、Tool、Execution、Verification、モデル指示のどれを調整すべきか分かります。したがって Observability は Harness の長期改善基盤です。Agent を「玄学的な調整」から「エンジニアリング診断」へ戻してくれます。
八、Verification:完了はモデルが決めるものではない
第六層は Verification です。答える問いは次です。
システムは何を根拠に、タスクが完了したと信じるのか?
チャットアプリケーションでは、モデルが「説明は以上です」と言えば、通常は完了です。しかしプログラミング Agent では、それではまったく足りません。ユーザーが CLI Agent にテスト失敗の修正を頼み、モデルが最後にこう答えたとします。
問題を修正しました。
この文は完了証拠になりません。本当の完了証拠は外部検証から来るべきです。
関連テストが通った
新しい失敗を導入していない
変更範囲が期待通りである
重要ファイルが実際に更新された
ユーザー要求の制約に違反していない
Verification 層は、「モデルが完了を自称した」を「システムが完了を検証した」へ変えます。最小実装はとても簡単にできます。
type VerificationPlan = {
commands: string[];
expectedFiles?: string[];
successCriteria: string[];
};
async function verifyFix(plan: VerificationPlan) {
for (const command of plan.commands) {
const result = await execution.run({
kind: "shell",
args: { command },
timeoutMs: 120_000,
});
if (!result.ok) {
return { ok: false, reason: result.stderr ?? result.stdout };
}
}
return { ok: true };
}
検証は常にテストを走らせるとは限りません。タスクごとに異なる証拠があります。
ドキュメントタスク:リンク、見出し、形式を確認する
リファクタリングタスク:単体テスト、型チェック、lint を実行する
データタスク:出力行数、schema、サンプリングを検証する
デプロイタスク:ヘルスチェック、ログ、ロールバックポイントを確認する
リサーチタスク:出典、日時、引用チェーンを残す
しかし原則は同じです。
完了状態はモデルの言語だけに由来してはいけない。
完了状態は外部証拠に結びついていなければならない。
Verification が欠けると、Agent は「完了したように見える」幻覚を起こしやすくなります。ファイルを一つ修正したがテストを走らせていないかもしれません。テストを走らせたがコマンドが間違っているかもしれません。テストが失敗したのに、要約時に失敗を無視したかもしれません。最初のエラーだけを直して、タスク全体を完了扱いにしたかもしれません。Harness はこれらを止める必要があります。だから Verification は Observability とよく結びつきます。観測は何が起きたかを教えます。検証は基準を満たしたかを教えます。両者が合わさって、Agent は「作業できる」から「作業を完了できる」へ進みます。
九、Governance:Agent が強くなるほど、境界が必要になる
第七層は Governance です。答える問いは次です。
この Agent は、誰のために、どんなルールの下で働くのか?
Execution が低レベル実行制限であり、Tools が能力入口プロトコルであるなら、Governance はより上位のガバナンス戦略です。関心は、あるコマンドを実行できるかだけではありません。さらに次を気にします。
ユーザーごとに異なる権限があるか
workspace ごとに異なる戦略があるか
どのツールはデフォルトで読み取り専用か
どのアクションは二重確認が必要か
どのデータはモデルコンテキストへ入れてはいけないか
どのログは脱機密化が必要か
どの memory は長期保存できるか
どの外部サービスを呼び出せるか
どのタスクは人間の受け入れ確認が必要か
個人 CLI では、Governance は薄くて構いません。たとえば三つだけでもよいです。
現在のリポジトリだけにアクセスできる
ファイル書き込みは監査可能な patch 経由でなければならない
危険な shell コマンドはユーザーに尋ねなければならない
しかし Agent がチーム環境へ入ると、ガバナンスは急速に複雑になります。プロジェクトごとにルールが違います。一部のリポジトリではコード片をアップロードできません。一部のコマンドは CI 外で走らせてはいけません。一部のファイルにはシークレットが含まれます。一部のユーザーは読み取りだけでき、書き込みはできません。一部のタスクは監査記録を残す必要があります。これらは、モデルの自覚だけで安定的に守れるものではありません。Harness が戦略へ変えなければなりません。単純な戦略判断は次のように書けます。
function reviewAction(action: Action, session: Session): PolicyDecision {
if (!isInsideWorkspace(action.path, session.workspace)) {
return { type: "deny", reason: "path outside workspace" };
}
if (action.kind === "shell" && isDestructive(action.command)) {
return { type: "ask_user", prompt: "危险命令需要确认" };
}
if (action.kind === "write_file" && session.mode === "read_only") {
return { type: "deny", reason: "session is read-only" };
}
return { type: "allow" };
}
このコードは普通です。しかし Harness の性格をよく示しています。Harness は「より賢い」ことで問題を解くのではありません。「境界を明確にする」ことで問題を解きます。これが Agent との根本的な違いです。Agent の価値は不確定タスク内での判断能力から来ます。Harness の価値は確定境界内での制御能力から来ます。両者は上下関係にある知能体ではなく、異なる責任層です。
十、ETCLOVG を小さな CLI Agent へ落とす
最初の例へ戻ります。小さな Claude Code 風 CLI Agent を書き、ユーザーのテスト失敗修正を手伝わせるなら、最小 Harness はまず次のように育てられます。
src/
agent/
loop.ts
model-client.ts
harness/
execution.ts
tools.ts
context.ts
lifecycle.ts
trace.ts
verification.ts
policy.ts
session/
event-log.ts
store.ts
cli/
main.ts
これは固定ディレクトリ名を推奨しているわけではありません。責任を混ぜないことを強調しています。
agent/loop.ts はモデルラウンドを前へ進めます。
直接 shell を exec するべきではありません。
harness/execution.ts はコマンドとファイルアクションを実行します。
モデルの次の思考を決めるべきではありません。
harness/tools.ts はツールプロトコルと observation を扱います。
権限をこっそり迂回するべきではありません。
harness/context.ts は session から本ラウンド入力を投影します。
完全な履歴を無造作にモデルへ詰めるべきではありません。
harness/lifecycle.ts は一時停止、復旧、完了、失敗状態を扱います。
世界全体を while true だけで表現するべきではありません。
harness/trace.ts はイベントとデバッグ情報を記録します。
最後の回答だけを保存するべきではありません。
harness/verification.ts は外部証拠でタスク完了を確認します。
モデルの「直しました」という一文を信じるべきではありません。
harness/policy.ts は権限、範囲、ガバナンスを扱います。
高リスクアクションをモデルの自覚に委ねるべきではありません。これらの責任を合わせると、完全な耐荷重経路が得られます。
ユーザー入力
-> Lifecycle が session を作成
-> Context が本ラウンド入力を組み立てる
-> Model が次の intent を返す
-> Tools が intent 構造を検証する
-> Governance が許可するか判断する
-> Execution がアクションを実行する
-> Observability がイベントを記録する
-> Context が次ラウンド入力を生成する
-> Verification が完了か判断する
この経路が Harness の骨格です。モデルの推論仕事を奪いません。自分が別の Agent であるふりもしません。ただ一つのことをします。
モデルの動的判断を、安定したエンジニアリング制御システムへホストする。
最小 demo だけを書くなら、薄い Harness から始めて構いません。しかし最初から境界を乱さないでください。ローカルファイルツールだけであっても、次を区別する必要があります。
tool intent
policy decision
execution result
session event
model context
verification evidence
これらのオブジェクトは最初は少し冗長に見えます。しかしタスクが長くなり、ツールが増え、ユーザーが増えたときに助けてくれます。
十一、イベントオブジェクト:Harness の専門性は小さなオブジェクトに宿る

七層分類だけで Harness を理解すると、まだ抽象に留まりやすいです。実際にコードを書くとき、Harness の専門性は、小さいが安定したイベントとオブジェクトの集合に表れます。
たとえばモデルが出力するものは、直接「コマンド」と呼ぶべきではありません。よりよい名前は次です。
Intent:モデルが提出した行動意図。
Intent はまだ承認されておらず、実行もされていません。現在のコンテキストに基づき、モデルが提出した次の一手にすぎません。これを Intent と呼ぶことで、システムに次を問い続けさせられます。
この意図の構造は合法か?
どのツールに対応するか?
リスクレベルは何か?
現在の session で実行を許可されるか?
権限を通過した後、初めて次になります。
ExecutionRequest:実行環境へ渡す準備ができたアクション要求。
実行が終わると、得られるのは次です。
ExecutionResult:実行環境が返した事実結果。
しかし ExecutionResult も、そのままモデルへ詰めるべきではありません。さらに整理して次にします。
Observation:次ラウンドのモデルが理解できる観測。
Observation に含めるべきなのは stdout だけではありません。次も含めるべきです。
成功したか
終了コード
切り詰められたか
どのファイルが変更されたか
エラー種別
リトライ可能か
権限または安全イベントを引き起こしたか
検証証拠を生んだか
これらのオブジェクトは命名へのこだわりに見えますが、解いているのは復盤と復旧の問題です。ユーザーが「なぜこのファイルを変更したのか」と聞いたとき、最終回答だけを渡すことはできません。次を取り出す必要があります。
ModelIntent:モデルはなぜこの変更を提案したか
PolicyDecision:システムはなぜ実行を許可したか
ExecutionResult:実際の実行で何が起きたか
Observation:次ラウンドのモデルはどの結果を見たか
VerificationEvidence:完了判断の根拠は何か
AuditRecord:誰がどのアクションを承認したか
これらのオブジェクトがなければ、Harness は transcript から推測するしかありません。transcript は人に見せる叙述であって、システムが復旧や評価に使う事実源ではありません。
より完全なイベントストリームは次のように書けます。

この図の重点はノードの多さではなく、事実がどこから来るかです。モデルが「テストを実行する必要がある」と言うのは ModelIntent にすぎません。テストが本当に実行終了して初めて ExecutionResult があります。失敗ログ、終了コード、切り詰め状態を整理して初めて Observation があります。最後にタスク完了と言えるかどうかは、VerificationEvidence を見ます。
だから messages は session と同じではありません。messages はモデル可視コンテキストです。session event log はシステムの事実源です。前者は圧縮、並べ替え、投影できます。後者はできる限り監査可能、リプレイ可能、原因帰属可能であるべきです。
このイベントモデリングは、後の eval にも影響します。多くの Agent 評価は最終回答だけを見ると、本当の問題を見逃します。Agent は最終的に正しい回答をしても、途中で危険なツールを使ったかもしれません。最終的には失敗しても、原因はテスト環境に依存関係がないことで、モデル判断の誤りではないかもしれません。イベントチェーンが十分明確であって初めて、評価は具体的な層へ原因帰属できます。
モデル判断が間違っていたのか?
ツール schema が曖昧すぎたのか?
権限戦略が広すぎたのか?
コンテキスト投影が重要ファイルを漏らしたのか?
サンドボックス環境が一致していないのか?
検証コマンドの選択が間違っていたのか?
イベントオブジェクトがなければ、専門的な失敗原因帰属はありません。失敗原因帰属がなければ、Harness の改善は感覚頼みになります。
結び:モデルは判断し、Harness は判断をホストする
この記事は三文に圧縮できます。第一に、Agent はモデルが自分で作業しているのではなく、モデルがループの中で次の一手を提出しているものです。第二に、次の一手が実環境へ入るなら、Execution、Tools、Context、Lifecycle、Observability、Verification、Governance が必要になります。第三に、これらのモデル外部の制御責任を合わせたものが Harness です。したがって Harness は別の Agent でも、より長い system prompt でも、監督モデルでも、ツール集合でも、フレームワーク名でも、プロダクト UI でもありません。Agent が安全に現実世界へ入るための制御システムです。次の記事では、さらに自然な進化経路を見ます。
Chat Agent
-> Tool Agent
-> Runtime Agent
-> Managed Agent
そのとき分かるはずです。Harness は最初から思いつきで設計する巨大アーキテクチャではありません。Agent が現実世界へ少し深く触れるたび、やむを得ず育ってくるエンジニアリング境界です。
教学 Harness への落とし込み
教学プロジェクトの Harness は最初から厚くなくてかまいません。ただし責任は分けます。Express API は request orchestration、runAgentLoop() は state transition、ToolRegistry は tool execution boundary、JsonlSessionStore は事実記録、React UI は messages と events の projection を担当します。ここを混ぜなければ、後から permission、trace、resume を足しても core を作り直さずに済みます。
GitHub ソース: 00-04-harness-control-system.md