Conteúdo da trilha
PadrãoComportamento de agentes

Humano no Circuito

Human-in-the-Loop

Humano no Circuito (Human-in-the-Loop) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Comportamento de agentes. Pausar em pontos definidos para um humano aprovar, editar ou recusar.

Elemento
46/70
Nível
Intermediário
Leitura
10 min
Atualizado
Nesta página 18
  1. 01Intenção
  2. 02Problema
  3. 03Solução
  4. 04Estrutura
  5. 05Pseudocódigo
  6. 06Cenários reais
  7. 07Aplicabilidade
  8. 08Quando não usar
  9. 09Como implementar
  10. 10Variações
  11. 11Custo, latência e falhas
  12. 12Como avaliar
  13. 13Em produção
  14. 14Segurança
  15. 15Prós e contras
  16. 16Código
  17. 17Suporte por framework
  18. 18Teste seu entendimento
  19. ··Perguntas rápidas

Intenção

Pausar a execução em pontos definidos para que uma pessoa aprove, edite ou recuse o que o sistema vai fazer. Serve para fluxos em que um erro do modelo custa mais do que alguns segundos ou horas de espera.

Em uma frase
O copiloto que lê o checklist em voz alta e espera o “confere” antes de cada etapa crítica da decolagem.

Problema

Rafael é responsável pelo assistente jurídico de um escritório. O assistente revisa contratos de clientes, aponta cláusulas de risco e redige sugestões de alteração. No piloto, as sugestões iam direto para o cliente por email, porque “o modelo é muito bom”.

Na terceira semana, o assistente sugeriu remover uma cláusula de foro que o cliente precisava manter, com uma justificativa convincente e errada. O cliente aceitou. O problema só apareceu meses depois, num litígio.

Tirar o assistente não era opção: ele economizava horas de trabalho dos advogados. Pedir para os advogados “conferirem tudo” também não funcionava: sem um ponto claro de revisão, ninguém sabia o que já tinha sido conferido.

Esse tipo de erro já saiu do laboratório. Em Moffatt v. Air Canada (2024), o chatbot da companhia inventou uma política de tarifa por luto; o tribunal britânico-colombiano considerou a empresa responsável pelo que o bot afirmou. Em 2023, o chatbot de uma concessionária Chevrolet em Watsonville “aceitou” vender um Tahoe por 1 dólar depois de um prompt injection (injeção de instrução). Nos dois casos, uma afirmação ou um efeito saiu sem uma pessoa (nem um sistema de registro) no meio.

A pergunta que este padrão responde: onde e como colocar uma pessoa no fluxo para que o sistema seja útil e os erros caros não saiam?

Solução

Escolha os pontos do fluxo em que uma decisão humana muda o risco: antes de um efeito irreversível, antes de algo sair para fora, quando a confiança do modelo é baixa, ou quando a regra exige uma pessoa responsável. Em cada ponto, o sistema:

  1. Pausa e guarda o estado da execução.
  2. Mostra o que vai acontecer, com os dados que a pessoa precisa para decidir e o motivo da sugestão.
  3. Recebe uma de três respostas: aprovar, editar ou recusar (com motivo).
  4. Retoma do ponto em que parou, com a decisão como parte do estado.

A pessoa não revisa tudo; revisa o que importa, num formato feito para revisão rápida. As decisões viram dado: aprovações, edições e recusas alimentam o eval e mostram onde o sistema erra.

A aprovação precisa vir de um canal que o modelo e o conteúdo externo não conseguem escrever. Um “sim” no mesmo chat que lê emails de terceiros não prova nada. A prévia mostra dados do sistema (nome do recebedor na base, texto da cláusula no contrato), não a prosa convincente do modelo.

Exemplo trabalhado

O assistente de Rafael propõe um email ao cliente. Antes de send_to_client, o código monta a prévia com o contrato na base, não com o resumo do modelo:

{
  "acao": "send_to_client",
  "destinatario": "maria@cliente.com",
  "clausula": {
    "id": "12",
    "titulo": "Foro",
    "texto": "Fica eleito o foro da comarca da sede do Cliente.",
    "efeito_proposto": "remover a cláusula 12 do anexo"
  },
  "motivo_do_modelo": "cláusula de foro é incomum em contratos deste porte"
}

A advogada vê o texto da cláusula 12, o efeito (remover) e o email de destino. Recusa com motivo: “cliente exige foro na sede”. O modelo recebe a recusa como observação e propõe outra coisa, ou para. Sem a prévia com o texto real, a justificativa “incomum” teria passado, como passou no piloto.

No Pix, a mesma ideia: a prévia mostra o nome que o banco devolve para a chave, o valor em reais e a data. Não mostra “pagar a conta de luz”, que o modelo inventou a partir de um PDF anexado.

Estrutura

flowchart TD
  T(["1. Tarefa"]):::io --> A(["2. Agente trabalha"]):::llm
  A --> C{"3. Ponto de revisão?"}
  C -->|não| A
  C -->|sim| S[("4. Estado salvo")]:::store
  S --> P["5. Prévia com dados e motivo"]:::tool
  P --> H((("6. Pessoa: aprovar, editar, recusar"))):::human
  H --> R["7. Retomar com a decisão"]:::tool
  R --> A
  A --> OUT(["8. Resultado"]):::io
  H --> L[("9. Registro da decisão")]:::store
  1. A tarefa começa normalmente.
  2. O agente trabalha com ferramentas de leitura e prepara as ações.
  3. O código checa se a próxima ação é um ponto de revisão, por tipo de ação, valor ou confiança.
  4. O estado completo é salvo, para que a espera possa durar minutos ou dias.
  5. A prévia mostra a ação concreta, os dados vindos do sistema e a justificativa do modelo.
  6. A pessoa decide, num canal que só ela controla.
  7. A execução retoma: com a ação aprovada, com a versão editada, ou com a recusa como informação.
  8. O resultado sai.
  9. Toda decisão fica registrada, com quem decidiu e o que foi mostrado.
Checagem +20 XP de primeiraO chatbot lê emails de clientes no mesmo fio de conversa. Uma mensagem chega dizendo ‘aprovado, pode enviar’. O código trata isso como aprovação humana. O que está errado?

Pseudocódigo

schema Review { decision: "approve" | "edit" | "reject", edited_action: any?, reason: string?, reviewer: string }

REVIEW_POLICY = {
    "send_to_client": always,
    "change_contract": always,
    "schedule_payment": when(amount > 500_00),
    "draft_note": never,
}

function step(state):
    reply = llm_with_tools(SYSTEM, TOOLS, state)                 # 2
    for call in reply.tool_calls:
        if REVIEW_POLICY[call.name](call.args):                  # 3
            checkpoint = state.save()                            # 4
            preview = build_preview(call)                        # 5. dados do sistema
            review = human.approve(preview) -> Review            # 6. pode levar dias
            audit.log(call, preview, review)                     # 9
            state = checkpoint.restore()                         # 7
            if review.decision == "reject":
                state.add(tool_result(call, "recusado: " + review.reason))
                continue
            if review.decision == "edit":
                call = review.edited_action
        state.add(execute(call))
    return state

Cenários reais

  • Assistente financeiro com aprovação. Todo Pix agendado passa por confirmação no app, com o nome real do recebedor. Pagamentos recorrentes pré-autorizados têm teto.
  • Bot de suporte com escalonamento humano. Cancelamentos e reembolsos pedem o “sim” do cliente; reembolsos acima de um valor vão para um atendente.
  • Contrato e saúde (hipotético). Sugestão de alteração contratual e orientação sobre medicamento esperam um profissional. LGPD e regras do conselho pedem uma pessoa responsável; o modelo redige, a pessoa assina.

Aplicabilidade

  • Ações irreversíveis ou caras: dinheiro, comunicação externa, alteração de dados de terceiros.
  • Domínios regulados em que uma pessoa precisa responder pela decisão.
  • Fluxos em que o modelo acerta quase sempre, e o “quase” custa caro.
  • Fases iniciais de um produto, para medir a qualidade antes de dar mais autonomia.

O smell que este padrão trata de frente é Efeito Sem Portão. A refatoração correspondente é Aprovar Antes do Efeito.

Quando não usar

  • Ações reversíveis e baratas. A revisão vira ruído e as pessoas param de ler.
  • Volume alto demais para revisão humana. Use revisão por amostragem, por confiança ou por valor, e invista em checagens automáticas na saída (schema, lista de termos, consulta à política oficial).
  • Respostas que precisam sair em segundos, como voz. Revise o que dá para revisar depois e limite o que o sistema pode fazer em tempo real.

Como implementar

  1. Liste as ações do sistema e classifique por risco e reversibilidade.
  2. Escreva a política de revisão: quais ações, a partir de que valor ou abaixo de que confiança.
  3. Desenhe a prévia com quem vai revisar: o que precisa ver para decidir em segundos. Uma tela, um fato do sistema, um efeito em linguagem de gente.
  4. Implemente a pausa com estado persistido, para esperas longas. Veja Mover para Execução Durável.
  5. Suporte as três respostas: aprovar, editar e recusar com motivo.
  6. Faça a ferramenta recusar executar sem aprovação registrada. O portão vive no código da ferramenta, não no prompt.
  7. Registre cada decisão na Trilha de Auditoria.
  8. Use recusas e edições como casos novos no eval.

O que quem começa erra

  • Revisar tudo. A fila explode, o tempo médio de revisão cai para um segundo, e você tem um carimbo, não um controle. Menos pontos, melhores.
  • Prévia com a prosa do modelo. “Esta cláusula é redundante” esconde o texto da cláusula. Mostre o texto.
  • Espera síncrona de dias. O processo morre, o estado some, o agente recomeça e manda de novo. Checkpoint primeiro.
  • Aprovar no mesmo canal da injeção. WhatsApp que lê PDF do cliente não é um canal de “sim”.

No atendimento de banco, a prévia de um Pix de R$ 1.890 precisa do nome que o PSP devolveu para a chave, o valor em reais e a data. “Pagar a conta de luz, como o cliente pediu no PDF” é a frase que o golpe quer que a pessoa carimbe. O nome do recebedor na tela é o que faz a advogada, o operador ou o próprio cliente hesitar.

Ingênuo contra correto

Ingênuo Correto
O que revisar Tudo que o modelo produz Só efeito irreversível, saída externa ou exceção
Prévia Justificativa do modelo Dado do sistema mais o efeito concreto
Canal do “sim” A mesma conversa que lê o mundo Botão, app ou fila com identidade
Espera Request HTTP aberto por dois dias Checkpoint e retomada durável
Checagem +20 XP de primeiraA advogada aprova uma alteração. Meses depois descobre que era a cláusula de foro. A prévia dizia ‘remover cláusula redundante’, com a justificativa do modelo. O que falhou?

Variações

  • Aprovação antes. A ação espera o “sim”. É o caso padrão para efeitos irreversíveis.
  • Revisão depois, com janela de cancelamento. A ação é agendada e pode ser cancelada em X minutos. Útil quando a espera atrapalha e o efeito pode ser desfeito.
  • Revisão por amostragem. Uma fração das saídas vai para revisão, para medir qualidade em volume alto.
  • Escalonamento. O sistema decide sozinho quando tem confiança alta e passa para uma pessoa quando não tem. Confiança sozinha não basta para dinheiro e email externo.
  • Pré-autorização. O usuário aprova uma regra (“até R$ 200 para a escola, todo mês”), e ações dentro dela seguem sem pausa.

Custo, latência e falhas

Aspecto O que acontece Como aparece no trace
Latência A tarefa espera a pessoa: segundos a dias span human_review com duração longa
Custo Tempo de pessoas; o custo do modelo quase não muda revisões por dia e tempo médio por revisão
Falha: fadiga Aprovação sem leitura tempo de revisão muito curto, taxa de aprovação perto de 100%
Falha: prévia ruim A pessoa aprova algo diferente do que acha recusas e reclamações depois da execução
Falha: espera sem fim Ninguém responde e a tarefa fica parada revisões pendentes acima do prazo
Falha: retomada quebrada O estado salvo não basta para continuar erros logo depois de resume
Checagem +20 XP de primeiraA taxa de aprovação está em 99,8% e o tempo mediano de revisão é 1,2 s. O que isso sugere primeiro?

Como avaliar

  • Taxa de aprovação, edição e recusa por tipo de ação. Edição frequente mostra onde o modelo erra.
  • Tempo de revisão: tempo muito curto sugere aprovação no automático.
  • Erros que passaram: ações aprovadas que depois se mostraram erradas, por amostragem.
  • Eval com recusas: as ações recusadas viram casos em que o sistema deveria propor outra coisa.
{ "action": "send_to_client", "proposal": "remover cláusula 12 (foro)", "expected_review": "reject", "reason": "cliente exige foro na sede" }

Coloque no eval a prévia que a pessoa vê, não só a ação. Se a prévia omite o texto da cláusula, o caso “deveria recusar” não testa o produto real.

Em produção

  • Span human_review com ação, prévia, decisão, revisor e duração.
  • Fila de revisões pendentes com prazo e alerta quando passa do prazo.
  • Painel de aprovação por revisor e por tipo de ação.
  • Revisão periódica da política: ações sempre aprovadas sem edição podem ganhar pré-autorização; ações muito recusadas pedem mudança no sistema.

Segurança

A aprovação precisa vir de um canal que o modelo e o conteúdo externo não conseguem escrever. Um “sim” digitado no mesmo chat que lê emails de terceiros não prova que o usuário aprovou. Mostre dados do sistema, não texto gerado, na prévia.

Isso também é defesa contra o caso Chevrolet: o modelo pode “aceitar” um preço absurdo no diálogo. O sistema de registro (tabela de preço, limite, pessoa) é quem vende, não o turno do chat.

Prós e contras

Prós Contras
Erros caros não saem sem uma pessoa ver Latência e custo de pessoas
Responsabilidade clara em domínios regulados Fadiga leva a aprovação automática
Edições e recusas viram dados de melhoria Exige estado persistido para esperas longas
Permite dar mais capacidade ao agente com segurança Não escala para volume muito alto sem amostragem

Código

As abas aplicam a política de revisão dentro da execução, com as três respostas possíveis. Python e TypeScript pausam dentro da ferramenta, no mesmo processo. LangGraph pausa o grafo com interrupt e um checkpointer, para a espera sobreviver a um restart.

Python. Use quando o loop é seu e a aprovação é síncrona, como uma confirmação no app.

import json
from dataclasses import dataclass
from typing import Callable, Literal

import anthropic

client = anthropic.Anthropic()


@dataclass
class Review:
    decision: Literal["approve", "edit", "reject"]
    edited_input: dict | None = None
    reason: str = ""
    reviewer: str = ""


POLICY: dict[str, Callable[[dict], bool]] = {  # 3. quando revisar
    "send_to_client": lambda a: True,
    "schedule_payment": lambda a: a["amount_cents"] > 50_000,
    "draft_note": lambda a: False,
}


def run_tool(block, execute, ask_human) -> dict:
    args = block.input
    if POLICY.get(block.name, lambda a: True)(args):  # ferramenta desconhecida: revisa
        preview = build_preview(block.name, args)  # 5. dados do sistema, não texto do modelo
        review: Review = ask_human(preview)  # 6. bloqueia até a decisão
        audit.log(block.name, preview, review)  # 9
        if review.decision == "reject":
            return {"type": "tool_result", "tool_use_id": block.id,
                    "content": f"Recusado pelo revisor: {review.reason}", "is_error": True}
        if review.decision == "edit":
            args = review.edited_input
    result = execute(block.name, args)  # 7
    return {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}

O que você ganhou: a política fica num lugar só, e ferramentas novas são revisadas por padrão. O que você perdeu: a espera é síncrona; para esperas de horas ou dias, salve o estado e retome com um workflow durável.

TypeScript. Use quando o agente roda no runner do SDK e o portão fica dentro da ferramenta.

import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";

type Review =
  | { decision: "approve" }
  | { decision: "edit"; edited: { amount_cents: number; key: string; date: string } }
  | { decision: "reject"; reason: string };

export function schedulePaymentTool(askHuman: (preview: object) => Promise<Review>) {
  return betaZodTool({
    name: "schedule_payment",
    description: "Agenda um pagamento. Sempre passa por revisão do usuário acima de R$ 500.",
    inputSchema: z.object({ key: z.string(), amount_cents: z.number().int().positive(), date: z.string() }),
    run: async (args) => {
      let input = args;
      if (input.amount_cents > 50_000) {
        // 5. prévia com dados do sistema
        const preview = { recebedor: await bank.lookupKeyOwner(input.key), valor: input.amount_cents, data: input.date };
        const review = await askHuman(preview); // 6
        await audit.log("schedule_payment", preview, review); // 9
        if (review.decision === "reject") return `Recusado pelo usuário: ${review.reason}`;
        if (review.decision === "edit") input = review.edited;
      }
      const result = await bank.schedulePayment(input); // 7
      return JSON.stringify(result);
    },
  });
}

O que você ganhou: o portão vive na ferramenta, então nenhum caminho do runner o pula. O que você perdeu: a política fica espalhada por ferramenta; centralize num objeto quando houver muitas.

Python + LangGraph. Use quando a pessoa pode responder minutos ou dias depois, e o processo pode reiniciar no meio.

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
from typing_extensions import TypedDict

class State(TypedDict):
    action: dict
    decision: str
    result: str


def review(state: State) -> dict:
    # 5 e 6. a prévia sai para um canal que o modelo não escreve; o grafo pausa aqui
    decision = interrupt({"preview": state["action"], "reason": "pagamento acima do limite"})
    return {"decision": decision}


def act(state: State) -> dict:
    # 7. retomar com a decisão
    if state["decision"] != "approve":
        return {"result": f"recusado: {state['decision']}"}
    return {"result": bank.schedule_payment(state["action"])}


graph = StateGraph(State)
graph.add_node("review", review)
graph.add_node("act", act)
graph.add_edge(START, "review")
graph.add_edge("review", "act")
graph.add_edge("act", END)

# 4. estado salvo. MemorySaver morre com o processo; em produção use o checkpointer do banco.
app = graph.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "pay-8831"}}

# 3. só entre neste grafo se a política marcou a ação como ponto de revisão
app.invoke({"action": payment, "decision": "", "result": ""}, config)
# mais tarde, no botão do app, não numa frase do chat:
app.invoke(Command(resume="approve"), config)

bank e payment são seus. resume aceita "approve", "reject" ou o payload editado. Grave a decisão no mesmo lugar da aba Python (passo 9).

O que você ganhou: a espera sobrevive ao restart e o sim não viaja no texto que o modelo lê. O que você perdeu: um checkpointer para operar, e a regra de qual ação pede revisão, que continua no seu código.

Suporte por framework

Conferido na documentação de interrupts do LangGraph 1.x em outubro de 2026.

Biblioteca Neste padrão Continua seu
Loop à mão A aba Python, pausa síncrona A política e o canal autenticado
SDK da Anthropic O portão dentro da ferramenta, no runner A mesma política
LangGraph interrupt mais checkpointer Quais ações pausam, e o registro da decisão
Temporal A espera longa como atividade humana, se o resto do fluxo já é um workflow durável A prévia que a pessoa vê

Teste seu entendimento

Checagem +20 XP de primeiraO usuário pré-autorizou ‘até R$ 200 para a escola, todo mês’. Chega um Pix de R$ 30 para essa chave, dentro do mês. O que a política deve fazer?

Checagem +20 XP de primeiraUma revisão fica três dias na fila. Quando a pessoa aperta ‘sim’, o processo do agente já morreu e a tarefa recomeça do zero, gerando um segundo email. O que faltou?

Checagem +20 XP de primeiraSeu bot de suporte responde política de reembolso a partir do prompt, não da base oficial. Um cliente recebe uma promessa que a empresa não honra. Qual mudança vem primeiro?

Perguntas rápidas

O que é Humano no Circuito?

Humano no Circuito (Human-in-the-Loop) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Comportamento de agentes. Pausar em pontos definidos para um humano aprovar, editar ou recusar.

Quais smells o padrão Humano no Circuito ajuda a curar?

Efeito Sem Portão.

Onde Humano no Circuito aparece na prática?

Nos cenários reais Assistente financeiro com aprovação, Bot de suporte com escalonamento humano, Assistente de saúde com guardrails e Assistente jurídico.

Para ir além

Relações

Smells que ajuda a curar 1
Refatorações que chegam aqui 1
Aparece nas trilhas 1
Buscar