Cambiar tema

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

Easton editorial illustration: agent rollout and rollback rail

"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ónCriterioEjemplos de acciones
Impacto externoToca sistemas o usuarios externosEnviar correo, enviar formulario, llamar API externa, escribir en Feishu/Slack
ReversibilidadSi la acción puede deshacerseBorrar un registro (irreversible), guardar borrador (reversible), pago (compensación parcial), enviar mensaje (irreversible)
Sensibilidad de datosNivel de datos implicadosConsultar datos públicos, modificar registros internos, exportar privacidad de usuarios, leer config de producción
Umbral de dinero/permisosImplica dinero o cambios de permisosPago, transferencia, reembolso, cambio de permisos, operación masiva, borrar datos de usuarios
Nivel de autonomíaAutomatización permitidaConsulta 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:

CampoDescripciónEjemplo
tool_nameTool + tipo de operaciónsend_email / delete_record
tool_argumentsJSON completo de argumentos{“to”: “[email protected]”, “subject”: “Aviso de reembolso”}
invoker_idIdentidad invocadora[email protected] / agent_run_abc123
request_timeHora de solicitud2026-06-23T09:26:10Z
approver_idIdentidad del aprobador[email protected]
decision_timeHora de decisión2026-06-23T09:35:12Z
decisionResultadoapproved / rejected / timeout_auto_reject
evidenceEvidencia: captura o resumen”Destinatario correcto, sin datos sensibles”
audit_trail_idEnlace a logs del runrun_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

CapaResponsabilidadEjemplo
PolicyReglas estáticas que limitan tools”No borrar producción”, “tool de pago solo en sandbox”
GuardrailControles automáticos de entrada/salidaValidación, sanitización, filtro de datos sensibles, umbral de importe
ApprovalDecisión humana ante riesgo altoMostrar destinatario y contenido antes de correo, confirmar borrado, aprobar pago
AuditTrazabilidad posteriorLogs 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:

RiesgoDescripciónRespuesta con approval
LLM01 Prompt InjectionEntrada externa induce llamadas no autorizadas, fuga de datos o comandos externosExigir aprobación para alto riesgo; no depender solo del prompt; mostrar argumentos e impacto
LLM06 Excessive AgencyFunciones, permisos y autonomía excesivos generan riesgoLimitar 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:

FaseResponsabilidad de aprobaciónEjemplo
GovernDefinir roles y reglasRoles owner/on-call engineer, niveles L0-L3, estrategias reject/timeout
MapIdentificar escenarios de riesgoBorrado, pago, cambio de permisos y prompt injection con la matriz
MeasureMedir cobertura y rechazoSeguir coverage, rejection rate, timeout rate y ajustar policy
ManageRespuesta y recuperaciónUsar 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. 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. 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. 3

    Step 3: Definir niveles de aprobación

    Asigna cada acción a auto, draft, approval, strong approval o deny.
  4. 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. 5

    Step 5: Mostrar evidencia de aprobación

    Muestra tool, resumen de argumentos, objeto afectado, reversibilidad, sensibilidad e impacto esperado.
  6. 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. 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?
Mensajes salientes, borrar o sobrescribir datos, pagos, cambios de permisos, lectura o escritura de datos sensibles, escrituras masivas, envíos irreversibles y compartir contexto con tools remotos suelen requerir approval o strong approval.
¿Sigue haciendo falta aprobación si ya tengo guardrails?
Sí. Un guardrail es una comprobación automática; la aprobación humana es un punto de decisión antes de una acción de negocio riesgosa. Cubren riesgos distintos.
¿Por qué necesito aprobación después de conceder OAuth scope?
Un OAuth scope indica permiso técnico para llamar una API. No decide si esa acción de negocio debe ejecutarse en el contexto actual.
¿Puede un botón significar permitir siempre en esta sesión?
Sí, pero con duración corta, scope de tool limitado, scope de objeto y audit log. Una aprobación no debe convertirse en acceso ilimitado.
¿Qué debe pasar después de un rechazo?
El run debe tomar una rama explícita: guardar borrador, pedir más datos, elegir un camino menos riesgoso, escalar, compensar o detenerse. No debe reintentar en silencio la misma acción.
¿Qué campos debe guardar un registro de aprobación?
Como mínimo taskId o runId, tool, resumen de argumentos, objeto afectado, nivel de riesgo, aprobador, decisión, razón, hora, traceId, acción de reanudación y código de error.

14 min de lectura · Publicado el: 11 sep 2026 · Actualizado el: 11 sep 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog