Human-in-the-loop no Copilot Studio com custom connector

Por Erick Alves de Moura
Human-in-the-loop no Copilot Studio com custom connector

O ponto cego dos aprovadores nativos

Todo workflow de Copilot Studio que envolve decisão humana, aprovar uma exceção, validar um dado, confirmar uma ação irreversível, chega num ponto em que precisa parar e esperar alguém responder. A plataforma resolve isso de duas formas prontas: o conector Human Review, que dispara um e-mail, e a ação Post adaptive card and wait for a response, do conector do Teams, que envia um cartão para um chat.

Ambas funcionam bem quando o volume é baixo. O problema aparece na escala. Se uma organização roda dez, vinte, cinquenta workflows diferentes, cada um gerando pedidos de aprovação ao longo do dia, o resultado é uma caixa de entrada ou um chat lotado de mensagens isoladas, sem hierarquia, sem agrupamento, sem noção de prioridade. A pessoa responsável não enxerga o que está pendente em conjunto, só vê a próxima notificação que chegou.

Existe ainda um efeito colateral comum: para lidar com o volume, alguém cria um agente para triar os próprios pedidos de aprovação, o que gera mais pedidos que também precisam de decisão humana. O ruído não diminui, só muda de forma.

Por que e-mail e adaptive card não escalam

A limitação de fundo é estrutural, não de configuração. Tanto o Human Review quanto o card do Teams são donos do canal de entrega. Cada aprovação vira um item novo, isolado, no mesmo canal onde já circula todo o resto da comunicação da pessoa. Não há como o aprovador ver, num único lugar, tudo que está pendente entre workflows diferentes, nem como responder em lote, nem como priorizar por urgência ou por origem.

Isso não é falha de implementação da Microsoft. É o preço de usar um conector genérico: ele entrega a notificação da forma como foi desenhado para entregar, ponto a ponto. Quando o volume de decisões humanas cresce a ponto de exigir uma fila organizada, com estado, com abas de pendente e concluído, com atribuição por pessoa, o conector nativo já não é a peça certa.

A solução não é abandonar o modelo de pausa e retomada do Copilot Studio, que é sólido e eficiente. É trocar o canal de entrega por uma interface própria, mantendo o mesmo comportamento de pausar o workflow e retomá-lo quando a resposta chega.

A peça pouco conhecida: webhook action em custom connector

A pergunta técnica que sustenta essa solução é simples de formular e nada óbvia de responder: como fazer uma ação de custom connector pausar um flow?

A resposta está num padrão que a própria ação de adaptive card do Teams usa internamente, mas que também está disponível para qualquer custom connector: o webhook action. É diferente de um webhook trigger. Um trigger inicia uma nova execução de flow quando recebe uma chamada externa. Um webhook action faz o oposto: pausa a execução atual e só a retoma quando o backend chama de volta.

A parte relevante para arquitetura é o que acontece durante a espera. O flow não fica rodando, não consome polling, não segura recursos da Power Platform. Ele dehidrata completamente. Pode ficar parado minutos, horas ou dias sem custo de execução, e volta a rodar exatamente do ponto onde parou assim que a chamada de retomada chega.

O que diferencia uma action de um trigger na definição

Três elementos na definição OpenAPI do connector fazem esse comportamento existir:

  • x-ms-notification-url: true no parâmetro que recebe a URL de callback. Isso instrui a Power Platform a gerar essa URL automaticamente e injetá-la na chamada, sem que o backend precise construí-la.
  • x-ms-notification-content, definido no nível do path, descreve o schema do payload que o flow vai receber quando for retomado. É o contrato de dados da resposta humana.
  • A ausência de x-ms-trigger. Esse é o detalhe que decide tudo. Sem essa propriedade, a plataforma interpreta a operação como uma ação que pausa a execução atual, não como um gatilho que inicia uma nova.

Além disso, o connector precisa expor um endpoint DELETE para cancelamento de inscrição (webhook unsubscribe), chamado automaticamente pela plataforma quando o flow que estava esperando é cancelado antes de receber resposta.

Anatomia da definição OpenAPI

Um exemplo de definição, adaptado para um cenário de aprovação de exceções operacionais, mostra a estrutura mínima que precisa existir:

paths:
  /api/decisoes/$subscriptions:
    x-ms-notification-content:
      description: Resposta da pessoa que decidiu
      schema:
        type: object
        properties:
          decisao:
            type: string
            description: Resultado principal (aprovado, recusado, etc.)
          detalhes:
            type: object
            description: Campos adicionais preenchidos pelo humano
          decididoEm:
            type: string
            description: Data e hora da resposta
    post:
      operationId: SolicitarDecisaoHumana
      summary: Solicita decisão humana e aguarda resposta
      # Sem x-ms-trigger: isso torna a operação uma ACTION, não um trigger
      parameters:
        - name: body
          in: body
          required: true
          schema:
            type: object
            required:
              - notificationUrl
              - body
            properties:
              notificationUrl:
                type: string
                x-ms-notification-url: true
                x-ms-visibility: internal
              body:
                type: object
                required:
                  - titulo
                properties:
                  titulo:
                    type: string
                    description: Título exibido para quem vai decidir
                  mensagem:
                    type: string
                    description: Instruções ou contexto da solicitação
      responses:
        '201':
          description: Criado, aguardando resposta

  /api/decisoes/{id}:
    delete:
      operationId: CancelarDecisao
      x-ms-visibility: internal
      # Webhook unsubscribe, chamado quando o flow é cancelado

Vale notar que essa definição não faz nada sozinha. Ela apenas descreve o contrato entre o connector e a Power Platform. Quem implementa o comportamento real de receber a solicitação, guardar o estado e disparar o callback é o backend por trás do connector.

Como o fluxo completo funciona

De ponta a ponta, a sequência é a seguinte:

  1. O workflow do Copilot Studio chama a ação do custom connector, que faz um POST para o endpoint de inscrição (/api/decisoes/$subscriptions no exemplo acima).
  2. O backend responde 201 Created e armazena a solicitação, incluindo o notificationUrl gerado pela plataforma.
  3. Nesse momento o flow pausa e dehidrata. Não há polling, não há consumo de execução enquanto espera.
  4. A interface escolhida, um console web, um app interno, um canal de chat, exibe a solicitação pendente para o humano responsável.
  5. A pessoa preenche a resposta e confirma.
  6. O backend faz um POST para o notificationUrl armazenado, com o payload no formato definido em x-ms-notification-content.
  7. A Power Platform rehidrata o flow exatamente nesse ponto e a execução continua com os dados da resposta.

O backend precisa implementar apenas dois endpoints para o connector funcionar: o de inscrição, que recebe e guarda a solicitação, e o de cancelamento, chamado quando o flow que esperava é cancelado antes de qualquer resposta. Tudo o que fica entre esses dois pontos, como a fila é exibida, como o humano é notificado, se existe atribuição por pessoa ou por equipe, é decisão de produto, não restrição da plataforma.

Isso é o que abre espaço para qualquer interface: um console web simples com abas de pendente, concluído e todos, uma integração com uma ferramenta de chat corporativa, ou um módulo dentro de um sistema interno já existente. A plataforma só exige que os dois contratos de callback sejam respeitados.

Riscos que precisam entrar na conversa de governança

O ponto mais sensível dessa arquitetura é o próprio notificationUrl. Ele é assinado pela Power Platform (SAS), o que garante que não é adivinhável, mas não exige autenticação adicional para ser chamado. Na prática, qualquer requisição que chegue com essa URL válida consegue retomar o flow, independente de quem a enviou.

Isso muda o desenho de segurança do backend. Algumas decisões não são opcionais:

  • O notificationUrl deve ficar exclusivamente no lado servidor. Nunca deve ser exposto ao navegador, a um app cliente ou a qualquer camada que o usuário final consiga inspecionar.
  • Autorização de quem pode responder a qual solicitação precisa ser resolvida na aplicação, não na plataforma. A Power Platform não valida se a pessoa que respondeu era a pessoa certa.
  • Autenticação no backend (OAuth ou equivalente) é obrigatória em produção. O exemplo de referência com armazenamento em memória e túnel de desenvolvimento serve para demonstração, não para uso real.
  • Armazenamento persistente (banco de dados em vez de mapa em memória) é necessário para não perder solicitações pendentes em caso de reinício do serviço.
  • Hospedagem HTTPS estável, em Azure App Service, Azure Functions ou equivalente, é pré-requisito para qualquer cenário além de prova de conceito.

Vale registrar que esse tipo de arquitetura amplia a superfície de decisão sobre governança de agentes de IA. Quando o aprovador deixa de ser um canal controlado pela Microsoft (caixa de e-mail corporativa, Teams com autenticação Entra ID) e passa a ser uma aplicação própria, a responsabilidade por controle de acesso, trilha de auditoria e retenção de dados migra inteiramente para quem construiu o backend.

Quando vale a pena construir isso

Faz sentido investir num custom connector com webhook action quando o volume de decisões humanas já ultrapassou o que um canal genérico consegue organizar, quando existem múltiplos workflows gerando solicitações que precisam ser vistas em conjunto, ou quando o processo de negócio exige uma interface com lógica própria, como atribuição por fila, SLA de resposta ou priorização automática.

Não faz sentido para aprovações esporádicas e de baixo volume. Nesses casos, o Human Review ou o card do Teams continuam sendo a escolha mais simples e com menor custo de manutenção. A decisão de arquitetura aqui é a mesma de qualquer investimento em plataforma: construir uma camada própria só se justifica quando o ganho de controle supera o custo de manter mais um serviço rodando.

Quando o volume e a criticidade dos workflows de agentes de IA já pedem esse nível de controle sobre aprovações humanas, o desenho do connector, do contrato OpenAPI e da governança do backend costuma ser o ponto onde vale trazer quem já lidou com esse tipo de arquitetura na Power Platform.

Ver mais artigos

Entre em Contato

Vamos juntos transformar sua dor
em solução!

#moveFast