Routine Labo

作らせる前に、これを置く
— AIが自分でブラウザを開いて、自分のコードを落とすまで

AIにアプリを作らせる話は、たいてい「作らせた」で終わります。動いた画面を見せて、おしまい。 でも実際に困るのはその先で、「動きました」と返ってきたコードを、動かしてみる以外に確かめる方法がない。 この記事では順番をひっくり返します。アプリを1行も書く前に、合格の条件だけを先に置く。 そのうえで Claude Code と Codex(GPT-6 Astra)に同じ課題を渡し、 同じゲートを2つとも通れる環境を組み立てます。

作らせる前に、これを置く
図解:Routine Labo

まず、これを見てください

違いは、これだけ
図解:Routine Labo

同じAIです。同じ依頼文です。違うのは、作らせる前に 「何ができたら合格か」を置いたかどうかだけ。 左はあとで開き直すと画像が消えていて、右は残っています。

今日のテーマ —— ハーネスエンジニアリング

AIそのものを賢くするのではなく、AIの「周り」に仕組みを置いて、失敗しにくくする考え方のことです。 上の右側を作ったのは、賢いAIではありません。周りに置いた仕組みです。 この記事では、その仕組みを実際に組み立てます。

この記事は3つの層に分けて書いてあります。自分に合うところから読んでください。

1. 考え方(この章と次の章)… ハーネスが何のために要るのか。コードは出てきません
2. 手を動かす(2〜6章)… 受入条件を自分で組みます。打つコマンドは全部載せています
3. 応用(7章〜)… AIを2つ以上使う人/自分の環境を育てたい人向け
おまけ(9章)… そのまま使える「ハーネス設計の指示書」。【 】を自分の題材に置き換えてコピーするだけで使えます

すぐ効く人=AIにWebアプリを作らせている人/ 効く人=AIの書いたコードをレビューしきれていない人/ いずれ効く人=これからAIに任せる範囲を広げたい人。
先に断っておくと、この記事は「どちらのAIが優秀か」を扱いません。 見るのは1点だけ、同じテストで、同じ緑が出るかです。 時間も行数も比べません。差が出た箇所は、能力の差ではなく環境の差として扱います。

0. なぜ「仕組み」の側に持たせるのか

手順の前に、ひとつだけ。AIに「注意してください」と書いても、守られないことがあります。 悪意でも能力不足でもなく、毎回まっさらな状態で来るので、前回の指摘を覚えていないだけです。

気をつける、ができない
図解:Routine Labo

この記事を書いている最中にも、それが起きました。完了条件に 「テストは修正しないこと」という1行を書き忘れたら、 AIはテストのほうを書き換えて緑にしてきたのです。 指示が足りないところは、素直にそこを埋めてきます。

だからやることは1つだけです。品質を、AIの記憶や注意力ではなく、環境と仕組みの側に持たせる。 AIを賢くするのではなく、AIの「周り」を整える —— これがハーネス(馬具)と呼ばれているものの中身です。

なぜ、いま必要になったのか

AIが何十分もひとりで走るようになったからです。 人が見ていない時間ができた。その時間に効くのは、 その場にいなくても働く仕組みだけです。

1. ハーネス=AIの周りに置く、4つの部品

各論に入る前に、全体像を先に置いておきます。今日つくるのは①②③で、 ④は最後にもう一度出てきます。

AIの周りに置く4つの部品
図解:Routine Labo
#部品何をするかこの記事のどこ
①受入条件何ができたら合格か、作らせる前に決める2〜3章
②検証ループ合否が自動で返る。赤を読んで直して、また回す7章
③権限とルール触っていい範囲・やってはいけないこと8章
④フィードバック出来を、作った本人以外が見る—

ここが今日いちばん大事なところです。 テストを書くのも、サンドボックスの設定をいじるのも、やっていることは同じです。 ぜんぶ AIの「周り」の仕組みを設計している。バラバラの小技ではありません。

4つに共通しているのは、「気をつける」ではなく「通れない」形にしていることです。 人の注意力に預けたものは、いつか抜けます。

2. 順番を、ひっくり返す

ふつうの流れはこうです。作らせる → 動かしてみる → たぶん大丈夫。 最後の判定を人間の目がやっているので、見落としがそのまま残ります。

今回はこうします。合格の条件を置く → 作らせる → 機械が合否を出す。 順番を入れ替えただけですが、決定的に違うのは合否を人が決めなくなることです。

順番をひっくり返す
図解:Routine Labo

まっさらなフォルダで最初に打つのは、アプリのコードではなくこれです。

mkdir -p ~/project/develop/"20260908 pixel-harness" && cd $_
npm init playwright@latest -- --quiet --browser=chromium --lang=ts

ひとつ注意。npm init playwright@latest はサンプルのテストも一緒に作ります。 そのままだと外部サイトへ通信するテストまで走ってしまうので、消しておきます。

rm -rf tests-examples tests/example.spec.ts

つづけて、アプリを置く app/ を作って、空のページだけ置いておきます (これが無いとサーバーが404を返して、テストが始まりません)。

mkdir -p app
echo '<!doctype html><meta charset="utf-8"><title>pixel</title>' > app/index.html

そして配信の設定。依存パッケージを足したくないので、Python の標準モジュールで app/ を配ります。 設定側にも testMatch を入れて、消し忘れても3本だけが走るようにしておきます。

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  testMatch: /pixel\.spec\.ts$/,
  use: { baseURL: 'http://localhost:5174' },
  webServer: {
    command: 'python3 -m http.server 5174 --directory app',
    url: 'http://localhost:5174',
    reuseExistingServer: true,
  },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});

3. 受入条件の中身 — この4つの名前が「仕様」になる

テストを書くというのは、実は名前を決めることでもあります。 下のテストは4つの data-testid を要求していて、AIはこの名前で作るしかありません。 日本語で「保存ボタンを付けてください」と書くより、はるかに強い指示になります。

名前何を指すか
pixel-grid16×16 のマス目全体
pixel-cellマス1つ(data-i で 0〜255)
color-swatch色パレットの1色(data-color)
pixel-save保存ボタン

これが受入条件の本体です。tests/pixel.spec.ts に置きます。 5本あります——「閉じて開き直しても残るか」の3本に加えて、 16個ごとに行が折り返しているか・8色とも違う色か・ドラッグでなぞって塗れるかも見ています (数を数えるだけだと、256個を1列に並べた実装でも通ってしまいます)。

import { test, expect, type Page, type BrowserContext } from '@playwright/test';

// 契約: pixel-grid / pixel-cell / color-swatch / pixel-save
const RED  = 'rgb(231, 76, 60)';
const BLUE = 'rgb(52, 152, 219)';
const WHITE = 'rgb(255, 255, 255)';

async function paint(page: Page, color: string, cells: number[]) {
  await page.locator(`[data-testid="color-swatch"][data-color="${color}"]`).click();
  for (const i of cells) {
    await page.locator(`[data-testid="pixel-cell"][data-i="${i}"]`).click();
  }
}

// ★★ここが受入条件の肝★★
//   同じタブの再読み込みでは、壊れた実装でも絵が残ってしまう(実測)。
//   さらに、元のタブを開いたままだと「新しいタブ」でも残ってしまう。
//   ユーザーが実際にやるのは「閉じて、あとで開き直す」。だからそれを再現する。
async function closeAndReopen(ctx: BrowserContext, page: Page) {
  await page.close();                 // ← 作った本人(元の文書)が居なくなる
  const fresh = await ctx.newPage();
  await fresh.goto('/');
  await fresh.waitForTimeout(400);
  return fresh;
}

test('塗った色が、閉じて開き直しても同じマスに残る', async ({ context }) => {
  const page = await context.newPage();
  await page.goto('/');
  await paint(page, '#e74c3c', [0, 17, 34]);
  await page.getByTestId('pixel-save').click();
  await page.waitForTimeout(300);

  const fresh = await closeAndReopen(context, page);
  for (const i of [0, 17, 34]) {
    await expect(fresh.locator(`[data-testid="pixel-cell"][data-i="${i}"]`))
      .toHaveCSS('background-color', RED);
  }
});

test('複数の色を使っても、全部そのまま残る', async ({ context }) => {
  const page = await context.newPage();
  await page.goto('/');
  await paint(page, '#e74c3c', [5, 6]);
  await paint(page, '#3498db', [20, 21]);
  await page.getByTestId('pixel-save').click();
  await page.waitForTimeout(300);

  const fresh = await closeAndReopen(context, page);
  await expect(fresh.locator('[data-testid="pixel-cell"][data-i="5"]')).toHaveCSS('background-color', RED);
  await expect(fresh.locator('[data-testid="pixel-cell"][data-i="21"]')).toHaveCSS('background-color', BLUE);
});

test('塗っていないマスは、白のままになっている', async ({ context }) => {
  const page = await context.newPage();
  await page.goto('/');
  await paint(page, '#e74c3c', [0]);
  await page.getByTestId('pixel-save').click();
  await page.waitForTimeout(300);

  const fresh = await closeAndReopen(context, page);
  await expect(fresh.locator('[data-testid="pixel-cell"][data-i="255"]')).toHaveCSS('background-color', WHITE);
});

test('マス目と色パレットが、仕様どおり並んでいる', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByTestId('pixel-grid')).toBeVisible();
  await expect(page.getByTestId('pixel-cell')).toHaveCount(256);   // 16×16
  await expect(page.getByTestId('color-swatch')).toHaveCount(8);
  await expect(page.getByTestId('pixel-save')).toBeVisible();

  // ★数だけ数えても足りない。「16個で1行」に並んでいるかまで見る
  //   (256個を1列に並べた実装でも、数えるだけなら通ってしまう)
  const box = async (i: number) =>
    (await page.locator(`[data-testid="pixel-cell"][data-i="${i}"]`).boundingBox())!;
  const [c0, c15, c16] = [await box(0), await box(15), await box(16)];
  expect(c15.y).toBeCloseTo(c0.y, 0);        // 0 と 15 は同じ行
  expect(c16.y).toBeGreaterThan(c0.y);       // 16 は次の行
  expect(c16.x).toBeCloseTo(c0.x, 0);        // 16 は行の先頭に戻る

  // ★色も「8個あるか」ではなく「8色とも違うか」を見る(赤・青・白×6 でも数は合う)
  const colors = await page.getByTestId('color-swatch').evaluateAll(
    els => els.map(e => (e as HTMLElement).dataset.color));
  expect(new Set(colors).size).toBe(8);
});

test('ドラッグでなぞると、通った道が続けて塗れる', async ({ page }) => {
  await page.goto('/');
  await page.locator('[data-testid="color-swatch"][data-color="#3498db"]').click();

  // 横に3マスなぞる(押したまま動かして離す)
  const cells = [100, 101, 102].map(i => page.locator(`[data-testid="pixel-cell"][data-i="${i}"]`));
  const first = await cells[0].boundingBox();
  await page.mouse.move(first!.x + first!.width / 2, first!.y + first!.height / 2);
  await page.mouse.down();
  for (const c of cells.slice(1)) {
    const b = await c.boundingBox();
    await page.mouse.move(b!.x + b!.width / 2, b!.y + b!.height / 2);
  }
  await page.mouse.up();

  for (const c of cells) await expect(c).toHaveCSS('background-color', BLUE);
});

この状態で npx playwright test を走らせると、3本とも赤になります。 当然です。app/ にはまだ何も無いので。 でもここが大事で、アプリのコードが0行の時点で、もう合否が出ている。 この赤が、これから作るものの仕様書です。

4. 日本語で渡すと、何が起きるか

「マス目を塗れるドット絵エディタを作って」と日本語で頼むと、AIはだいたい 「絵なんだから、画像として保存しよう」と考えます。 canvas に描いて、URL.createObjectURL() で保存する —— そしてその場ではちゃんと動きます。塗って、保存して、開き直せば絵が戻ってくる。

Browsers will release object URLs automatically when the document is unloaded.
日本語で渡すと、こうなる
出典:MDN Web Docs「blob: URLs」(2026-09-08 取得)
developer.mozilla.org / 図解:Routine Labo

5. ここが一番大事 — 浅いテストは、壊れた実装を通す

正直に書きます。ここで2回つまずきました

実際に走らせて、はじめて分かったことがあります。

確かめ方壊れた実装の判定
描いた直後に見る絵が出ている → 通る
同じタブで再読み込み通ってしまう
別のタブで開く(元のタブは開いたまま)通ってしまう
タブを閉じて、開き直す絵が消える → 落ちる

blob: URL は、それを作った画面が生きているあいだは使えてしまいます。 ユーザーが実際にやるのは「閉じて、あとで戻ってくる」。 テストもそれを再現しないと、意味がありません。

浅さには2段あります。

浅いテストは通してしまう
図解:Routine Labo

1段目:見えている ≠ 塗れている

await expect(cell).toHaveCSS('background-color', 'rgb(231, 76, 60)');

2段目:閉じてから、開き直す

await page.close();
const fresh = await ctx.newPage();
await fresh.goto('/');

テストがあるかどうかではなく、テストが何を、どうやって見ているかが全てです。

6. ここまで来たら、はじめて作らせる

渡す依頼文はこれです。2つのAIに、一字一句同じものを渡します。

書ける場所: このフォルダの中だけ。それ以外には書かない
触らないもの: 保存や履歴の操作/設定ファイルを開いて読む/頼んでいない変更や削除
迷ったとき: 止まらずに進めて、置いた前提を最後に書き残す
報告の形式: 何をしたか・変更したファイル・実行した検証コマンドと結果

app/ に、ドット絵エディタを作ってください。

要件:
- 16×16 のマス目を並べる
- 色のパレットから色を選べる(8色)
- マスをクリック、またはドラッグでなぞると、その色で塗れる
- 「保存」ボタンで、描いた絵を保存できる
- ブラウザを閉じて開き直しても、描いた絵がそのまま残っている
- 依存パッケージは追加しない。app/index.html だけで完結させる(素の HTML / CSS / JS)
- 保存先は localStorage を使う(受入テストがそれを前提にしている)

完了条件:
- `npx playwright test` が全部緑になること
- テストは tests/pixel.spec.ts に既にある。テストは修正しないこと。テストに合わせて実装すること

最後の1行が無いと、こうなる

「テストは修正しないこと」を書き忘れると、AIはテストのほうを書き換えて緑にしてきます。 笑い話のようですが、完了条件の書き方ひとつで結果が変わるという意味では、けっこう本質的な話です。

あとは走らせるだけです。AIが実装し、自分で npx playwright test を回し、 赤を読んで、直して、緑にする。ブラウザが実際に動くところを見たいときは --headed を付けます。

npx playwright test --headed
AIが、自分で直す
図解:Routine Labo

無人のブラウザが立ち上がり、色を選び、マスを塗り、保存して、閉じて、開き直す。 誰も触っていないのに全部動いて、最後に緑が出ます。 「動きました」は自己申告ですが、この緑は事実です。

7. 2つ目のAIを、同じゲートに通す

次は同じ依頼文・同じテストを Codex(GPT-6 Astra)へ渡します。 ただしその前に、非対話で走るAIには質問ができないという前提を押さえておく必要があります。

AIは聞き返せない
図解:Routine Labo
決めること具体
書ける場所作業ルートと、追加で許可したフォルダだけ。それ以外には一切書かない
触らないものコミットやブランチの操作/鍵・トークンを開いて読む/頼んでいない変更や削除
迷ったとき止まらずに進めて、置いた前提を書き残す
報告の形式決まった項目だけを返させる(status / changed_files / verify / notes)

やり方は簡単で、依頼文の先頭にこの4つを書いておくだけです。ただのテキストなので、 コピーして使えます。毎回書くのが面倒になったら、自分でスクリプトにまとめればいい —— 私はそうしていますが、最初は手で書いて十分です。

そのうえで、2つ目のAIに同じ依頼文を渡します。まず受入条件だけをコピーした別フォルダを作ります (node_modules は持っていきません。あとで出てくる「ネットワークの壁」を再現するためです)。

rsync -a --exclude node_modules --exclude 'app/index.html' \
  ~/project/develop/"20260908 pixel-harness"/ \
  ~/project/develop/"20260908 pixel-harness-codex"/
cd ~/project/develop/"20260908 pixel-harness-codex"
echo '<!doctype html><meta charset="utf-8"><title>pixel</title>' > app/index.html

# ★配信ポートを 5175 へ変える(これを忘れると、さっきのフォルダを検証してしまう)
sed -i '' 's/5174/5175/g' playwright.config.ts
ポートを変えるのを忘れると、こうなります。 さっきのフォルダのサーバー(5174)がまだ動いていると、 設定の reuseExistingServer がそれを再利用してしまい、 こちらのフォルダが空でもテストが通ってしまいます。私はここで一度ひっかかりました。

次に、渡す依頼文をファイルにしておきます(先頭に、さっきの4項目を書き足しておきます)。

cat > 依頼文.md <<'EOF'
書ける場所: このフォルダの中だけ。それ以外には書かない
触らないもの: 保存や履歴の操作/設定ファイルを開いて読む/頼んでいない変更や削除
迷ったとき: 止まらずに進めて、置いた前提を最後に書き残す
報告の形式: 何をしたか・変更したファイル・実行した検証コマンドと結果

app/ に、ドット絵エディタを作ってください。

要件:
- 16×16 のマス目を並べる
- 色のパレットから色を選べる(8色)
- マスをクリック、またはドラッグでなぞると、その色で塗れる
- 「保存」ボタンで、描いた絵を保存できる
- ブラウザを閉じて開き直しても、描いた絵がそのまま残っている
- 依存パッケージは追加しない。app/index.html だけで完結させる(素の HTML / CSS / JS)
- 保存先は localStorage を使う(受入テストがそれを前提にしている)

完了条件:
- `npx playwright test` が全部緑になること
- テストは tests/pixel.spec.ts に既にある。テストは修正しないこと。テストに合わせて実装すること
EOF

準備ができたら、実行します。

# ① まず既定のまま渡す(ネットワークの指定をしない)
cd ~/project/develop/"20260908 pixel-harness-codex"
codex exec --skip-git-repo-check -m gpt-6-astra \
  --sandbox workspace-write \
  "$(cat 依頼文.md)"
--skip-git-repo-check を忘れると、git 管理外のフォルダでは Not inside a trusted directory… と出て依頼を読む前に止まります。 デモ用のフォルダは git 管理外なので、これが要ります。

そして、実際にぶつかる壁

この状態で委譲すると、失敗して返ってきます。 Codex の既定のサンドボックスは workspace-write で、 作業フォルダには書けますがネットワークは既定で無効だからです。 npm install がネットに出られず、そこで止まります。

ネットは既定で切れている
出典:OpenAI「Agent approvals & security」(2026-09-08 取得)
developers.openai.com / 図解:Routine Labo

ここが今回いちばん見てほしいところです。人間なら「ネット使っていいですか」と聞く場面で、 非対話のAIは聞けない。だから質問ではなく失敗として返ってくる。 つまり、渡す側が先に決めておくしかありません。直し方は許可を1つ足すだけです。

# ② ネットワークを開けて、同じ依頼をもう一度(足すのはこの1行だけ)
codex exec --skip-git-repo-check -m gpt-6-astra \
  --sandbox workspace-write \
  -c sandbox_workspace_write.network_access=true \
  "$(cat 依頼文.md)"

もう一度走らせると、今度は最後まで進んで緑になります。 同じテストで、同じ緑が出ました。それだけです。 落ちた理由は能力ではなく環境で、環境は渡す側が決められます。

8. 今日組んだのは最小の1セット — 実際はこう育つ

ここまでが「1つのアプリを作らせるための最小構成」です。 参考までに、私が実際に毎日回している環境がどうなっているかを出しておきます。 作りこんだというより、事故るたびに1つずつ足していったら、こうなったという形です。

実際に回している形
図解:Routine Labo

① 置く場所は「時間」で決まる

いつ本数何をしているか
開くとき16最新を取る/作業の現在地を出す/残りのタスクを出す
書くとき2保存した瞬間に検査が走る
通すとき2条件を満たさないと、そもそも進めない
出すとき1外に出す前に、名前や個人情報が混ざっていないか読む
閉じるとき4自動で保存・記録・同期
人がいない間1310分ごとの同期、毎朝の自動収集

② 「通れない門」は3つ

正直に書くと、この記事を書いている最中にも2回止められました。 面倒に見えますが、止まってくれたほうが、あとで気づくより圧倒的に安いです。

ついでに、もう1つ正直に書いておきます。この門には穴もありました。 同じ日に3回、自動保存のほうが先に走ってしまい、門を通らずに記録が残っています。 仕組みは作って終わりではなく、穴が見つかるたびに塞ぐものです。

③ 配線が生きているかを、まとめて確かめる

仕組みを足していくと、今度は仕組み自体が壊れていないかが心配になります。 なので、まとめて確かめるコマンドを持っています(2本です。 最初は1本のつもりでしたが、調べたら片方は別コマンドでした)。

見るもの規模
配線ぜんぶ(参照先が実在するか・同期がずれていないか)10章
同時に動かしても壊れないか22項目
作業中に情報が溢れない配線になっているか28項目

④ そして、この記事自体も止められました

この台本も、止められた
図解:Routine Labo
この記事をつくる間の記録
別のAIに、この原稿を読ませた回数15回
突き返された2回(重い指摘 3件)
記録を止められた3回

この3件のうち1件が、この記事でお見せしているコマンドそのものでした。 引数が1つ足りず、そのまま公開していたら、真似した人の手元で動きませんでした。

私が気づいたのではありません。仕組みが止めました。 作った本人は、自分の穴が見えない —— それはAIでも人間でも同じです。

品質を、AIの注意力に預けない。環境と仕組みの側に持たせる —— やっていることは、この記事の受入条件とまったく同じです。規模が違うだけで。

9. おまけ:そのまま使える「ハーネス設計の指示書」

ここまでの内容を、自分のプロジェクトでそのまま使える形にまとめておきました。 【 】を自分の題材に置き換えて、上から順に貼るだけです。 今日の回で説明した範囲(①受入条件・②検証ループ・③権限とルール)に対応しています。

使い方

STEP 1 → STEP 2 の順に、別々に渡してください。 まとめて渡すと、AIは受入条件とアプリを同時に作ってしまい、 「条件を先に置く」という今日の肝が消えます。

STEP 0 毎回いちばん上に貼る4行(権限とルール)

書ける場所: このフォルダの中だけ。それ以外には書かない
触らないもの: 保存や履歴の操作/設定ファイルを開いて読む/頼んでいない変更や削除
迷ったとき: 止まらずに進めて、置いた前提を最後に書き残す
報告の形式: 何をしたか・変更したファイル・実行した検証コマンドと結果

STEP 1 まず受入条件だけを作らせる(アプリのコードはまだ書かせない)

これから【 作りたいもの 】を作ります。
ただし今回は、アプリのコードは1行も書かないでください。
先に「何ができたら合格か」だけを、実行できるテストとして書いてください。

書き方のルール:
- 画面の要素には data-testid を付ける前提で書く。テストからはその名前だけで触る
- テスト名は日本語で「何ができたら合格か」がそのまま読める文にする
- 次の3種類を必ず入れる:
  1. 見た目や構造が仕様どおりか(数だけでなく、並び方や重複も見る)
  2. ユーザーの主な操作が実際にできるか
  3. 【 いったん閉じて開き直しても / 再起動しても 】、状態が残っているか

満たしてほしいこと:
- 「要素が存在する」だけで通らないようにする。中身まで確かめる
- 一度きりの確認ではなく、本当にユーザーがやる手順を再現する
- テストの前提(保存先など)が決まっているなら、コメントに明記する

できたら、テストを実行して「全部赤になること」を報告してください。
アプリがまだ無いので、赤になるのが正しい状態です。

STEP 2 受入条件ができてから、作らせる

【 STEP 0 の4行をここに貼る 】

【 作りたいもの 】を作ってください。

要件:
- 【 箇条書きで。テストと矛盾しないように書く 】
- 依存パッケージは追加しない(増やすなら理由を報告に書く)
- 【 保存先・使う技術の指定があればここに書く 】

完了条件:
- 受入テストが全部緑になること
- テストは既にある。テストは修正しないこと。テストに合わせて実装すること
- 赤が出たら、出力を読んで自分で直してから、もう一度実行すること
「テストは修正しないこと」を消さないでください。 この1行が無いと、AIはテストのほうを書き換えて緑にしてきます。 実際に私も一度やられました。

STEP 3 2つ目のAIにも同じものを渡すとき

別のフォルダにコピーして、同じ STEP 2 をそのまま渡すだけです。 ただし2つ注意があります。

この指示書が効かない場面

1回きりの作業・判断が要ること・人にしか決められないことには効きません。 効くのは「繰り返す」「機械で確かめられる」「人の着手を待っている」が 揃うところだけです。全部を仕組みにしようとすると、だいたい失敗します。

10. 今日やったのは、3つのうちの1つ

最後に、この記事が何の一部なのかを置いておきます。私は普段、3つをセットで考えています。

3つで1セット
図解:Routine Labo
何を決めるかこの記事
ハーネスAIの周りの仕組み(受入条件・検証ループ・権限とルール)これをやった
ループ回し方。実行 → 観測 → 修正。1回直して終わりにしない触れるだけ
グラフ置き場所。同じことを2か所に書かない触れるだけ

ループで大事なのは、直したら効果を確かめる手段もセットで作ること。 そして作った本人に採点させないこと。 この記事の「検証ループ」も「フィードバック」も、元をたどればこの考え方です。

グラフは、同じ設定をあちこちに手で書くと必ずどこかでズレるから、 正本を1つ決めて、ほかは参照にするという話です。 設定ファイルをどこに置くか、という次回の話は、まさにこれにあたります。

道具をそろえるだけでは足りません。回し方と、置き場所まで決めて、やっと仕組みになります。

11. 緑になった。でも、テストは「動くか」しか見ていない

2つとも通りました。環境としては成功です。 ただ、ここで満足すると足をすくわれます。

テストは動くかしか見ない
図解:Routine Labo

テストが保証したのは「動くこと」だけです。 読みやすいか、危ない書き方をしていないか、境界の条件が抜けていないか、設計として妥当か—— そこは1ミリも見ていません。

では、こうなります

この2つが書いたコードの「出来」は、誰が採点するのか?

答えは、別のモデルに採点させることです。 作った本人に、合否を出させない。

——ただ、それを「仕組み」にするには、設定ファイルに何をどう書くかが要ります。 その中身は、次回。

まとめ