Un agente, tres niveles de confianza
Un modo de permiso decide lo que un agente puede hacer sin preguntar. En modo plan solo lee. En modo ask pregunta antes de cambiar nada. En modo auto trabaja solo dentro de unos límites.
El mismo agente es seguro de dejar ejecutándose en un modo y peligroso en otro. Por eso el modo se elige en cada ejecución.
Al final de esta lección
Nota
Puedo ejecutar el mismo agente en modos ask, plan y auto, y decir qué deja pasar cada uno y qué pregunta siempre.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Modo | un ajuste que decide qué se ejecuta sin preguntar |
| Modo plan | un modo que solo lee |
| Riesgo | cuánto daño puede hacer una acción |
| Lista permitida | comandos aprobados de antemano |
| Ruta protegida | un archivo que siempre pide aprobación |
| Aprobación | el sí o el no de la persona a una acción |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un modo es un ajuste, no un prompt
Podrías escribir «nunca cambies archivos» en el prompt. Un modelo puede hacerlo igualmente, porque un prompt es una petición.
Un modo es código en el arnés. Cada acción pasa por una función antes de ejecutarse. En modo plan esa función rechaza una escritura, diga lo que diga el modelo. El modelo no ve ninguna forma de esquivarla.
Las reglas se comprueban en orden
Da a cada acción un riesgo: read, write, run o destructive. Luego decide por modo, con las reglas en un orden fijo. El modo plan va primero, porque rechaza más que ningún otro.
Después, una ruta protegida o una acción destructiva pregunta siempre. Solo entonces un modo relaja algo. Una lista permitida deja que el modo auto ejecute comandos con nombre sin preguntar.
Qué hace cada modo con cada riesgo
| Riesgo | Plan | Ask | Auto |
|---|---|---|---|
| Lectura | Permitir | Permitir | Permitir |
| Escritura | Denegar | Preguntar | Permitir |
| Ejecución | Denegar | Preguntar | Permitir si está en la lista |
| Destructiva | Denegar | Preguntar | Preguntar |
| Ruta protegida | Denegar | Preguntar | Preguntar |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente pide aprobación para cada acción. Entonces la gente pulsa sí sin leer. Pregunta solo donde importa: rutas protegidas, acciones destructivas y comandos fuera de la lista.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el ayudante del modelo, parte 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(\"estado \" + 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(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el revisor de modos, parte 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 sitio\" },' ' { tool: \"delete_file\", risk: \"destructive\", target: \"src/viejo.py\" },' ' { tool: \"read_file\", risk: \"read\", target: \".env\" },' '];' > modes3.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el revisor de modos, parte 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"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el revisor de modos, parte 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 = \"Un agente quiere borrar src/viejo.py. Escribe una frase corta que pida a la persona que lo apruebe.\";' ' const reply = await ask([{ role: \"user\", content: q }], \"El agente quiere borrar src/viejo.py. ¿Lo permites?\", 40);' ' console.log(\"pregunta mostrada: \" + reply.text);' '}' 'main();' >> modes3.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta las acciones en tres modos
docker exec -w /home/node/project agent node modes3.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Las decisiones no necesitan modelo
Las seis líneas salen solo de la función de decisión, así que son iguales para cada estudiante. Luego el Run hace una llamada corta, para redactar la pregunta que vería una persona antes de un borrado.
Sin modelo imprime una muestra grabada para esa sola línea.
Una salida posible
| Línea | Qué muestra |
|---|---|
| read_file src/app.py -> ask:allow plan:allow auto:allow | Una lectura simple pasa en todas partes |
| write_file src/app.py -> ask:ask plan:deny auto:allow | Solo auto deja pasar una escritura |
| run pytest -> ask:ask plan:deny auto:allow | Está en la lista permitida |
| run curl sitio -> ask:ask plan:deny auto:ask | Fuera de la lista, auto pregunta |
| delete_file src/viejo.py -> ask:ask plan:deny auto:ask | Un borrado siempre pregunta |
| read_file .env -> ask:ask plan:deny auto:ask | Una ruta protegida siempre pregunta |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.