Design de agentes human-in-the-loop: quais etapas precisam de aprovação humana

"A documentação human-in-the-loop do OpenAI Agents SDK descreve tools que exigem approval, interruptions do run e retomada com RunState após approve ou reject."
O rascunho de uma mensagem no Feishu já está pronto: título, corpo e link do anexo. Falta só enviar. Mas o agente para antes de send_message e espera sua confirmação.
Um e-mail pode ser gerado automaticamente, mas antes de ir para um cliente a interface precisa mostrar destinatário, assunto, resumo do corpo e anexos. Um formulário CMS pode ser preenchido sozinho, mas submit_form é bloqueado por policy e espera owner approve. Parece um botão de confirmação, mas a fronteira real é o estado de execução. O RunState do agente é salvo e a execução só continua depois da decisão humana. Onde pausar, quem aprova e o que fazer em reject ou timeout não é detalhe de frontend: é segurança do sistema de tools.
Este guia traz uma matriz de risco, uma checklist de pontos de aprovação, um mecanismo de pausa/retomada e um modelo de auditoria. A ideia é sair de “colocar um botão” para um estado serializável, retomável e auditável.
Matriz de risco: decidir quais ações precisam de aprovação
Nem tudo precisa de aprovação. Leituras podem rodar sozinhas; apagar e pagar devem esperar uma pessoa. Comece com cinco dimensões:
| Dimensão | Critério | Exemplos de ações |
|---|---|---|
| Impacto externo | Toca sistemas ou usuários externos | Enviar e-mail, enviar formulário, chamar API externa, escrever em Feishu/Slack |
| Reversibilidade | A ação pode ser desfeita? | Apagar registro (irreversível), salvar rascunho (reversível), pagamento (compensação parcial), enviar mensagem (irreversível) |
| Sensibilidade dos dados | Nível dos dados envolvidos | Consultar dados públicos, modificar registros internos, exportar privacidade de usuários, ler config de produção |
| Limite financeiro/permissões | Envolve dinheiro ou permissões | Pagamento, transferência, reembolso, mudança de permissões, operação em massa, apagar dados de usuários |
| Nível de autonomia | Automação permitida | Consulta read-only (automática), rascunho (automático), mensagem externa (confirmação), apagar/pagar (approval) |
Essa matriz segue a lógica da OWASP LLM06: funcionalidade excessiva, permissões excessivas e autonomia excessiva. Use como base e ajuste os limites ao negócio.
Impacto externo: qualquer ação que alcance outra pessoa ou sistema merece cuidado. Um e-mail enviado não volta. Um formulário pode disparar um pedido. Uma API externa pode alterar dados de outro sistema.
Reversibilidade: apagar registro é irreversível; rascunho se edita; pagamento pode exigir reembolso ou compensação.
Sensibilidade dos dados: dados públicos podem ser lidos com mais liberdade; dados internos precisam de controle de escrita; privacidade e configuração de produção pedem aprovação.
Limite financeiro/permissões: dinheiro e permissões devem pausar. Pagamentos, transferências, reembolsos, mudanças de permissão e operações em massa são pontos de alto risco.
Nível de autonomia: leituras podem ser automáticas. Rascunhos também. Saídas externas exigem confirmação; apagar e pagar exigem approval.
A matriz evolui. Uma mensagem interna no Feishu pode ficar em confirmação; um pagamento real continua com approval e revisão de duas pessoas.
Três cenários reais de classificação
Caso 1: rascunho de mensagem no Feishu
Escrever um rascunho no Feishu é automático (L0). Ele fica em rascunhos, não é enviado, é reversível e não tem impacto externo. Chamar send_message para um cliente exige approval (L2). Depois de enviada, a mensagem é irreversível, sai para fora e pode conter informação sensível. Confirmar antes de MCP tools/call é esse caso.
Caso 2: envio de e-mail
Gerar o conteúdo do e-mail é automático (L0). Ainda é texto. Enviar pela API exige approval com destinatário, assunto e anexos (L2). A UI deve mostrar evidência e resumo, não só “confirmar envio”.
Caso 3: envio de formulário CMS
Preencher o formulário é automático (L0). Nada foi enviado. Chamar a API do CMS para enviar deve bloquear e esperar owner approve (L2). O bloqueio pode vir de um guardrail, como “valor acima do limite”, ou de uma policy fixa.
Caso 4: apagar banco de dados de produção
Consultar produção é automático (L0). É leitura. Apagar em produção exige aprovação humana forte + auditoria de backup (L3). É irreversível, sensível e afeta usuários. Deve ser limitado por Policy, não só por Guardrail.
A lição: na mesma tarefa, cada etapa tem risco diferente. Rascunho é automático, envio pede approval, apagar produção pede revisão dupla. Classifique ações concretas.
Checklist de pontos de aprovação: descer até o tipo de ação
Com a matriz, defina níveis:
L0 automático: consultas de banco, busca vetorial, leitura de configuração; salvar rascunho ou gerar preview. Sem impacto externo, reversível, sem dados sensíveis.
L1 confirmação: mensagens ou dados externos, como e-mail, formulário, API externa; leituras em massa como exportação. Há impacto externo, mas controlável.
L2 approval: apagar, mudar permissões, apagar em massa; escrever em Feishu, Slack ou CRM. São ações irreversíveis ou de alto impacto.
L3 strong approval + revisão dupla: pagamentos, reembolsos, exportar privacidade, mudar config de produção, apagar banco de produção. Dinheiro ou dados sensíveis.
A lista combina com needs_approval no OpenAI Agents SDK e com segurança MCP. MCP espera tool calls visíveis, recusáveis e confirmados para ações sensíveis.
Você pode ajustar:
Se Feishu é só notificação interna, L1 pode bastar.
Se apagar toca dados de usuários, mantenha L2.
Se pagamento é crítico, suba para L3 com motivo obrigatório.
A lista muda com o negócio. Se a regra mudar, “enviar mensagem” pode sair de approval.
Máquina de estados do fluxo de aprovação: pausar, salvar, retomar
Aprovação não é pop-up. É um run pausado. Quando um tool call precisa de approval, RunState é salvo e a execução retoma depois da decisão.
Diagrama de transição de estado
O fluxo é:
request -> pending -> approved/rejected/timeout -> resume/abort/compensate
request: o tool call cria a solicitação; RunState contém tool, argumentos e contexto.
pending: o run espera decisão; o estado é salvo em checkpoint e ligado a thread_id.
approved: aprovado; o run retoma do checkpoint e chama o tool.
rejected: rejeitado; vai para abort ou convert to draft.
timeout: expira; escala ou auto-reject.
resume/abort/compensate: continuar, parar ou compensar.
Modos de retomada
approve: continuar, chamar o tool e seguir.
reject: parar ou converter em rascunho, sem chamar o tool.
edit: modificar parâmetros, como destinatário ou conteúdo, e aprovar de novo.
Checkpoint e thread state são a base técnica. O artigo publicado sobre LangGraph checkpoint/thread state explica como salvar o ponto de pausa e voltar a ele.
Exemplo de código OpenAI Agents SDK HITL
Este exemplo mostra o fluxo no OpenAI Agents SDK. A API pode mudar; confira a documentação oficial antes de produção:
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="Rascunhar o e-mail e esperar aprovação antes do envio",
)
result = Runner.run_sync(agent, "Escreva um aviso de reembolso para o cliente")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
print(f"Tool pendente de aprovação: {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)
Pontos principais:
needs_approval=True marca o tool.
interruptions contém tool calls pendentes.
result.to_state() cria um RunState serializável.
state.approve() ou state.reject() registra a decisão.
Runner.run_sync(agent, state) retoma da pausa.
Os nomes podem mudar depois de 2026-07; confira a documentação oficial.
Exemplo de código LangGraph interrupt/resume
Este exemplo usa interrupt e 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": ["O envio do e-mail foi rejeitado; a mensagem foi salva como rascunho"]}
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": ["Escreva uma notificação de reembolso para o cliente"]},
config=config,
)
# Após a pausa, o payload de interrupt volta ao chamador.
# Mostre a UI de aprovação e espere a decisão 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": "new_customer@example.com"}})
app.invoke(Command(resume="approved"), config=config)
Pontos principais:
interrupt() pausa o graph.
Command(resume=...) retoma.
checkpoint + thread_id mantêm consistência.
approve/reject/edit são rotas de retomada.
Confira também a API do LangGraph.
Campos de evidência de aprovação: o que guardar e como rastrear
Aprovação também é registro. O audit log mínimo:
| Campo | Descrição | Exemplo |
|---|---|---|
| tool_name | Tool + tipo de operação | send_email / delete_record |
| tool_arguments | JSON completo de argumentos | {“to”: “customer@example.com”, “subject”: “Aviso de reembolso”} |
| invoker_id | Identidade chamadora | user@example.com / agent_run_abc123 |
| request_time | Hora da solicitação | 2026-06-23T09:26:10Z |
| approver_id | Identidade do aprovador | on-call-engineer@example.com |
| decision_time | Hora da decisão | 2026-06-23T09:35:12Z |
| decision | Resultado | approved / rejected / timeout_auto_reject |
| evidence | Evidência: captura ou resumo | ”Destinatário correto, sem dados sensíveis” |
| audit_trail_id | Link para logs do run | run_abc123_step_5_tool_3 |
Esses campos vêm de recomendações MCP Tools e de items OpenAI API como mcp_approval_request. Logs de aprovação são observabilidade; o artigo Agent monitoring/recovery cobre logging de forma mais ampla.
Como serializar RunState? No OpenAI Agents SDK, result.to_state() converte o resultado pausado. LangGraph usa checkpoint + thread_id. Salve o estado em banco ou logs e vincule a audit_trail_id.
Usos principais:
Rastreamento de incidentes: saber quem aprovou o quê e quando.
Evidência de compliance: demonstrar aprovação humana em ações críticas.
Melhoria de policy: medir ações aprovadas, rejeitadas ou expiradas.
Você pode adicionar duração, canal Slack/Feishu/e-mail ou revisão dupla. Mas o mínimo precisa existir.
Rejeição e timeout: o que acontece quando a aprovação falha
A aprovação nem sempre passa. Reject e timeout precisam de rotas claras para o run não travar.
Três rotas após rejeição
Rota 1: continue with fallback. Use uma ação menos arriscada. Se o envio de e-mail for rejeitado, salve como rascunho e continue.
Rota 2: convert to draft. Transforme a ação em rascunho. Se o envio CMS for rejeitado, deixe para edição humana.
Rota 3: abort task. Pare toda a tarefa. Se apagar produção for rejeitado, o run deve parar.
Escolha por tipo:
Reversível: fallback ou convert to draft.
Irreversível e de alto risco: abort task.
Precisa de intervenção humana: escalate.
Duas rotas após timeout
Rota 1: escalate to backup approver. Se o aprovador principal não responder em 30 minutos, envie ao on-call engineer.
Rota 2: auto-reject. Após uma hora, rejeite automaticamente e pare. Útil para baixo risco com limite de tempo.
Escolha por contexto:
Alto risco: escalar, não executar automaticamente.
Tempo crítico: auto-reject para não bloquear.
Caso geral: escalar.
Como desfazer etapas já executadas
Depois de uma rejeição, algo pode ter sido feito. Se o agente criou um pedido antes da rejeição do pagamento, cancele o pedido.
Estratégias:
Checkpoint rollback: voltar ao checkpoint antes de approval.
Transação compensatória: chamar uma API, como cancelar pedido.
Intervenção humana: avisar alguém quando não dá para automatizar.
Nem sempre dá para desfazer. E-mail enviado não volta. Nesse caso ficam audit log e tratamento posterior.
Quatro fronteiras de segurança: combinar Policy, Guardrail, Approval e Audit
Approval não é proteção isolada. Policy, Guardrail, Approval e Audit se combinam e não se substituem.
Tabela de responsabilidades em quatro camadas
| Camada | Responsabilidade | Exemplo |
|---|---|---|
| Policy | Regras estáticas que limitam tools | ”Não apagar produção”, “tool de pagamento só em sandbox” |
| Guardrail | Checagens automáticas de entrada/saída | Validação, sanitização, filtro de dados sensíveis, limite de valor |
| Approval | Decisão humana para alto risco | Mostrar destinatário e conteúdo antes do e-mail, confirmar exclusão, aprovar pagamento |
| Audit | Rastreabilidade posterior | Logs de aprovação, tool calls, mudanças de estado |
Policy não substitui Guardrail: é estática.
Guardrail não substitui Approval: não toma decisão de negócio.
Approval não substitui Audit: decisão e rastreabilidade são diferentes.
Audit não substitui as três primeiras: chega depois do risco.
Exemplos:
Pagamento: Policy limita valor, Guardrail valida argumentos, Approval exige revisão dupla, Audit registra.
E-mail: Policy limita domínios, Guardrail revisa conteúdo sensível, Approval mostra resumo, Audit registra envio.
Fronteira de segurança de tools MCP
MCP (Model Context Protocol) tem sua própria fronteira. Para a spec 2025-06-18, confira a versão antes de publicar:
tools/list mostra tools disponíveis.
tools/call antes de ações sensíveis corresponde a Approval.
inputSchema corresponde a Guardrail.
timeout evita tool calls travados.
audit logging corresponde a Audit.
Lembrete: MCP approval não substitui OAuth scope nem server-side authorization. MCP approval confirma um tool call; OAuth scope dá permissão de API; server-side authorization verifica permissões de negócio. Os três são necessários.
Um servidor Feishu MCP pode ter OAuth e scope send_message. Isso não torna toda mensagem segura. MCP approval revisa conteúdo; server-side authorization revisa destinatário permitido.
Casos de mensagens externas, escritas colaborativas e alterações em massa de tabelas ficarão no artigo planejado sobre Feishu MCP.
Design da UI de aprovação
A UI não é só approve/reject. Ela precisa mostrar informação suficiente.
Princípios:
Mostrar tool e argumentos.
Mostrar impacto esperado, como “enviar e-mail para customer@example.com com assunto Aviso de reembolso”.
Mostrar reversibilidade: “irreversível após envio” ou “restaurável após exclusão”.
Mostrar sensibilidade dos dados.
Separar cancel e reject. Cancel abandona a interação; reject nega o tool call e audita.
Elementos:
Tool + tipo de operação
Argumentos completos, dobráveis
Resumo do impacto
Aviso de reversibilidade
Rótulo de sensibilidade
Campo de motivo
Botões approve / reject / cancel
Importante: UI não substitui server-side authorization. Mesmo depois de confirmar, o backend verifica chamador, objeto e permissão.
Se a UI diz “apagar record ID=123”, o backend ainda verifica dono e permissão.
HITL não é pop-up isolado. Ele fica no tool gateway, logs e permissões. O artigo de arquitetura MCP vai detalhar.
Mapeamento de riscos OWASP LLM01/LLM06
OWASP LLM Top 10 define riscos de LLMs e agentes. Números e versões podem mudar; confira antes de publicar. Dois importam para approval:
| Risco | Descrição | Resposta com approval |
|---|---|---|
| LLM01 Prompt Injection | Entrada externa induz chamadas não autorizadas, vazamento de dados ou comandos externos | Exigir aprovação para alto risco; não depender só de prompt; mostrar argumentos e impacto |
| LLM06 Excessive Agency | Funções, permissões e autonomia excessivas geram risco | Limitar tools com Policy, autonomia com Approval e restringir qualquer “always allow” |
LLM01 mostra que prompt injection pode levar o modelo a chamar tools não autorizados. Approval pausa antes do alto risco e mostra argumentos + impacto.
LLM06 mostra que autonomia demais é perigosa. Approval precisa trabalhar com Policy e Guardrail. “Permitir sempre nesta sessão” deve ser bem restrito.
Mapeamento com NIST AI RMF Core
NIST AI RMF Core organiza risco de IA em quatro fases. Um time pequeno pode usar uma versão leve:
| Fase | Responsabilidade de aprovação | Exemplo |
|---|---|---|
| Govern | Definir papéis e regras | Papéis owner/on-call engineer, níveis L0-L3, estratégias reject/timeout |
| Map | Identificar cenários de risco | Exclusão, pagamento, mudança de permissões e prompt injection com a matriz |
| Measure | Medir cobertura e rejeição | Acompanhar coverage, rejection rate, timeout rate e ajustar policy |
| Manage | Resposta e recuperação | Usar logs, rollback e compensação |
Govern define regras. Map encontra riscos. Measure mede se o controle funciona. Manage trata incidentes.
Para times pequenos basta: níveis de aprovação, ações de alto risco, taxa de rejeição e audit logs.
Conclusão
Classificar risco é o primeiro passo. Nem tudo precisa de aprovação: leituras e rascunhos podem ser automáticos; exclusões e pagamentos devem esperar. Use impacto externo, reversibilidade, sensibilidade, dinheiro/permissões e autonomia.
Approval não é pop-up. É estado serializável, retomável e auditável. RunState é salvo em checkpoint, o run volta ao ponto de pausa e o audit log guarda decisão e cadeia de execução.
As quatro fronteiras têm papéis diferentes. Policy limita tools, Guardrail checa automaticamente, Approval adiciona decisão humana, Audit dá rastreabilidade. Use juntas.
OWASP LLM01 e LLM06 colocam prompt injection e excessive agency como riscos centrais. Approval deve trabalhar com Policy e Guardrail.
NIST AI RMF Core dá o marco. Para um time pequeno: níveis de approval, ações de alto risco, taxa de rejeição e audit logs.
Leituras seguintes:
Artigo publicado de LangGraph checkpoint/thread state: base técnica para salvar estado de aprovação.
Artigo publicado de Agent monitoring/recovery: approval logs como observabilidade.
Artigo planejado de arquitetura MCP em produção: por que HITL vive no gateway e nas permissões.
Artigo planejado de Feishu MCP: cenários para mensagens externas e escritas colaborativas.
Projetar um fluxo de aprovação humana para um agente
Use classificação de risco, pausa de execução, evidências de aprovação e logs de auditoria para criar um fluxo retomável.
- 1
Step 1: Listar tools e ações
Liste os tools, sistemas externos e ações concretas que o agente pode chamar. Não classifique apenas pelo nome do tool. - 2
Step 2: Marcar dimensões de risco
Para cada ação, marque impacto externo, reversibilidade, sensibilidade dos dados, limite financeiro ou de permissões e nível de autonomia. - 3
Step 3: Definir níveis de aprovação
Associe cada tipo de ação a auto, draft, approval, strong approval ou deny. - 4
Step 4: Persistir o estado de execução
Em runtime, salve approval request, RunState ou checkpoint e relacione com taskId, runId e traceId. - 5
Step 5: Mostrar evidências de aprovação
Mostre tool, resumo dos argumentos, objeto afetado, reversibilidade, sensibilidade e impacto esperado. - 6
Step 6: Tratar approve, reject e timeout
Conforme a decisão, retome, rebaixe para rascunho, compense etapas feitas, escale ou pare a tarefa. - 7
Step 7: Registrar auditoria e testes
Salve logs de aprovação e resultados de retomada; teste caminhos de reject, timeout e compensação.
FAQ
Quais ações de um agente de IA precisam de aprovação humana?
Ainda preciso de aprovação se já tenho guardrails?
Por que preciso de aprovação depois de conceder OAuth scope?
Um botão pode significar sempre permitir nesta sessão?
O que deve acontecer depois de uma rejeição?
Quais campos um registro de aprovação deve guardar?
13 min de leitura · Publicado em: 11 set 2026 · Atualizado em: 11 set 2026
Guia de engenharia de AI Agents
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Context engineering para agentes de IA: como separar System Prompt, Memory, Tools e Files
Um guia prático para dividir o contexto de um agente entre system prompt, regras de desenvolvimento, memory, files, retrieval, tool schema, runtime state e output contract, evitando perda de regras em execuções longas.
Parte 9 de 13
Próximo
Controle de custos em agentes de IA: roteamento de modelos, orçamento de ferramentas, cache e retries
Guia prático para controlar custos de agentes de IA com objetos de orçamento, roteamento de modelos, limites de chamadas de ferramentas, Prompt Caching, Batch/Flex, circuit breakers, logs de custo e alertas.
Parte 11 de 13



Comentários
Entre com GitHub para comentar