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

無料レッスン

コーディングエージェントの権限モード

このレッスンのあとで: plan、ask、auto の各モードで、エージェントが確認なしでしてよい操作を言える。

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

一つのエージェントに、三つの信頼の段階

権限モードは、エージェントが確認なしでしてよいことを決めます。plan モードでは、読むだけです。ask モードでは、何かを変える前に確認します。auto モードでは、限られた範囲で一人で動きます。

同じエージェントでも、あるモードなら放っておけて、別のモードでは危険です。だからモードは、実行ごとに選びます。

このレッスンのゴール

メモ

plan、ask、auto の各モードで、エージェントが確認なしでしてよい操作を言える。

キーワード

ことばいみ
モード何を確認なしで実行するかを決める設定
plan モード読むだけのモード
リスク操作がどれだけ害を与えうるか
許可リスト前もって承認したコマンド
保護されたパス常に承認が必要なファイル
承認一つの操作に対する、利用者の「はい」か「いいえ」

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

モードは設定であり、プロンプトではない

プロンプトに「ファイルを変えるな」と書くこともできます。それでもモデルが変えるかもしれません。プロンプトはお願いだからです。

モードは、ハーネスの中のコードです。すべての操作は、実行の前に一つの関数を通ります。plan モードでは、モデルが何を言っても、その関数が書き込みを拒否します。モデルには、迂回する道が見えません。

規則は順番に調べる

各操作にリスクを付けます。read、write、run、destructive のどれかです。そのうえでモードごとに判断し、規則は決まった順番に並べます。plan モードが最初です。ほかのどのモードより多く拒否するからです。

次に、保護されたパスと destructive の操作は、常に確認します。そのあとで初めて、モードが何かを緩めます。許可リストがあると、auto モードは名前のあるコマンドを確認なしで実行できます。

各モードがリスクごとにすること

リスクplanaskauto
read許可許可許可
write拒否確認許可
run拒否確認リストにあれば許可
destructive拒否確認確認
保護されたパス拒否確認確認

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

よくある間違い

注意

多くの人は、すべての操作で承認を求めます。すると人は、読まずに「はい」を押します。確認するのは、大事な場面だけにします。保護されたパス、destructive の操作、リストにないコマンドです。

モデル用の補助を書く(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 ALLOWED = [\"pytest\"];' 'const actions = [' '  { tool: \"read_file\", risk: \"read\", target: \"src/app.py\" },' '  { tool: \"write_file\", risk: \"write\", target: \"src/app.py\" },' '  { tool: \"run\", risk: \"run\", target: \"pytest\" },' '  { tool: \"run\", risk: \"run\", target: \"curl site\" },' '  { tool: \"delete_file\", risk: \"destructive\", target: \"src/old.py\" },' '  { tool: \"read_file\", risk: \"read\", target: \".env\" },' '];' > modes3.js"

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

モードの判定を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function protectedPath(t) {' '  return t === \".env\" || t.startsWith(\".env.\") || t.startsWith(\".git/\") || t.toLowerCase().includes(\"secret\");' '}' 'function decide(mode, a) {' '  if (mode === \"plan\") return a.risk === \"read\" && protectedPath(a.target) === false ? \"allow\" : \"deny\";' '  if (protectedPath(a.target) || a.risk === \"destructive\") return \"ask\";' '  if (mode === \"ask\") return a.risk === \"read\" ? \"allow\" : \"ask\";' '  if (a.risk === \"run\") return ALLOWED.includes(a.target) ? \"allow\" : \"ask\";' '  return \"allow\";' '}' >> modes3.js"

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

モードの判定を書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  for (const a of actions) {' '    const row = [\"ask\", \"plan\", \"auto\"].map(function (m) { return m + \":\" + decide(m, a); });' '    console.log(a.tool + \" \" + a.target + \" -> \" + row.join(\" \"));' '  }' '  const q = \"エージェントが src/old.py を削除しようとしています。利用者に承認を求める短い一文を書いてください。\";' '  const reply = await ask([{ role: \"user\", content: q }], \"エージェントが src/old.py を削除しようとしています。許可しますか。\", 60);' '  console.log(\"表示する質問: \" + reply.text);' '}' 'main();' >> modes3.js"

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

三つのモードで操作を実行する

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

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

判断にモデルは要らない

六行は判断の関数だけから出るので、だれが実行しても同じです。そのあと Run は短い呼び出しを一回行い、削除の前に利用者が目にする質問を文章にします。

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

出力の一例

行示すこと
read_file src/app.py -> ask:allow plan:allow auto:allow単純な読み取りはどこでも通る
write_file src/app.py -> ask:ask plan:deny auto:allow書き込みを通すのは auto だけ
run pytest -> ask:ask plan:deny auto:allow許可リストにある
run curl site -> ask:ask plan:deny auto:askリストにないので、auto も確認する
delete_file src/old.py -> ask:ask plan:deny auto:ask削除は常に確認する
read_file .env -> ask:ask plan:deny auto:ask保護されたパスは常に確認する

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

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

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