Diseño de agentes human-in-the-loop: qué pasos necesitan aprobación humana

"La documentación human-in-the-loop del OpenAI Agents SDK describe tools que requieren approval, interruptions del run y reanudación con RunState tras approve o reject."
El borrador de un mensaje de Feishu ya está listo: título, cuerpo y enlace adjunto. Solo falta enviarlo. Pero el agente se detiene antes de send_message y espera tu confirmación.
Un correo puede generarse automáticamente, pero antes de enviarlo a un cliente debe mostrar destinatario, asunto, resumen del cuerpo y adjuntos. Un formulario CMS puede rellenarse solo, pero submit_form queda bloqueado por policy y espera owner approve. Parece un botón de confirmar, pero la frontera real es estado de ejecución. El RunState del agente se guarda y la ejecución se reanuda solo después de una decisión humana. Dónde pausar, quién aprueba y qué pasa ante reject o timeout no es un detalle de frontend: es parte de la seguridad del sistema de tools.
Esta guía da una matriz de riesgo, una checklist de puntos de aprobación, un mecanismo de pausa/reanudación y una plantilla de auditoría. La idea es pasar de “poner un botón” a un estado serializable, reanudable y auditable.
Matriz de riesgo: decidir qué acciones necesitan aprobación
No todo necesita aprobación. Las lecturas pueden correr solas; borrar y pagar deben esperar a una persona. Empieza con cinco dimensiones:
| Dimensión | Criterio | Ejemplos de acciones |
|---|---|---|
| Impacto externo | Toca sistemas o usuarios externos | Enviar correo, enviar formulario, llamar API externa, escribir en Feishu/Slack |
| Reversibilidad | Si la acción puede deshacerse | Borrar un registro (irreversible), guardar borrador (reversible), pago (compensación parcial), enviar mensaje (irreversible) |
| Sensibilidad de datos | Nivel de datos implicados | Consultar datos públicos, modificar registros internos, exportar privacidad de usuarios, leer config de producción |
| Umbral de dinero/permisos | Implica dinero o cambios de permisos | Pago, transferencia, reembolso, cambio de permisos, operación masiva, borrar datos de usuarios |
| Nivel de autonomía | Automatización permitida | Consulta read-only (automática), borrador (automático), mensaje saliente (confirmación), borrar/pagar (approval) |
Esta matriz encaja con OWASP LLM06: funcionalidad excesiva, permisos excesivos y autonomía excesiva. Úsala tal cual o ajusta umbrales al negocio.
Impacto externo: todo lo que llegue a otra persona o sistema merece atención. Un correo enviado no vuelve. Un formulario puede activar un pedido. Una API externa puede cambiar datos ajenos.
Reversibilidad: borrar un registro es irreversible; un borrador se edita; un pago quizá requiera reembolso o compensación.
Sensibilidad de datos: lo público puede leerse con libertad; lo interno necesita control de escritura; privacidad y configuración de producción deben pedir aprobación.
Umbral de dinero/permisos: dinero y permisos deben detenerse. Pagos, transferencias, reembolsos, cambios de permisos y operaciones masivas son puntos de alto riesgo.
Nivel de autonomía: las lecturas pueden ser automáticas. Los borradores también. Las salidas externas necesitan confirmación; borrar y pagar necesitan approval.
La matriz no es fija. Un mensaje interno de Feishu puede bajar a confirmación; un pago real debe seguir con approval y revisión de dos personas.
Tres escenarios reales de clasificación
Caso 1: borrador de mensaje Feishu
Escribir un borrador de Feishu es automático (L0). Queda en borradores, no se envía, es reversible y no tiene impacto externo. Pero llamar send_message a un cliente necesita approval (L2). Una vez enviado, el mensaje es irreversible, llega fuera y puede contener información sensible. Confirmar antes de MCP tools/call encaja aquí.
Caso 2: envío de correo
Generar contenido de correo es automático (L0). Todavía es texto. Enviar mediante API necesita approval mostrando destinatario, asunto y adjuntos (L2). La UI debe mostrar evidencia y resumen, no solo “confirmar envío”.
Caso 3: envío de formulario CMS
Rellenar el formulario es automático (L0). Aún no se envió. Llamar la API CMS para enviar debe bloquearse y esperar owner approve (L2). Puede ser un guardrail como “importe supera umbral” o una policy fija.
Caso 4: borrar base de datos de producción
Consultar producción es automático (L0). Es lectura. Borrar en producción necesita aprobación humana fuerte + auditoría de backup (L3). Es irreversible, sensible y afecta usuarios. Debe limitarse por Policy, no solo por Guardrail.
La lección: en una misma tarea, cada paso tiene riesgo distinto. El borrador es automático, enviar pide approval, borrar producción pide revisión de dos personas. Clasifica acciones concretas.
Checklist de puntos de aprobación: bajar al tipo de acción
Con la matriz, define niveles:
L0 automático: consultas de base, búsqueda vectorial, lectura de configuración; guardar borrador o generar preview. Sin impacto externo, reversible, sin datos sensibles.
L1 confirmación: mensajes o datos salientes como correo, formulario, API externa; lecturas masivas como exportación. Hay impacto externo, pero controlable.
L2 approval: borrar, cambiar permisos, borrar en masa; escribir en Feishu, Slack o CRM. Son acciones irreversibles o de impacto alto.
L3 strong approval + revisión doble: pagos, reembolsos, exportar privacidad, cambiar config de producción, borrar base de producción. Dinero o datos sensibles.
La lista coincide con needs_approval en OpenAI Agents SDK y con seguridad MCP. MCP espera tool calls visibles, rechazables y confirmados para acciones sensibles.
Puedes ajustar:
Si Feishu es solo notificación interna, L1 puede bastar.
Si borrar toca datos de usuarios, conserva L2.
Si un pago es crítico, sube a L3 con razón obligatoria.
La lista cambia con el negocio. Si la regla cambia, “enviar mensaje” puede salir del approval.
Máquina de estado del flujo de aprobación: pausar, guardar, reanudar
La aprobación no es un pop-up. Es un run pausado. Cuando un tool call necesita approval, se guarda RunState y se reanuda tras la decisión.
Diagrama de transición de estado
El flujo es:
request -> pending -> approved/rejected/timeout -> resume/abort/compensate
request: el tool call crea la solicitud; RunState contiene tool, argumentos y contexto.
pending: el run espera decisión; el estado se guarda en checkpoint y se vincula a thread_id.
approved: se aprueba; el run se reanuda desde checkpoint y llama al tool.
rejected: se rechaza; va a abort o convert to draft.
timeout: expira; escala o auto-reject.
resume/abort/compensate: continuar, detener o compensar.
Modos de reanudación
approve: continuar, llamar el tool y seguir.
reject: detener o convertir en borrador, sin llamar el tool.
edit: modificar parámetros, por ejemplo destinatario o contenido, y volver a aprobar.
Checkpoint y thread state son la base técnica. El artículo publicado sobre LangGraph checkpoint/thread state explica cómo guardar el punto de pausa y volver a él.
Ejemplo de código OpenAI Agents SDK HITL
Este ejemplo muestra el flujo de aprobación en OpenAI Agents SDK. La API puede cambiar; revisa la documentación oficial antes de producción:
from agents import Agent, Runner, function_tool
@function_tool(needs_approval=True)
def send_email(to: str, subject: str, body: str) -> str:
return send_email_handler(to=to, subject=subject, body=body)
agent = Agent(
name="EmailAgent",
tools=[send_email],
instructions="Redactar el correo y esperar aprobación antes de enviarlo",
)
result = Runner.run_sync(agent, "Escribe un aviso de reembolso para el cliente")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
print(f"Tool pendiente de aprobación: {interruption.tool_name}")
print(f"Argumentos: {interruption.arguments}")
decision = show_approval_ui(interruption)
if decision == "approve":
state.approve(interruption)
elif decision == "reject":
state.reject(interruption)
result = Runner.run_sync(agent, state)
Puntos clave:
needs_approval=True marca el tool.
interruptions contiene tool calls pendientes.
result.to_state() crea un RunState serializable.
state.approve() o state.reject() registra la decisión.
Runner.run_sync(agent, state) reanuda desde la pausa.
Los nombres pueden cambiar después de 2026-07; verifica la documentación oficial.
Ejemplo de código LangGraph interrupt/resume
Este ejemplo usa interrupt y Command(resume=...):
from langgraph.graph import StateGraph, MessagesState
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
def send_email_node(state: MessagesState):
approved = interrupt({
"action": "send_email",
"summary": state["email_summary"],
})
if approved != "approved":
return {"messages": ["El envío del correo fue rechazado; el mensaje se guardó como borrador"]}
email_result = send_email(state["email_params"])
return {"messages": [email_result]}
graph = StateGraph(MessagesState)
graph.add_node("send_email", send_email_node)
graph.add_edge("draft_email", "send_email")
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
thread_id = "thread_123"
config = {"configurable": {"thread_id": thread_id}}
result = app.invoke(
{"messages": ["Escribe una notificación de reembolso para el cliente"]},
config=config,
)
# Tras la pausa, el payload de interrupt vuelve al llamador.
# Muestra la UI de aprobación y espera la decisión humana.
decision = show_approval_ui(result["__interrupt__"])
if decision == "approve":
app.invoke(Command(resume="approved"), config=config)
elif decision == "reject":
app.invoke(Command(resume="rejected"), config=config)
elif decision == "edit":
app.update_state(config, {"email_params": {"to": "[email protected]"}})
app.invoke(Command(resume="approved"), config=config)
Puntos clave:
interrupt() pausa el graph.
Command(resume=...) reanuda.
checkpoint + thread_id mantienen coherencia.
approve/reject/edit son rutas de reanudación.
Verifica también la API de LangGraph.
Campos de evidencia de aprobación: qué guardar y cómo rastrear
La aprobación también es registro. El mínimo audit log:
| Campo | Descripción | Ejemplo |
|---|---|---|
| tool_name | Tool + tipo de operación | send_email / delete_record |
| tool_arguments | JSON completo de argumentos | {“to”: “[email protected]”, “subject”: “Aviso de reembolso”} |
| invoker_id | Identidad invocadora | [email protected] / agent_run_abc123 |
| request_time | Hora de solicitud | 2026-06-23T09:26:10Z |
| approver_id | Identidad del aprobador | [email protected] |
| decision_time | Hora de decisión | 2026-06-23T09:35:12Z |
| decision | Resultado | approved / rejected / timeout_auto_reject |
| evidence | Evidencia: captura o resumen | ”Destinatario correcto, sin datos sensibles” |
| audit_trail_id | Enlace a logs del run | run_abc123_step_5_tool_3 |
Estos campos vienen de recomendaciones MCP Tools y de items OpenAI API como mcp_approval_request. Los logs de aprobación son observabilidad; el artículo Agent monitoring/recovery cubre el logging más amplio.
¿Cómo serializar RunState? En OpenAI Agents SDK, result.to_state() convierte el resultado pausado. LangGraph usa checkpoint + thread_id. Guarda el estado en base o logs y vincúlalo a audit_trail_id.
Usos principales:
Rastreo de incidentes: saber quién aprobó qué y cuándo.
Evidencia de cumplimiento: demostrar aprobación humana para acciones críticas.
Mejora de policy: medir acciones aprobadas, rechazadas o expiradas.
Puedes añadir duración, canal Slack/Feishu/correo o revisión doble. Pero el mínimo debe existir.
Rechazo y timeout: qué pasa cuando falla la aprobación
La aprobación no siempre pasa. Reject y timeout necesitan rutas claras para que el run no quede colgado.
Tres rutas tras rechazo
Ruta 1: continue with fallback. Usa una acción de menor riesgo. Si se rechaza enviar un correo, guárdalo como borrador y sigue.
Ruta 2: convert to draft. Cambia la acción a borrador. Si se rechaza un envío CMS, queda como borrador para edición humana.
Ruta 3: abort task. Detén toda la tarea. Si se rechaza borrar producción, el run debe parar.
Elección por tipo:
Reversible: fallback o convert to draft.
Irreversible y de alto riesgo: abort task.
Requiere intervención humana: escalate.
Dos rutas tras timeout
Ruta 1: escalate to backup approver. Si el aprobador principal no responde en 30 minutos, pasa al on-call engineer.
Ruta 2: auto-reject. Tras una hora, rechaza automáticamente y detén. Útil para bajo riesgo con límite de tiempo.
Elección por contexto:
Alto riesgo: escalar, no ejecutar automáticamente.
Tiempo crítico: auto-reject para no bloquear.
Caso general: escalar.
Cómo revertir pasos ya ejecutados
Después de un rechazo, puede haber pasos ya hechos. Si el agente creó un pedido antes de rechazar el pago, hay que cancelarlo.
Estrategias:
Checkpoint rollback: volver al checkpoint anterior a approval.
Transacción compensatoria: llamar una API, como cancelar pedido.
Intervención humana: avisar a una persona cuando no se puede automatizar.
No siempre se puede revertir. Un correo enviado no se desenvía. En ese caso quedan audit log y gestión posterior.
Cuatro fronteras de seguridad: combinar Policy, Guardrail, Approval y Audit
Approval no es un mecanismo aislado. Policy, Guardrail, Approval y Audit se combinan y no se sustituyen.
Tabla de responsabilidades en cuatro capas
| Capa | Responsabilidad | Ejemplo |
|---|---|---|
| Policy | Reglas estáticas que limitan tools | ”No borrar producción”, “tool de pago solo en sandbox” |
| Guardrail | Controles automáticos de entrada/salida | Validación, sanitización, filtro de datos sensibles, umbral de importe |
| Approval | Decisión humana ante riesgo alto | Mostrar destinatario y contenido antes de correo, confirmar borrado, aprobar pago |
| Audit | Trazabilidad posterior | Logs de aprobación, tool calls, cambios de estado |
Policy no reemplaza Guardrail: es estática.
Guardrail no reemplaza Approval: no toma decisiones de negocio.
Approval no reemplaza Audit: decisión y trazabilidad son distintas.
Audit no reemplaza las tres primeras: llega después del riesgo.
Ejemplos:
Pago: Policy limita importe, Guardrail valida argumentos, Approval exige doble revisión, Audit guarda.
Correo: Policy limita dominios, Guardrail revisa contenido sensible, Approval muestra resumen, Audit registra envío.
Frontera de seguridad de tools MCP
MCP (Model Context Protocol) tiene su propia frontera. Para la spec 2025-06-18, verifica versión antes de publicar:
tools/list muestra tools disponibles.
tools/call antes de acciones sensibles corresponde a Approval.
inputSchema corresponde a Guardrail.
timeout evita tool calls colgados.
audit logging corresponde a Audit.
Recordatorio: MCP approval no reemplaza OAuth scope ni server-side authorization. MCP approval confirma un tool call; OAuth scope da permiso API; server-side authorization verifica permisos de negocio. Los tres hacen falta.
Un servidor Feishu MCP puede tener OAuth y scope send_message. Eso no vuelve seguro cada mensaje. MCP approval revisa contenido; server-side authorization revisa destinatario permitido.
Los casos de mensajes salientes, escrituras colaborativas y cambios masivos de tablas irán en el artículo Feishu MCP planificado.
Diseño de la UI de aprobación
La UI no son solo botones approve/reject. Debe mostrar suficiente información.
Principios:
Mostrar tool y argumentos.
Mostrar impacto esperado, como “enviar correo a [email protected] con asunto Aviso de reembolso”.
Mostrar reversibilidad: “irreversible tras envío” o “restaurable tras borrado”.
Mostrar sensibilidad de datos.
Distinguir cancel y reject. Cancel abandona la interacción; reject deniega el tool call y audita.
Elementos:
Tool + tipo de operación
Argumentos completos, plegables
Resumen de impacto
Advertencia de reversibilidad
Etiqueta de sensibilidad
Campo de razón
Botones approve / reject / cancel
Importante: la UI no reemplaza server-side authorization. Incluso tras confirmar, el backend verifica invocador, objeto y permiso.
Si la UI dice “borrar record ID=123”, el backend aún comprueba propietario y permiso.
HITL no es un pop-up aislado. Va en el tool gateway, logs y permisos. El artículo de arquitectura MCP lo desarrollará.
Mapeo de riesgos OWASP LLM01/LLM06
OWASP LLM Top 10 define riesgos de LLM y agentes. Números y versiones pueden cambiar; verifica antes de publicar. Dos importan para approval:
| Riesgo | Descripción | Respuesta con approval |
|---|---|---|
| LLM01 Prompt Injection | Entrada externa induce llamadas no autorizadas, fuga de datos o comandos externos | Exigir aprobación para alto riesgo; no depender solo del prompt; mostrar argumentos e impacto |
| LLM06 Excessive Agency | Funciones, permisos y autonomía excesivos generan riesgo | Limitar tools con Policy, autonomía con Approval y acotar cualquier “always allow” |
LLM01 muestra que prompt injection puede empujar al modelo a llamar tools no autorizados. Approval pausa antes de riesgo alto y muestra argumentos + impacto.
LLM06 muestra que demasiada autonomía es peligrosa. Approval necesita Policy y Guardrail. “Permitir siempre esta sesión” debe estar muy acotado.
Mapeo con NIST AI RMF Core
NIST AI RMF Core organiza riesgos IA en cuatro fases. Un equipo pequeño puede usar una versión ligera:
| Fase | Responsabilidad de aprobación | Ejemplo |
|---|---|---|
| Govern | Definir roles y reglas | Roles owner/on-call engineer, niveles L0-L3, estrategias reject/timeout |
| Map | Identificar escenarios de riesgo | Borrado, pago, cambio de permisos y prompt injection con la matriz |
| Measure | Medir cobertura y rechazo | Seguir coverage, rejection rate, timeout rate y ajustar policy |
| Manage | Respuesta y recuperación | Usar logs, rollback y compensación |
Govern define reglas. Map encuentra riesgos. Measure mide si el control funciona. Manage gestiona incidentes.
Para equipos pequeños basta: niveles de aprobación, acciones de riesgo, tasa de rechazo y audit logs.
Conclusión
Clasificar riesgo es el primer paso. No todo necesita aprobación: lecturas y borradores pueden ser automáticos; borrados y pagos deben esperar. Usa impacto externo, reversibilidad, sensibilidad, dinero/permisos y autonomía.
Approval no es un pop-up. Es estado serializable, reanudable y auditable. RunState se guarda en checkpoint, el run vuelve al punto de pausa y audit log conserva decisión y cadena de ejecución.
Las cuatro fronteras tienen roles distintos. Policy limita tools, Guardrail revisa automáticamente, Approval añade decisión humana, Audit da trazabilidad. Se necesitan juntas.
OWASP LLM01 y LLM06 sitúan prompt injection y excessive agency como riesgos centrales. Approval debe trabajar con Policy y Guardrail.
NIST AI RMF Core da el marco. Para un equipo pequeño: niveles de approval, acciones de alto riesgo, tasa de rechazo y audit logs.
Lecturas siguientes:
Artículo publicado de LangGraph checkpoint/thread state: base técnica para guardar estado de aprobación.
Artículo publicado de Agent monitoring/recovery: approval logs como observabilidad.
Artículo planificado de arquitectura MCP en producción: por qué HITL vive en gateway y permisos.
Artículo planificado de Feishu MCP: escenarios para mensajes salientes y escrituras colaborativas.
Diseñar un flujo de aprobación humana para un agente
Usa clasificación de riesgos, pausa de ejecución, evidencias de aprobación y registros de auditoría para diseñar un flujo reanudable.
- 1
Step 1: Listar tools y acciones
Enumera los tools, sistemas externos y acciones concretas que puede invocar el agente. No clasifiques solo por nombre de tool. - 2
Step 2: Marcar dimensiones de riesgo
Para cada acción, marca impacto externo, reversibilidad, sensibilidad de datos, umbral de dinero o permisos y nivel de autonomía. - 3
Step 3: Definir niveles de aprobación
Asigna cada acción a auto, draft, approval, strong approval o deny. - 4
Step 4: Persistir el estado de ejecución
En runtime, guarda approval request, RunState o checkpoint y vincúlalos con taskId, runId y traceId. - 5
Step 5: Mostrar evidencia de aprobación
Muestra tool, resumen de argumentos, objeto afectado, reversibilidad, sensibilidad e impacto esperado. - 6
Step 6: Gestionar approve, reject y timeout
Según la decisión, reanuda, degrada a borrador, compensa pasos ya hechos, escala o detiene la tarea. - 7
Step 7: Registrar auditoría y pruebas
Guarda logs de aprobación y resultados de reanudación; añade rutas de reject, timeout y compensación a pruebas de regresión.
FAQ
¿Qué acciones de un agente de IA necesitan aprobación humana?
¿Sigue haciendo falta aprobación si ya tengo guardrails?
¿Por qué necesito aprobación después de conceder OAuth scope?
¿Puede un botón significar permitir siempre en esta sesión?
¿Qué debe pasar después de un rechazo?
¿Qué campos debe guardar un registro de aprobación?
14 min de lectura · Publicado el: 11 sep 2026 · Actualizado el: 11 sep 2026
Guía de ingeniería de AI Agents
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Context engineering para agentes de IA: cómo separar System Prompt, Memory, Tools y Files
Un marco práctico para dividir el contexto de un agente entre system prompt, reglas de desarrollo, memory, files, retrieval, tool schema, runtime state y output contract, evitando que las reglas se diluyan en ejecuciones largas.
Parte 18 de 22
Siguiente
Control de costos en agentes de IA: routing de modelos, presupuestos de herramientas, caché y retries
Guía práctica para controlar el costo de agentes de IA con objetos de presupuesto, routing de modelos, límites de llamadas a herramientas, Prompt Caching, Batch/Flex, circuit breakers, logs de costo y alertas.
Parte 20 de 22



Comentarios
Inicia sesión con GitHub para dejar un comentario