Saltar al contenido principal

12 · Agentes y AI Engineering

Gobernanza y human-in-the-loop

Políticas como código, deny-ask-allow, clasificación de riesgo y aprobaciones que una persona realmente puede dar.

13 minCaso de estudio: docket (se abre en una ventana nueva)

Un que solo conversa no necesita gobernanza. Uno que ejecuta comandos, edita archivos o hace deploys sí. En cuanto el modelo puede pedir acciones, alguien tiene que decidir cuáles ocurren, bajo qué reglas y con qué evidencia. La intuición habitual es escribir esas reglas en un AGENTS.md y confiar en que el modelo las siga.

Esta lección explica por qué eso no alcanza, cómo se ve una política aplicada por código y cómo diseñar aprobaciones humanas que funcionen de verdad: que una persona pueda darlas, que no se aprueben solas y que dejen un registro útil.

Instrucción frente a política

Una instrucción ("no hagas push a main") es texto que el modelo lee; funciona si la recuerda, la interpreta bien y la prioriza frente a otro texto, incluido el de un atacante. Una política es código que evalúa cada llamada a una herramienta antes de ejecutarla, lo quiera el modelo o no.

Instrucción Política
Quién la aplica el modelo el harness
Cuándo cuando el modelo lo decide en cada tool call
Ante texto adversario puede ser persuadida no lee argumentos
Si falla en silencio deja registro
Se puede testear no de forma determinista sí, con tests unitarios

Las instrucciones mejoran el caso normal; la política acota el peor caso. Se necesitan las dos. La tesis de fondo: el modelo no debe ser la frontera de seguridad.

Deny, ask, allow: gana lo más restrictivo

Un motor de políticas produce un veredicto por llamada. El patrón más simple y robusto tiene tres salidas (deny, ask, allow) y una regla de combinación: si varias reglas coinciden, gana la más restrictiva. Así, agregar una regla nunca puede abrir un permiso que otra cerró.

Una política de docket es un JSON pequeño. Esta es la plantilla high-risk-deploy.json (patrón abreviado):

{
  "id": "high-risk-deploy",
  "applies_to": ["*"],
  "hook": "pre_tool_call",
  "match": {"type": "regex", "pattern": "kubectl\\s+(apply|...)"},
  "action": "require_approval",
  "message": "Production deployment always requires approval..."
}

El significado de una acción depende de cuándo se evalúa: pedir aprobación solo tiene sentido antes del efecto. Por eso, en pre_output, docket trata require_approval como una advertencia.

Riesgo y roles: quitar antes que vigilar

Pedir aprobación para todo es inviable, así que primero hay que clasificar por riesgo: lectura (riesgo de filtración), mutación local (normalmente reversible) y alto riesgo (efectos externos o irreversibles). Con herramientas genéricas como bash, el riesgo está en los argumentos, no en el nombre; el detalle del clasificador está en Tool calling y harness.

La segunda idea es distinguir permiso de capacidad:

  • Con un permiso, el agente tiene la herramienta y un control verifica cada uso. Si el control tiene un hueco, la acción ocurre.
  • Con una capacidad retirada, el agente no tiene la herramienta. No hay nada que verificar.

La regla práctica: un rol que estructuralmente no puede hacer algo es más seguro que uno al que se le dijo que no lo haga. Cuando no puedes quitar la herramienta, verifica el permiso en un único punto de enforcement.

Anatomía de una aprobación

Para lo que ninguna regla puede decidir sola (¿este push a producción es el deploy planificado?), se pregunta a una persona. Un diseño serio define qué la dispara, qué ve el humano, por qué canal llega, qué pasa si nadie responde o si se rechaza, y quién decidió.

agente pide tool call
        |
  política + clasificador --deny--> rechazado ----+
        |                                         |
       ask --> registro pending --> humano        |
        |           |            |                |
      allow     granted     denied / timeout -----+
        |           |                             |
        v           v                             v
     ejecución <----+                        auditoría

El canal también es superficie de ataque. El bot de Telegram de docket acepta solo cuatro verbos (/approve, /deny, /status, /delegate) y solo desde chats vinculados; los demás se rechazan y se auditan como telegram.unauthorized. La API HTTP exige un Bearer, pero el llamador declara el campo channel y src/docket/serve.py solo verifica que sea un valor válido: la etiqueta de canal es una declaración, no una prueba.

Cuando nadie responde

Un timeout que aprueba convierte la aprobación en un retraso: basta con disparar la acción cuando nadie mira. El comportamiento por defecto de un control que falla tiene que ser el seguro.

Esa última distinción es valiosa: "nadie fue consultado", "alguien dijo que no" y "nadie respondió a tiempo" son tres hechos distintos. El primero habla de la configuración, el segundo de la acción, el tercero de la disponibilidad del equipo.

Para CTOs

  • Decide qué acciones son in the loop (aprobación previa), on the loop (supervisión con capacidad de detener) y out of the loop (revisión posterior). La autonomía se gana con evidencia: trazas, , historial de aprobaciones.
  • Exige reglas en archivos versionados y testeables, no en .
  • Mide la tasa de aprobaciones y su tiempo de respuesta: si casi todo se aprueba en segundos, son decorativas. Y define qué pasa a las 3 de la madrugada: la respuesta correcta es "no ocurre".

Para llevar

  • Una instrucción mejora el caso normal; una política aplicada por código acota el peor caso. El modelo no es la frontera de seguridad.
  • Combina veredictos con "gana lo más restrictivo" (deny > ask > allow) y valida las políticas antes de desplegarlas: el fail-closed se decide camino por camino.
  • Quitar una herramienta a un rol es más fuerte que prohibirle usarla.
  • Una aprobación útil es atómica, redactada, auditada por canal y se deniega cuando nadie responde.
  • Pide aprobación solo donde el juicio humano agrega información; si no, produces fatiga y evidencia vacía.

Comprueba lo aprendido

¿Por qué un rol sin la herramienta `write` es más seguro que uno con la instrucción "no escribas archivos"?

Porque no depende de que el modelo obedezca ni de que un control verifique cada uso: la herramienta no está en el registry, así que la llamada se rechaza como desconocida. No hay nada que un texto persuasivo pueda desbloquear.

¿Qué problema tiene un timeout de aprobación que aprueba automáticamente?

Convierte el control en un simple retraso: cualquier acción se ejecuta si nadie mira a tiempo, y un atacante puede elegir ese momento. El timeout debe denegar.

Una política con JSON inválido se ignora y el resto sigue. ¿Qué decisión de diseño es y cómo se mitiga?

Es un camino fail-open elegido para que un typo no detenga toda la flota. Se mitiga validando y probando las políticas antes de desplegarlas y alertando cuando una no carga.