クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

小さなエージェントハーネスの作り方

このレッスンのあとで: コンテキスト、ツール、モード、分離、元に戻す仕組みを一つのハーネスにまとめ、実行して巻き戻せる。

75分のレッスンコマンド9件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

部品を一つのループにまとめる

エージェントハーネスは、モデルを包むプログラムです。ウィンドウを作り、ツールを実行し、モードを適用し、変更を記録します。モデルは、一つの段階を提案するだけです。何が起きるかを決めるのは、ハーネスです。

このレッスンでは、前の五つのレッスンを、実行して巻き戻せる一つのループにまとめます。

このレッスンのゴール

メモ

コンテキスト、ツール、モード、分離、元に戻す仕組みを一つのハーネスにまとめ、実行して巻き戻せる。

キーワード

ことばいみ
ハーネスモデルを包んで、エージェントを動かすプログラム
ループ段階を頼み、判断し、実行し、記録して、繰り返すこと
台本のモデルモデルの代わりをする、決まった段階のリスト
経過の記録各段階とその結果の記録
ウィンドウ次にモデルへ送る、最近の結果
巻き戻し選んだ段階より後の変更を、すべて元に戻すこと

学生が入力するセルは、このプレビューでは空白です。

ループを、段階ごとに見る

毎回、ハーネスはモデルに段階を一つ頼みます。モードで判断します。許可、確認、拒否のどれかです。確認が必要なら、利用者が答えます。段階が許可されたら、ハーネスは置き換えるものを記録してから、実行します。

結果はウィンドウに入り、ウィンドウは最近のものだけを残します。そのあと、モデルが終わりと言うまで、ループが繰り返されます。

前のレッスンが加えるもの

レッスンループの中の位置
コンテキストウィンドウは最近の結果だけを残す
ツール各段階が、狭いツールの名前を持つ
モード一つの関数が、許可、確認、拒否を決める
分離副次的な作業が、短い結果をウィンドウに返す
元に戻す変更の前に、記録を一つ書く

学生が入力するセルは、このプレビューでは空白です。

よくある間違い

注意

多くの人は、拒否された段階を消してしまいます。するとモデルは、拒否されたことを学べず、また頼みます。拒否を結果として記録し、次のウィンドウに見せます。

モデル用の補助を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答に含まれるトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

モデル用の補助を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const d = await res.json();' '    return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const RISK = { read_file: \"read\", write_file: \"write\", delete_file: \"destructive\", run: \"run\" };' 'const script = [' '  { tool: \"read_file\", path: \"app.py\" },' '  { tool: \"write_file\", path: \"app.py\", text: \"x = 2\" },' '  { tool: \"run\", path: \"pytest\" },' '  { tool: \"delete_file\", path: \"notes.txt\" },' '  { tool: \"write_file\", path: \".env\", text: \"KEY=1\" },' '  { tool: \"done\" },' '];' 'const files = { \"app.py\": \"x = 1\", \"notes.txt\": \"あとで\" };' 'const log = [];' > harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function decide(mode, risk, path) {' '  if (mode === \"plan\") return risk === \"read\" ? \"allow\" : \"deny\";' '  if (path.startsWith(\".env\") || risk === \"destructive\") return \"ask\";' '  if (mode === \"auto\" && (risk === \"read\" || risk === \"write\")) return \"allow\";' '  if (mode === \"ask\" && risk === \"read\") return \"allow\";' '  return \"ask\";' '}' >> harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function act(s, n) {' '  if (s.tool === \"read_file\") return s.path in files ? files[s.path] : \"なし\";' '  if (s.tool === \"run\") return \"実行 \" + s.path;' '  log.push({ step: n, path: s.path, before: s.path in files ? files[s.path] : null });' '  if (s.tool === \"write_file\") { files[s.path] = s.text; return \"書き込み \" + s.path; }' '  delete files[s.path];' '  return \"削除 \" + s.path;' '}' >> harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function runAll(mode, approvals) {' '  const out = [];' '  for (let i = 0; i < script.length; i++) {' '    const s = script[i];' '    if (s.tool === \"done\") break;' '    const v = decide(mode, RISK[s.tool], s.path);' '    let outcome = v === \"allow\" ? \"ok\" : \"拒否\";' '    if (v === \"ask\") outcome = approvals.shift() === true ? \"承認\" : \"却下\";' '    const good = outcome === \"ok\" || outcome === \"承認\";' '    out.push({ step: i + 1, tool: s.tool, path: s.path, outcome: outcome, result: good ? act(s, i + 1) : outcome });' '  }' '  return out;' '}' >> harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function undoTo(step) {' '  for (const e of log.slice().reverse()) {' '    if (e.step <= step) continue;' '    if (e.before === null) delete files[e.path];' '    else files[e.path] = e.before;' '  }' '}' >> harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを書く(6)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const out = runAll(\"auto\", [true, false]);' '  for (const o of out) console.log(o.step + \" \" + o.tool + \" \" + o.path + \" \" + o.outcome + \" -> \" + o.result);' '  console.log(\"ウィンドウ \" + JSON.stringify(out.slice(-3).map(function (o) { return o.result; })));' '  console.log(\"ファイル \" + JSON.stringify(files));' '  undoTo(1);' '  console.log(\"1 まで戻した後 \" + JSON.stringify(files));' '  const text = out.map(function (o) { return o.tool + \" \" + o.outcome; }).join(\"、\");' '  const reply = await ask([{ role: \"user\", content: \"次の操作を行った実行について、コミットメッセージを一行で書いてください: \" + text }], \"app.py のかごの合計を修正する\", 60);' '  console.log(\"メッセージ: \" + reply.text);' '}' 'main();' >> harness6.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ハーネスを実行して巻き戻す

コマンド
docker exec -w /home/node/project agent node harness6.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

モデルは台本

段階は決まったリストから来るので、だれが実行しても同じ実行になります。本物のハーネスは、各段階をモデルに頼みます。短い呼び出しは、実行内容からコミットメッセージを書くだけです。

モデルがなければ、その行は記録済みサンプルを表示します。

出力の一例

行示すこと
1 read_file app.py ok -> x = 1auto モードで、読み取りは通る
3 run pytest 承認 -> 実行 pytest一つ目の承認は「はい」だった
4 delete_file notes.txt 却下 -> 却下二つ目の承認は「いいえ」だった
5 write_file .env 却下 -> 却下承認が残っておらず、「いいえ」になった
ファイル {"app.py":"x = 2","notes.txt":"あとで"}書き込みだけが何かを変えた
1 まで戻した後 {"app.py":"x = 1",...}巻き戻しで、書き込みが元に戻った

学生が入力するセルは、このプレビューでは空白です。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。