> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/casos-de-uso/agente-de-ia-com-ferramentas.md).

# Agente de IA com ferramentas

Este guia descreve um conjunto de padrões para construir agentes de IA sobre o skail: da classificação do que o usuário quer até a execução de ações em sistemas externos, passando por conversas longas, integrações que falham e pontos onde uma pessoa precisa intervir.

A ideia central é simples: **um agente é um método durável**. Você escreve a lógica como código normal; o skail garante que esse código sobreviva a falhas, reinícios e esperas de horas sem você construir filas, máquinas de estado ou serviços auxiliares. As seções a seguir mostram como montar cada parte e o que o skail faz em cada uma.

***

### 1. O modelo mental

Um agente bem estruturado quase sempre tem as mesmas camadas, independente do domínio (atendimento, cobrança, agendamento, back-office, etc.):

1. **Gatilho**: algo inicia o agente (uma mensagem, um evento, uma rota de API).
2. **Compreensão**: o agente classifica a *intenção*: o que o usuário quer.
3. **Roteamento**: a intenção seleciona um *agente especializado* para aquela tarefa.
4. **Coleta**: o agente extrai os dados necessários, usando *ferramentas* para obter sozinho os dados que faltam e conversando com o usuário quando falta algo.
5. **Ação**: o agente executa a operação e **aguarda** o resultado de sistemas externos.
6. **Resolução**: sucesso, ou *human-in-the-loop* quando precisa de uma decisão humana.

O skail entra como a **camada de durabilidade** que conecta tudo isso: ele preserva o estado entre cada passo, repete o que falhou e deixa o agente esperar por eventos externos sem consumir recursos.

```
gatilho ─▶ [ compreensão ] ─▶ [ roteamento ] ─▶ [ agente especializado ]
                                                      │
                          coleta (ferramentas + conversa) ◀─┘
                                                      │
                                  ação ─▶ aguarda externo ─▶ resolução / revisão humana
```

***

### 2. Os primitivos do skail

Você constrói agentes com poucos conceitos. Dominá-los é o suficiente para implementar qualquer um dos padrões deste guia.

#### Função durável vs. passo atômico

| Conceito                      | Quando usar                                                                                                                                                                                                                           |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Função durável** (function) | O ponto de entrada de um agente ou sub-agente. É onde ficam as esperas por evento, os atrasos e as esperas com prazo. O estado é preservado entre cada `await`, mesmo que o processo reinicie.                                        |
| **Passo atômico** (command)   | Uma operação pontual e idempotente: uma chamada HTTP, uma escrita no banco, uma chamada ao LLM, o envio de uma mensagem. Se falhar, o skail repete **apenas o passo**, sem refazer o fluxo inteiro nem repetir trabalho já concluído. |

{% hint style="info" %}
**Regra de ouro:** isole cada ação não determinística (I/O, chamada de LLM, integração) em seu próprio passo atômico. É isso que torna o agente retomável e barato de repetir quando algo falha no meio.
{% endhint %}

#### Esperar por sistemas externos, sem segurar recursos

| Primitivo                       | Para quê                                                                                                                                         |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Espera por evento (nome, id)    | Pausa o agente até alguém disparar o evento `(nome, id)` com o payload esperado. Consome **0 CPU** enquanto espera: pode durar segundos ou dias. |
| Delay                           | Um atraso durável: timeout, polling, backoff.                                                                                                    |
| Esperar o primeiro que terminar | Retorna o que terminar primeiro. É o padrão **"evento ou timeout"**.                                                                             |

A espera por evento com prazo (o evento ou o delay, o que vier primeiro) é o padrão mais importante: **espere por um evento externo, mas nunca para sempre.**

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
// Aguarda um retorno externo por até 1 minuto; se não vier, segue por outro caminho.
var retorno = WaitForEvent<RespostaExterna>("EVENTO_RETORNO", correlacaoId);
var timeout = DelayMinutes(1);

var resultado = await WhenAny(retorno, timeout);
if (resultado == retorno)
    await Processar(await retorno);     // o sinal chegou; Processar é um command
else
    await ConsultarPorPolling(correlacaoId);  // timeout → plano B
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface RespostaExterna {
  Concluido: boolean;
}

class Exemplo {
  @SkailFunction()
  async AguardarRetorno(correlacaoId: string): Promise<void> {
    // Aguarda um retorno externo por até 1 minuto; se não vier, segue por outro caminho.
    const retorno = Skail.waitForEvent<RespostaExterna>("EVENTO_RETORNO", correlacaoId);
    const timeout = Skail.delayMinutes(1);

    const resultado = await Skail.whenAny(retorno, timeout);
    if (resultado !== undefined) {
      await this.Processar(resultado); // o sinal chegou
    } else {
      await this.ConsultarPorPolling(correlacaoId); // timeout → plano B
    }
  }

  @SkailCommand()
  async Processar(resposta: RespostaExterna): Promise<void> {
    /* grava ou repassa a resposta (banco, HTTP) */
  }

  @SkailFunction()
  async ConsultarPorPolling(correlacaoId: string): Promise<void> {
    /* polling durável (seção 7) */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Iniciar o agente e liberar as esperas

Um agente depende de duas chamadas HTTP à API do skail, feitas de fora da aplicação que roda o agente:

* **Disparar uma instância**: um `POST` no endpoint de trigger inicia uma nova execução da função durável, passando um GUID que identifica a execução. Tipicamente vem de uma rota de API, de um webhook de entrada ou de um agendador.
* **Disparar um evento**: um `POST` na [API de eventos](/referencia/api-http/post-fire.md) entrega um payload e **destrava** a espera por evento correspondente. Tipicamente vem de um callback externo, da chegada de uma nova mensagem do usuário, ou de uma aprovação numa interface.

O **identificador de correlação** (o `id` do evento) é o que liga o evento externo à execução certa. Escolha uma chave estável e única por fluxo:

* o id da conversa;
* o id da transação;
* o id do pedido.

***

### 3. Compreensão: classificação de intenção

Antes de agir, o agente precisa entender **o que o usuário quer**. Trate isso como um passo dedicado, com saída estruturada; não deixe a interpretação espalhada pela lógica.

**Recomendações:**

* **Declare as intenções como dados**, não como código. Uma lista de `(chave, descrição)` é fácil de manter, revisar e expandir. A descrição ensina o modelo a reconhecer cada caso.
* **Force saída estruturada** (JSON Schema estrito) e `temperature = 0`. A classificação deve ser determinística e validável, não texto livre.
* **Devolva sempre três coisas:** a intenção, um sinal de *incompleto* (o agente ainda não tem clareza?) e uma *mensagem para o usuário* (o que perguntar quando estiver incompleto).
* **Mantenha o classificador genérico.** Ele recebe o histórico, a lista de opções e um contexto ("você é um assistente de…"); o mesmo classificador serve a qualquer agente: você só troca as opções e o contexto.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
// Intenções declaradas como dados, específicas do seu domínio.
var intencoes = new[]
{
    ("executar_acao",    "usuário quer realizar a operação principal. Sinais: ..."),
    ("consultar_status", "usuário quer saber o andamento de algo já iniciado."),
    ("cancelar",         "usuário quer desfazer ou cancelar uma operação."),
    ("duvida",           "mensagem vaga ou saudação. Marque incompleto e pergunte o que precisa."),
    ("fora_de_escopo",   "assunto não relacionado. Responda educadamente e encerre."),
};

var resultado = await classificarIntencao(historico, intencoes, contexto);
// resultado: { intencao, incompleto, mensagemParaUsuario }
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { SkailCommand } from "@skail/runtime";

interface Mensagem {
  Papel: "usuario" | "agente";
  Texto: string;
}

interface IntencaoResult {
  Chave: string;
  Incompleto: boolean;
  MensagemParaUsuario?: string;
}

declare function classificarIntencao(
  historico: Mensagem[],
  intencoes: readonly (readonly [chave: string, descricao: string])[],
  contexto: string,
): Promise<IntencaoResult>;

// Intenções declaradas como dados, específicas do seu domínio.
const intencoes = [
  ["executar_acao", "usuário quer realizar a operação principal. Sinais: ..."],
  ["consultar_status", "usuário quer saber o andamento de algo já iniciado."],
  ["cancelar", "usuário quer desfazer ou cancelar uma operação."],
  ["duvida", "mensagem vaga ou saudação. Marque incompleto e pergunte o que precisa."],
  ["fora_de_escopo", "assunto não relacionado. Responda educadamente e encerre."],
] as const;

class Classificador {
  @SkailCommand()
  async ClassificarIntencao(historico: Mensagem[], contexto: string): Promise<IntencaoResult> {
    const resultado = await classificarIntencao(historico, intencoes, contexto);
    // resultado: { Chave, Incompleto, MensagemParaUsuario }
    return resultado;
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

A classificação roda como um **passo atômico** (command): é uma chamada de LLM isolada e retomável.

***

### 4. Roteamento: escolhendo o agente especializado

Um único agente "que faz tudo" fica frágil e difícil de evoluir. Prefira um **roteador simples**: a partir da intenção, ele passa o trabalho para um **agente especializado**, cada um focado em uma tarefa, com seu próprio prompt, suas ferramentas e suas regras.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask Atender(string conversaId, string primeiraMensagem)
{
    await SalvarMensagemDoUsuario(conversaId, primeiraMensagem);   // passo atômico

    var conversa = await ColetarContexto(conversaId);
    var intencao = await ClassificarIntencao(conversa);   // passo atômico

    // Loop de esclarecimento enquanto não há clareza (ver seção 6).
    intencao = await EsclarecerAteEntender(conversaId, intencao);

    switch (intencao.Chave)
    {
        case "executar_acao":    await AgenteDeExecucao(conversaId);   break;
        case "consultar_status": await AgenteDeConsulta(conversaId);   break;
        case "cancelar":         await AgenteDeCancelamento(conversaId); break;
        default:                 await ResponderForaDeEscopo(conversaId); break;
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { SkailCommand, SkailFunction } from "@skail/runtime";

interface Mensagem {
  Papel: "usuario" | "agente";
  Texto: string;
}

interface Intencao {
  Chave: string;
  Incompleto: boolean;
  MensagemParaUsuario?: string;
}

declare function salvarMensagem(conversaId: string, texto: string): Promise<void>;
declare function coletarContexto(conversaId: string): Promise<Mensagem[]>;
declare function classificar(conversa: Mensagem[]): Promise<Intencao>;
declare function responder(conversaId: string, texto: string): Promise<void>;

class Roteador {
  @SkailFunction()
  async Atender(conversaId: string, primeiraMensagem: string): Promise<void> {
    await this.SalvarMensagemDoUsuario(conversaId, primeiraMensagem); // passo atômico

    const conversa = await this.ColetarContexto(conversaId);
    let intencao = await this.ClassificarIntencao(conversa); // passo atômico

    // Loop de esclarecimento enquanto não há clareza (ver seção 6).
    intencao = await this.EsclarecerAteEntender(conversaId, intencao);

    switch (intencao.Chave) {
      case "executar_acao":
        await this.AgenteDeExecucao(conversaId);
        break;
      case "consultar_status":
        await this.AgenteDeConsulta(conversaId);
        break;
      case "cancelar":
        await this.AgenteDeCancelamento(conversaId);
        break;
      default:
        await this.ResponderForaDeEscopo(conversaId);
        break;
    }
  }

  @SkailCommand()
  async SalvarMensagemDoUsuario(conversaId: string, texto: string): Promise<void> {
    await salvarMensagem(conversaId, texto);
  }

  @SkailCommand()
  async ColetarContexto(conversaId: string): Promise<Mensagem[]> {
    return coletarContexto(conversaId);
  }

  @SkailCommand()
  async ClassificarIntencao(conversa: Mensagem[]): Promise<Intencao> {
    return classificar(conversa);
  }

  @SkailCommand()
  async ResponderForaDeEscopo(conversaId: string): Promise<void> {
    await responder(conversaId, "Posso ajudar a executar, consultar ou cancelar uma operação.");
  }

  @SkailFunction()
  async EsclarecerAteEntender(conversaId: string, intencao: Intencao): Promise<Intencao> {
    return intencao; /* loop de esclarecimento (seção 6) */
  }

  @SkailFunction()
  async AgenteDeExecucao(conversaId: string): Promise<void> {}

  @SkailFunction()
  async AgenteDeConsulta(conversaId: string): Promise<void> {}

  @SkailFunction()
  async AgenteDeCancelamento(conversaId: string): Promise<void> {}
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

**Por que isso funciona bem no skail:**

* Cada agente especializado é também uma function, durável e retomável por si só. Um fluxo de execução longo não trava nem complica os fluxos curtos.
* O roteador permanece simples: classifica e delega. A complexidade fica contida dentro de cada especialista.
* Adicionar uma capacidade nova é adicionar uma intenção e um agente, sem mexer no resto.

***

### 5. Coleta: ferramentas (tool use)

O agente especializado precisa reunir os dados para agir. Em vez de interrogar o usuário campo a campo, dê a ele **ferramentas** para obter sozinho os dados que faltam:

* buscar um cadastro;
* validar um documento;
* consultar uma referência;
* reaproveitar um registro anterior.

**Recomendações:**

* **Modele cada ferramenta como uma função com schema claro** (nome, descrição, parâmetros). A descrição diz ao modelo *quando* usar a ferramenta.
* **Prefira completar a perguntar.** Se um dado pode ser derivado de outro (endereço a partir de um CEP, cadastro a partir de um documento), exponha isso como ferramenta: a conversa fica mais curta e natural.
* **Cada ferramenta é idempotente.** No esqueleto, as ferramentas rodam dentro do command `ExtrairDados`, então são métodos comuns, que não são marcados como command nem como function: um command não chama outro command nem uma function. Se o command de extração rodar de novo, as ferramentas rodam de novo; por isso precisam ser idempotentes.
* **Devolva resultado estruturado**, indicando o que está *completo* e o que ainda *falta*, mais uma mensagem para pedir ao usuário só o que realmente não dá para descobrir sozinho.
* **Inclua rastreabilidade.** Registrar de onde veio cada dado (qual mensagem, qual ferramenta) facilita auditoria e depuração.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
// Ferramentas oferecidas ao modelo: ele decide quando chamar cada uma.
var ferramentas = new[]
{
    Ferramenta("buscar_cadastro",   "Recupera dados já conhecidos por um identificador."),
    Ferramenta("validar_documento", "Valida um documento antes de prosseguir."),
    Ferramenta("completar_por_referencia", "Completa campos a partir de um dado-chave."),
    Ferramenta("reusar_registro",   "Usa um registro anterior como base e aplica só as mudanças pedidas."),
};

var extracao = await extrairDados(conversa, ferramentas);
// extracao: { dados, incompleto, mensagemParaUsuario, evidencias }
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { SkailCommand } from "@skail/runtime";

interface Mensagem {
  Papel: "usuario" | "agente";
  Texto: string;
}

interface Ferramenta {
  Nome: string;
  Descricao: string;
}

interface Extracao {
  Dados: Record<string, unknown>;
  Incompleto: boolean;
  MensagemParaUsuario?: string;
  Evidencias: string[];
}

declare function extrairDados(conversa: Mensagem[], ferramentas: Ferramenta[]): Promise<Extracao>;

function ferramenta(nome: string, descricao: string): Ferramenta {
  return { Nome: nome, Descricao: descricao };
}

class Coleta {
  @SkailCommand()
  async ExtrairDados(conversa: Mensagem[]): Promise<Extracao> {
    // Ferramentas oferecidas ao modelo: ele decide quando chamar cada uma.
    const ferramentas = [
      ferramenta("buscar_cadastro", "Recupera dados já conhecidos por um identificador."),
      ferramenta("validar_documento", "Valida um documento antes de prosseguir."),
      ferramenta("completar_por_referencia", "Completa campos a partir de um dado-chave."),
      ferramenta("reusar_registro", "Usa um registro anterior como base e aplica só as mudanças pedidas."),
    ];

    const extracao = await extrairDados(conversa, ferramentas);
    // extracao: { Dados, Incompleto, MensagemParaUsuario, Evidencias }
    return extracao;
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Multimodal:** se o usuário envia PDFs, imagens, planilhas ou áudio, faça um pré-processamento que extraia o conteúdo (texto, visão, transcrição) **antes** da etapa de raciocínio, entregando tudo como contexto. Para o agente, "ler um documento" vira parte natural da coleta.

**Custo:** use o modelo certo para cada passo. Classificação e textos simples pedem um modelo rápido e barato; raciocínio com ferramentas e anexos pede um modelo mais capaz. Decida isso na camada de IA, não no workflow.
{% endhint %}

***

### 6. Conversas longas: o loop de esclarecimento

Raramente o usuário entrega tudo de uma vez. O padrão é **responder → esperar a próxima mensagem → reavaliar**, repetindo até ter o necessário, com um limite de inatividade para não esperar indefinidamente.

<figure><img src="https://1898946878-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJmS5ILPG5x2x5j0WyiV%2Fuploads%2FxWIyTEYRq0yoMqJwUmxv%2Fap-agentes-conversa.svg?alt=media" alt="EsclarecerAteEntender: Responder responde ao usuário e a function espera ao mesmo tempo o evento EVENTO_NOVA_MENSAGEM e um delay de 15 minutos de inatividade. Se o prazo vence, EncerrarConversa encerra a conversa e a function retorna. Se a mensagem chega, disparada por POST /api/v1/fire/EVENTO_NOVA_MENSAGEM/{conversaId} quando o usuário responde, SalvarMensagemDoUsuario grava a mensagem e Reavaliar reavalia a conversa, repetindo o ciclo enquanto o resultado estiver incompleto."><figcaption></figcaption></figure>

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
while (resultado.Incompleto)
{
    await Responder(conversaId, resultado.MensagemParaUsuario);

    var timeout      = DelayMinutes(15);
    var novaMensagem = WaitForEvent<MensagemRecebida>("EVENTO_NOVA_MENSAGEM", conversaId);

    if (await WhenAny(timeout, novaMensagem) == timeout)
    {
        await EncerrarConversa(conversaId);
        return;
    }

    // O texto chega no payload do evento: grave no histórico antes de reavaliar.
    var recebida = await novaMensagem;
    await SalvarMensagemDoUsuario(conversaId, recebida.Texto);

    resultado = await Reavaliar(conversaId);   // reclassifica ou re-extrai
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface MensagemRecebida {
  Texto: string;
}

interface Resultado {
  Incompleto: boolean;
  MensagemParaUsuario: string;
}

declare function responder(conversaId: string, texto: string): Promise<void>;
declare function encerrarConversa(conversaId: string): Promise<void>;
declare function salvarMensagemDoUsuario(conversaId: string, texto: string): Promise<void>;
declare function reavaliar(conversaId: string): Promise<Resultado>;

class Conversa {
  @SkailFunction()
  async EsclarecerAteEntender(conversaId: string, inicial: Resultado): Promise<void> {
    let resultado = inicial;

    while (resultado.Incompleto) {
      await this.Responder(conversaId, resultado.MensagemParaUsuario);

      const timeout = Skail.delayMinutes(15);
      const novaMensagem = Skail.waitForEvent<MensagemRecebida>("EVENTO_NOVA_MENSAGEM", conversaId);

      const recebida = await Skail.whenAny(timeout, novaMensagem);
      if (recebida === undefined) {
        await this.EncerrarConversa(conversaId);
        return;
      }

      // O texto chega no payload do evento: grave no histórico antes de reavaliar.
      await this.SalvarMensagemDoUsuario(conversaId, recebida.Texto);

      resultado = await this.Reavaliar(conversaId); // reclassifica ou re-extrai
    }
  }

  @SkailCommand()
  async Responder(conversaId: string, mensagem: string): Promise<void> {
    await responder(conversaId, mensagem);
  }

  @SkailCommand()
  async EncerrarConversa(conversaId: string): Promise<void> {
    await encerrarConversa(conversaId);
  }

  @SkailCommand()
  async SalvarMensagemDoUsuario(conversaId: string, texto: string): Promise<void> {
    await salvarMensagemDoUsuario(conversaId, texto);
  }

  @SkailCommand()
  async Reavaliar(conversaId: string): Promise<Resultado> {
    return reavaliar(conversaId);
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

O ponto-chave: durante a espera, o agente **não consome CPU nem memória**. Ele pode ficar pausado por minutos ou horas aguardando a resposta do usuário e retoma exatamente de onde parou quando a mensagem chega, porque o estado é durável.

***

### 7. Ação e espera por sistemas externos

Quando o agente executa a operação principal, o resultado muitas vezes não é imediato: depende de um provedor que responde por webhook, de uma fila de processamento, de uma confirmação que pode levar minutos.

O padrão recomendado combina **webhook com fallback para polling**: assim você não fica refém de o callback chegar:

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask ExecutarOperacao(Pedido pedido)
{
    await EnviarParaProvedor(pedido);                         // passo atômico

    var retorno = WaitForEvent<Resultado>("EVENTO_RETORNO", pedido.Id);
    var timeout = DelayMinutes(1);

    var resultado = await WhenAny(retorno, timeout) == retorno
        ? await retorno                                       // o webhook chegou
        : await ConsultarPorPolling(pedido.Id);               // timeout → polling durável

    await Persistir(resultado);                               // passo atômico

    if (!resultado.Concluido)
        await AguardarRevisaoHumana(pedido.Id, resultado);    // ver seção 8
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface Pedido {
  Id: string;
}

interface Resultado {
  Concluido: boolean;
  EhFinal: boolean;
}

declare function enviarParaProvedor(pedido: Pedido): Promise<void>;
declare function consultarProvedor(pedidoId: string): Promise<Resultado>;
declare function persistir(resultado: Resultado): Promise<void>;

const TentativasDePolling = 20; // 20 x 30 s = 10 minutos

class Operacao {
  @SkailFunction()
  async ExecutarOperacao(pedido: Pedido): Promise<void> {
    await this.EnviarParaProvedor(pedido); // passo atômico

    const retorno = Skail.waitForEvent<Resultado>("EVENTO_RETORNO", pedido.Id);
    const timeout = Skail.delayMinutes(1);

    const retornoOuTimeout = await Skail.whenAny(retorno, timeout);
    const resultado =
      retornoOuTimeout !== undefined
        ? retornoOuTimeout // o webhook chegou
        : await this.ConsultarPorPolling(pedido.Id); // timeout → polling durável

    await this.Persistir(resultado); // passo atômico

    if (!resultado.Concluido) {
      await this.AguardarRevisaoHumana(pedido.Id, resultado); // ver seção 8
    }
  }

  @SkailCommand()
  async EnviarParaProvedor(pedido: Pedido): Promise<void> {
    await enviarParaProvedor(pedido);
  }

  @SkailCommand()
  async Persistir(resultado: Resultado): Promise<void> {
    await persistir(resultado);
  }

  @SkailCommand()
  async ConsultarProvedor(pedidoId: string): Promise<Resultado> {
    return consultarProvedor(pedidoId);
  }

  // Número fixo de tentativas: o laço (loop) se repete igual no replay.
  @SkailFunction()
  async ConsultarPorPolling(pedidoId: string): Promise<Resultado> {
    let atual: Resultado = { Concluido: false, EhFinal: false };
    for (let tentativa = 0; tentativa < TentativasDePolling; tentativa++) {
      atual = await this.ConsultarProvedor(pedidoId);
      if (atual.EhFinal) return atual;
      await Skail.delaySeconds(30);
    }
    return atual;
  }

  @SkailFunction()
  async AguardarRevisaoHumana(pedidoId: string, pendente: Resultado): Promise<void> {}
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

O **polling durável** é só um laço (loop) com um número fixo de tentativas e um delay entre elas: cada iteração é retomável, então um reinício no meio não recomeça do zero nem perde a contagem.

{% hint style="warning" %}
Não controle o prazo verificando se um delay sem `await` já terminou: esse resultado pode ser outro no replay.
{% endhint %}

***

### 8. Human-in-the-loop

Alguns fluxos precisam de uma decisão humana:

* aprovar um valor;
* corrigir um dado;
* liberar uma operação sensível.

No skail, isso é apenas mais uma espera por evento.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask AguardarRevisaoHumana(string pedidoId, Resultado pendente)
{
    await RegistrarPedidoDeRevisao(pendente);   // notifica quem decide

    // O agente pausa aqui, sem consumir CPU, até alguém aprovar/corrigir na interface.
    var decisao = await WaitForEvent<Decisao>("EVENTO_REVISAO", pedidoId);

    // O PedidoCorrigido traz Id próprio para o reenvio (ex.: {pedidoId}-rev1), que o provedor devolve no webhook.
    await ExecutarOperacao(decisao.PedidoCorrigido);   // reenvia pelo caminho da seção 7
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface Pedido {
  Id: string;
}

interface Resultado {
  Concluido: boolean;
}

interface Decisao {
  PedidoCorrigido: Pedido;
}

declare function registrarPedidoDeRevisao(pendente: Resultado): Promise<void>;

class Revisao {
  @SkailFunction()
  async AguardarRevisaoHumana(pedidoId: string, pendente: Resultado): Promise<void> {
    await this.RegistrarPedidoDeRevisao(pendente); // notifica quem decide

    // O agente pausa aqui, sem consumir CPU, até alguém aprovar/corrigir na interface.
    const decisao = await Skail.waitForEvent<Decisao>("EVENTO_REVISAO", pedidoId);

    // O PedidoCorrigido traz Id próprio para o reenvio (ex.: {pedidoId}-rev1), que o provedor devolve no webhook.
    await this.ExecutarOperacao(decisao.PedidoCorrigido); // reenvia pelo caminho da seção 7
  }

  @SkailCommand()
  async RegistrarPedidoDeRevisao(pendente: Resultado): Promise<void> {
    await registrarPedidoDeRevisao(pendente);
  }

  @SkailFunction()
  async ExecutarOperacao(pedido: Pedido): Promise<void> {
    /* envia, espera o webhook ou faz polling e persiste (seção 7) */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Não há thread parada, nem job rodando em loop, nem timeout artificial te obrigando a decidir rápido. O agente fica genuinamente suspenso até a interface (ou outro sistema) disparar o evento de revisão. Isso torna aprovações de horas ou dias triviais de implementar.

O pedido corrigido precisa de um `Id` próprio, por exemplo `{pedidoId}-rev1`, definido por quem faz a revisão, enviado ao provedor e devolvido no webhook: a espera por `EVENTO_RETORNO` da primeira tentativa continua pendente com o id original, e o retorno do reenvio tem de chegar à espera nova. Se o reenvio também precisar de revisão, a nova espera por `EVENTO_REVISAO` usa esse mesmo `Id`, e é nele que a interface dispara.

***

### 9. Como o skail facilita tudo isso

Recapitulando o que você **não** precisa construir, porque o skail entrega por padrão:

| Necessidade do agente                 | O que o skail oferece                                                       |
| ------------------------------------- | --------------------------------------------------------------------------- |
| Não perder o que estava fazendo       | **Estado durável** preservado entre cada `await`, mesmo após reinício       |
| Sobreviver a falhas transitórias      | **Retries automáticos** por passo atômico, sem refazer o fluxo              |
| Esperar por sistemas externos         | Espera por evento com **0 CPU**, por segundos ou dias                       |
| Aprovações e revisões humanas         | Human-in-the-loop nativo, sem jobs nem polling artificial                   |
| "Evento ou timeout", polling, backoff | Delay e esperar o primeiro que terminar, como primitivos de primeira classe |
| Entender o que aconteceu em produção  | Trace completo de cada execução, com reprodução determinística              |
| Coordenar tudo isso                   | Um **método normal**, sem filas, brokers ou microsserviços novos            |

O resultado: a sua equipe escreve a **lógica do agente** (intenções, ferramentas, regras do domínio) e recebe pronta a infraestrutura de que um agente precisa para rodar em produção com confiabilidade.

***

### 10. Montando o seu agente

Um roteiro prático, reunindo os padrões acima:

1. **Mapeie as intenções** do seu domínio como uma lista de `(chave, descrição)` e defina o contexto do assistente.
2. **Escreva o roteador** como uma function: classifica a intenção, faz o loop de esclarecimento e delega para o agente especializado.
3. **Para cada intenção, crie um agente especializado** (uma function) com o seu prompt, suas ferramentas e seu resultado estruturado (`completo? / falta o quê? / o que perguntar`).
4. **Modele as ferramentas** que permitem ao agente obter sozinho os dados que faltam; implemente cada uma como método comum chamado pelo command de extração.
5. **Defina seus eventos e a chave de correlação.** Use a espera por evento no workflow e dispare os eventos, pela API HTTP, a partir de webhooks, novas mensagens e aprovações.
6. **Trate a ação principal** com o padrão webhook-ou-polling e adicione human-in-the-loop onde uma decisão humana for necessária.
7. **Conecte os gatilhos**: uma rota/serviço que dispara a instância do agente e os pontos que disparam os eventos de desbloqueio.

Comece pequeno (uma intenção, um agente especializado, uma ferramenta) e expanda. Como cada parte é um método durável independente, o agente cresce sem ficar difícil de manter: novas capacidades são novas intenções e novos especialistas.

***

### 11. Esqueleto completo

O exemplo abaixo conecta todos os padrões num único agente. É um **modelo genérico**: troque o domínio, as intenções, as ferramentas e as integrações pelas suas. A estrutura (roteador → especialistas, loop de conversa, ação com espera, human-in-the-loop) permanece a mesma para qualquer agente.

#### O agente

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
using Skail.Platform.Runtime;
using Skail.Platform.Runtime.Threading;
using static Skail.Platform.Runtime.Threading.SkailTask;

// Payload do evento de nova mensagem (corpo do fire: { "Texto": "..." }).
public sealed record MensagemRecebida(string Texto);

public sealed class Agente(
    IIntencaoExtractor intencao,   // classificador genérico (seção 3)
    IDadosExtractor    dados,      // extração com ferramentas (seção 5)
    IProvedorExterno   provedor,   // integração que executa a ação (seção 7)
    IConversaStore     store,      // histórico, estado e persistência
    ICanalSaida        canal)      // como responder ao usuário (WhatsApp, e-mail, chat...)
{
    private const ulong TimeoutInatividadeMin = 15;
    private const int   TentativasDePolling   = 20;   // 20 x 30 s = 10 minutos
    private const string Contexto = "assistente que ajuda o usuário a realizar e acompanhar operações";

    // As capacidades do agente, declaradas como dados (seção 3).
    private static readonly (string Chave, string Descricao)[] Intencoes =
    [
        ("executar_acao",    "usuário quer realizar a operação principal. Sinais: ..."),
        ("consultar_status", "usuário quer saber o andamento de algo já iniciado."),
        ("cancelar",         "usuário quer desfazer ou cancelar uma operação."),
        ("duvida",           "mensagem vaga ou saudação. Marque incompleto e pergunte o que precisa."),
        ("fora_de_escopo",   "assunto não relacionado. Responda educadamente e encerre."),
    ];

    // ── Roteador: ponto de entrada do agente (disparado por um trigger) ──────────
    [SkailFunction]
    public async SkailTask Atender(string conversaId, string primeiraMensagem)
    {
        // A primeira mensagem chega pelo trigger; o agente grava no histórico antes de classificar.
        await SalvarMensagemDoUsuario(conversaId, primeiraMensagem);

        var historico = await CarregarHistorico(conversaId);
        var intent    = await ClassificarIntencao(historico);

        // Loop de esclarecimento: conversa até entender o que o usuário quer (seção 6).
        while (intent.Incompleto)
        {
            if (!await AguardarResposta(conversaId, intent.MensagemParaUsuario)) return;
            historico = await CarregarHistorico(conversaId);
            intent    = await ClassificarIntencao(historico);
        }

        // Roteamento para o agente especializado (seção 4).
        switch (intent.Chave)
        {
            case "executar_acao":    await AgenteDeExecucao(conversaId);   break;
            case "consultar_status": await AgenteDeConsulta(conversaId);   break;
            case "cancelar":         await AgenteDeCancelamento(conversaId); break;
            default:
                await Responder(conversaId, "Posso ajudar a executar, consultar ou cancelar uma operação.");
                await EncerrarConversa(conversaId);
                break;
        }
    }

    // ── Agente especializado: coleta os dados e dispara a ação ───────────────────
    [SkailFunction]
    public async SkailTask AgenteDeExecucao(string conversaId)
    {
        var extracao = await ExtrairDados(conversaId);

        // Loop de coleta: pede só o que não dá para descobrir sozinho (seções 5 e 6).
        while (extracao.Incompleto)
        {
            if (!await AguardarResposta(conversaId, extracao.MensagemParaUsuario)) return;
            extracao = await ExtrairDados(conversaId);
        }

        await Responder(conversaId, "Recebi tudo. Já estou processando, um instante.");
        await ExecutarOperacao(extracao.Pedido);
        await EncerrarConversa(conversaId);
    }

    // ── Ação principal: webhook com fallback para polling (seção 7) ──────────────
    [SkailFunction]
    public async SkailTask ExecutarOperacao(Pedido pedido)
    {
        await EnviarParaProvedor(pedido);

        var retorno = WaitForEvent<Resultado>("EVENTO_RETORNO", pedido.Id);
        var timeout = DelayMinutes(1);

        var resultado = await WhenAny(retorno, timeout) == retorno
            ? await retorno                              // o webhook chegou
            : await ConsultarPorPolling(pedido.Id);      // timeout → polling durável

        await Persistir(resultado);

        if (!resultado.Concluido)
            await AguardarRevisaoHumana(pedido.Id, resultado);   // seção 8
    }

    // ── Polling durável: cada iteração é retomável ───────────────────────────────
    // Número fixo de tentativas: o laço (loop) se repete igual no replay.
    [SkailFunction]
    public async SkailTask<Resultado> ConsultarPorPolling(string pedidoId)
    {
        var atual = new Resultado();

        for (var tentativa = 0; tentativa < TentativasDePolling; tentativa++)
        {
            atual = await ConsultarProvedor(pedidoId);
            if (atual.EhFinal) return atual;
            await DelaySeconds(30);
        }
        return atual;
    }

    // ── Human-in-the-loop: pausa até uma decisão humana (seção 8) ────────────────
    [SkailFunction]
    public async SkailTask AguardarRevisaoHumana(string pedidoId, Resultado pendente)
    {
        await RegistrarPedidoDeRevisao(pendente);

        var decisao = await WaitForEvent<Decisao>("EVENTO_REVISAO", pedidoId);

        // O pedido corrigido passa pelo mesmo caminho: espera o webhook, cai para polling e persiste.
        // Ele traz Id próprio para o reenvio (ex.: {pedidoId}-rev1), que o provedor devolve no webhook.
        await ExecutarOperacao(decisao.PedidoCorrigido);
    }

    // ── Conversa: responde e espera a próxima mensagem (ou desiste) ──────────────
    // Retorna false quando a conversa deve terminar por inatividade.
    [SkailFunction]
    public async SkailTask<bool> AguardarResposta(string conversaId, string? mensagem)
    {
        await Responder(conversaId, mensagem ?? "Me conta um pouco mais sobre o que você precisa.");

        var timeout = DelayMinutes(TimeoutInatividadeMin);
        var msg     = WaitForEvent<MensagemRecebida>("EVENTO_NOVA_MENSAGEM", conversaId);

        if (await WhenAny(timeout, msg) == timeout)
        {
            await Responder(conversaId, "Vou encerrar por inatividade. É só me chamar quando quiser continuar.");
            await EncerrarConversa(conversaId);
            return false;
        }

        // A mensagem chega no payload do evento; o agente grava no histórico em um passo atômico.
        var recebida = await msg;
        await SalvarMensagemDoUsuario(conversaId, recebida.Texto);
        return true;
    }

    // Grava a resposta no histórico e envia ao usuário em passos separados:
    // se o envio falhar, só o envio é repetido, e a mensagem não é gravada de novo.
    [SkailFunction]
    public async SkailTask Responder(string conversaId, string mensagem)
    {
        await SalvarMensagemDoAgente(conversaId, mensagem);
        await EnviarMensagem(conversaId, mensagem);
    }

    // ── Passos atômicos: cada operação de I/O isolada e retomável (seção 2) ──────
    [SkailCommand] public async SkailTask<List<Mensagem>> CarregarHistorico(string id) => await store.Historico(id);
    [SkailCommand] public async SkailTask<IntencaoResult> ClassificarIntencao(List<Mensagem> h) => await intencao.Classificar(h, Intencoes, Contexto);
    [SkailCommand] public async SkailTask<DadosResult>    ExtrairDados(string id) => await dados.Extrair(await store.Historico(id));
    [SkailCommand] public async SkailTask EnviarParaProvedor(Pedido p) => await provedor.Enviar(p);
    [SkailCommand] public async SkailTask<Resultado> ConsultarProvedor(string id) => await provedor.Consultar(id);
    [SkailCommand] public async SkailTask Persistir(Resultado r) => await store.Salvar(r);
    [SkailCommand] public async SkailTask RegistrarPedidoDeRevisao(Resultado r) => await store.MarcarParaRevisao(r);
    [SkailCommand] public async SkailTask SalvarMensagemDoUsuario(string id, string texto) => await store.SalvarMensagemDoUsuario(id, texto);
    [SkailCommand] public async SkailTask SalvarMensagemDoAgente(string id, string msg) => await store.SalvarMensagem(id, msg);
    [SkailCommand] public async SkailTask EnviarMensagem(string id, string msg) => await canal.Enviar(id, msg);
    [SkailCommand] public async SkailTask EncerrarConversa(string id) => await store.Encerrar(id);

    // Sub-agentes de consulta/cancelamento seguem o mesmo padrão do AgenteDeExecucao.
    [SkailFunction] public async SkailTask AgenteDeConsulta(string conversaId)     { /* extrai identificador, consulta, responde */ }
    [SkailFunction] public async SkailTask AgenteDeCancelamento(string conversaId) { /* extrai identificador, cancela, confirma */ }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";
import {
  canal,
  dados,
  intencao,
  provedor,
  store,
  type DadosResult,
  type IntencaoResult,
  type Mensagem,
  type Pedido,
  type Resultado,
} from "./agente-integracoes.js";

// Payload do evento de nova mensagem (corpo do fire: { "Texto": "..." }).
export interface MensagemRecebida {
  Texto: string;
}

export interface Decisao {
  PedidoCorrigido: Pedido;
}

const TimeoutInatividadeMin = 15;
const TentativasDePolling = 20; // 20 x 30 s = 10 minutos
const Contexto = "assistente que ajuda o usuário a realizar e acompanhar operações";

// As capacidades do agente, declaradas como dados (seção 3).
const Intencoes = [
  ["executar_acao", "usuário quer realizar a operação principal. Sinais: ..."],
  ["consultar_status", "usuário quer saber o andamento de algo já iniciado."],
  ["cancelar", "usuário quer desfazer ou cancelar uma operação."],
  ["duvida", "mensagem vaga ou saudação. Marque incompleto e pergunte o que precisa."],
  ["fora_de_escopo", "assunto não relacionado. Responda educadamente e encerre."],
] as const;

export class Agente {
  // ── Roteador: ponto de entrada do agente (disparado por um trigger) ──────────
  @SkailFunction()
  async Atender(conversaId: string, primeiraMensagem: string): Promise<void> {
    // A primeira mensagem chega pelo trigger; o agente grava no histórico antes de classificar.
    await this.SalvarMensagemDoUsuario(conversaId, primeiraMensagem);

    let historico = await this.CarregarHistorico(conversaId);
    let intent = await this.ClassificarIntencao(historico);

    // Loop de esclarecimento: conversa até entender o que o usuário quer (seção 6).
    while (intent.Incompleto) {
      if (!(await this.AguardarResposta(conversaId, intent.MensagemParaUsuario))) return;
      historico = await this.CarregarHistorico(conversaId);
      intent = await this.ClassificarIntencao(historico);
    }

    // Roteamento para o agente especializado (seção 4).
    switch (intent.Chave) {
      case "executar_acao":
        await this.AgenteDeExecucao(conversaId);
        break;
      case "consultar_status":
        await this.AgenteDeConsulta(conversaId);
        break;
      case "cancelar":
        await this.AgenteDeCancelamento(conversaId);
        break;
      default:
        await this.Responder(conversaId, "Posso ajudar a executar, consultar ou cancelar uma operação.");
        await this.EncerrarConversa(conversaId);
        break;
    }
  }

  // ── Agente especializado: coleta os dados e dispara a ação ───────────────────
  @SkailFunction()
  async AgenteDeExecucao(conversaId: string): Promise<void> {
    let extracao = await this.ExtrairDados(conversaId);

    // Loop de coleta: pede só o que não dá para descobrir sozinho (seções 5 e 6).
    while (extracao.Incompleto) {
      if (!(await this.AguardarResposta(conversaId, extracao.MensagemParaUsuario))) return;
      extracao = await this.ExtrairDados(conversaId);
    }

    await this.Responder(conversaId, "Recebi tudo. Já estou processando, um instante.");
    await this.ExecutarOperacao(extracao.Pedido);
    await this.EncerrarConversa(conversaId);
  }

  // ── Ação principal: webhook com fallback para polling (seção 7) ──────────────
  @SkailFunction()
  async ExecutarOperacao(pedido: Pedido): Promise<void> {
    await this.EnviarParaProvedor(pedido);

    const retorno = Skail.waitForEvent<Resultado>("EVENTO_RETORNO", pedido.Id);
    const timeout = Skail.delayMinutes(1);

    const retornoOuTimeout = await Skail.whenAny(retorno, timeout);
    const resultado =
      retornoOuTimeout !== undefined
        ? retornoOuTimeout // o webhook chegou
        : await this.ConsultarPorPolling(pedido.Id); // timeout → polling durável

    await this.Persistir(resultado);

    if (!resultado.Concluido) {
      await this.AguardarRevisaoHumana(pedido.Id, resultado); // seção 8
    }
  }

  // ── Polling durável: cada iteração é retomável ───────────────────────────────
  // Número fixo de tentativas: o laço (loop) se repete igual no replay.
  @SkailFunction()
  async ConsultarPorPolling(pedidoId: string): Promise<Resultado> {
    let atual: Resultado = { Concluido: false, EhFinal: false };
    for (let tentativa = 0; tentativa < TentativasDePolling; tentativa++) {
      atual = await this.ConsultarProvedor(pedidoId);
      if (atual.EhFinal) return atual;
      await Skail.delaySeconds(30);
    }
    return atual;
  }

  // ── Human-in-the-loop: pausa até uma decisão humana (seção 8) ────────────────
  @SkailFunction()
  async AguardarRevisaoHumana(pedidoId: string, pendente: Resultado): Promise<void> {
    await this.RegistrarPedidoDeRevisao(pendente);

    const decisao = await Skail.waitForEvent<Decisao>("EVENTO_REVISAO", pedidoId);

    // O pedido corrigido passa pelo mesmo caminho: espera o webhook, cai para polling e persiste.
    // Ele traz Id próprio para o reenvio (ex.: {pedidoId}-rev1), que o provedor devolve no webhook.
    await this.ExecutarOperacao(decisao.PedidoCorrigido);
  }

  // ── Conversa: responde e espera a próxima mensagem (ou desiste) ──────────────
  // Retorna false quando a conversa deve terminar por inatividade.
  @SkailFunction()
  async AguardarResposta(conversaId: string, mensagem?: string): Promise<boolean> {
    await this.Responder(conversaId, mensagem ?? "Me conta um pouco mais sobre o que você precisa.");

    const timeout = Skail.delayMinutes(TimeoutInatividadeMin);
    const msg = Skail.waitForEvent<MensagemRecebida>("EVENTO_NOVA_MENSAGEM", conversaId);

    const recebida = await Skail.whenAny(timeout, msg);
    if (recebida === undefined) {
      await this.Responder(conversaId, "Vou encerrar por inatividade. É só me chamar quando quiser continuar.");
      await this.EncerrarConversa(conversaId);
      return false;
    }

    // A mensagem chega no payload do evento; o agente grava no histórico em um passo atômico.
    await this.SalvarMensagemDoUsuario(conversaId, recebida.Texto);
    return true;
  }

  // Grava a resposta no histórico e envia ao usuário em passos separados:
  // se o envio falhar, só o envio é repetido, e a mensagem não é gravada de novo.
  @SkailFunction()
  async Responder(conversaId: string, mensagem: string): Promise<void> {
    await this.SalvarMensagemDoAgente(conversaId, mensagem);
    await this.EnviarMensagem(conversaId, mensagem);
  }

  // ── Passos atômicos: cada operação de I/O isolada e retomável (seção 2) ──────
  @SkailCommand()
  async CarregarHistorico(id: string): Promise<Mensagem[]> {
    return store.Historico(id);
  }

  @SkailCommand()
  async ClassificarIntencao(h: Mensagem[]): Promise<IntencaoResult> {
    return intencao.Classificar(h, Intencoes, Contexto);
  }

  @SkailCommand()
  async ExtrairDados(id: string): Promise<DadosResult> {
    return dados.Extrair(await store.Historico(id));
  }

  @SkailCommand()
  async EnviarParaProvedor(p: Pedido): Promise<void> {
    await provedor.Enviar(p);
  }

  @SkailCommand()
  async ConsultarProvedor(id: string): Promise<Resultado> {
    return provedor.Consultar(id);
  }

  @SkailCommand()
  async Persistir(r: Resultado): Promise<void> {
    await store.Salvar(r);
  }

  @SkailCommand()
  async RegistrarPedidoDeRevisao(r: Resultado): Promise<void> {
    await store.MarcarParaRevisao(r);
  }

  @SkailCommand()
  async SalvarMensagemDoUsuario(id: string, texto: string): Promise<void> {
    await store.SalvarMensagemDoUsuario(id, texto);
  }

  @SkailCommand()
  async SalvarMensagemDoAgente(id: string, msg: string): Promise<void> {
    await store.SalvarMensagem(id, msg);
  }

  @SkailCommand()
  async EnviarMensagem(id: string, msg: string): Promise<void> {
    await canal.Enviar(id, msg);
  }

  @SkailCommand()
  async EncerrarConversa(id: string): Promise<void> {
    await store.Encerrar(id);
  }

  // Sub-agentes de consulta/cancelamento seguem o mesmo padrão do AgenteDeExecucao.
  @SkailFunction()
  async AgenteDeConsulta(conversaId: string): Promise<void> {
    /* extrai identificador, consulta, responde */
  }

  @SkailFunction()
  async AgenteDeCancelamento(conversaId: string): Promise<void> {
    /* extrai identificador, cancela, confirma */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Gatilho e desbloqueio

O agente acima é dirigido por **eventos**. Faltam os dois tipos de chamada que iniciam o agente e liberam as esperas, normalmente fora da aplicação que roda o agente, na sua API/BFF e nos seus webhooks. Os dois são chamadas HTTP à API do skail, com os headers `Authorization`, `skail-key` e `Skail-namespace`:

```http
# 1) GATILHO: inicia uma instância do agente (ex.: ao chegar a primeira mensagem).
#    O corpo é um array com os argumentos da function, na ordem (conversaId e o texto da primeira mensagem); o último segmento da URL é o id da execução, um GUID.
POST {SKAIL_BASE_URL}/api/v1/trigger/{workload}/{versao}/Atender/{id}
["{conversaId}", "{texto da primeira mensagem}"]

# 2) NOVA MENSAGEM: destrava o loop de conversa quando o usuário responde.
POST {SKAIL_BASE_URL}/api/v1/fire/EVENTO_NOVA_MENSAGEM/{conversaId}
{ "Texto": "texto da mensagem" }

# 3) RETORNO DO PROVEDOR: destrava a ação quando o webhook externo chega.
#    O último segmento é o Id do pedido enviado ao provedor: o original ou o corrigido ({pedidoId}-rev1).
POST {SKAIL_BASE_URL}/api/v1/fire/EVENTO_RETORNO/{pedidoId}
{ ...resultado }

# 4) REVISÃO HUMANA: destrava o agente quando alguém aprova/corrige na interface.
#    O último segmento é o Id do pedido que está em revisão: o original ou o corrigido ({pedidoId}-rev1).
POST {SKAIL_BASE_URL}/api/v1/fire/EVENTO_REVISAO/{pedidoId}
{ ...decisao }
```

O `id` do gatilho precisa ser um GUID. Quem dispara gera o GUID uma vez, reutiliza o mesmo valor nas novas tentativas desse disparo e guarda a relação entre ele e o id da conversa (por exemplo, numa coluna da conversa), para achar a execução no Monitor depois. Grave o GUID na conversa antes de disparar e marque a conversa quando o trigger responder 2xx. Se o webhook da primeira mensagem chegar de novo e a conversa já estiver marcada, não dispare outro; se a tentativa anterior não recebeu 2xx, tente de novo com o GUID gravado. Com um GUID novo, o trigger inicia uma segunda instância do agente na mesma conversa. Nos eventos, o último segmento é a chave de correlação: o id da conversa ou o id do pedido.

O corpo de cada evento é o payload que a espera por evento correspondente vai receber, em JSON. Quem dispara um evento não grava nada: o que precisa ser persistido a partir do evento (a mensagem do usuário, o resultado do provedor, a decisão da revisão) é gravado pelo agente, em um passo atômico, depois que o evento chega. Detalhes e exemplos em [POST /api/v1/fire](/referencia/api-http/post-fire.md) e em [Como disparar um evento pela API HTTP](/construa/como-fazer/disparar-um-evento-pela-api.md).

Com isso o ciclo se fecha: um gatilho inicia o agente, ele conversa e age usando LLM e ferramentas, e os eventos o reativam exatamente de onde parou, sem você gerenciar estado, filas ou threads em espera.

***

### Referência rápida

| Quero…                                    | Use                                                     |
| ----------------------------------------- | ------------------------------------------------------- |
| Definir um agente / sub-agente durável    | function                                                |
| Isolar um I/O ou chamada de LLM retomável | command                                                 |
| Esperar por um evento externo             | espera por evento (nome, id)                            |
| Timeout / polling / backoff               | delay                                                   |
| "Evento ou timeout"                       | esperar o primeiro que terminar (evento ou delay)       |
| Iniciar uma instância de agente           | `POST` no endpoint de trigger                           |
| Desbloquear uma espera por evento         | `POST` na API HTTP de eventos com a chave de correlação |
| Entender o que o usuário quer             | classificação de intenção com saída estruturada         |
| Escolher o fluxo certo                    | roteamento por intenção para um agente especializado    |
| Reunir dados sem interrogar o usuário     | ferramentas (tool use) que autocompletam                |
| Aprovação ou correção humana              | espera por evento (human-in-the-loop)                   |
