> 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)
    processar(await retorno);           // o sinal chegou
else
    await consultarPorPolling(correlacaoId);  // timeout → plano B
```

{% endcode %}
{% endtab %}

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

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

interface RespostaExterna {
  Concluido: boolean;
}

declare function processar(resposta: RespostaExterna): void;
declare function consultarPorPolling(correlacaoId: string): Promise<void>;

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) {
      processar(resultado); // o sinal chegou
    } else {
      await consultarPorPolling(correlacaoId); // timeout → plano B
    }
  }
}
```

{% 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 identificador de correlaçã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;
}

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

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);
  }

  @SkailFunction()
  async ConsultarPorPolling(pedidoId: string): Promise<Resultado> {
    return consultarPorPolling(pedidoId);
  }

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

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

O **polling durável** é só um laço 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);

    await aplicarEReprocessar(decisao);
}
```

{% endcode %}
{% endtab %}

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

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

interface Resultado {
  Concluido: boolean;
}

interface Decisao {
  Aprovado: boolean;
}

declare function registrarPedidoDeRevisao(pendente: Resultado): Promise<void>;
declare function aplicarEReprocessar(decisao: Decisao): 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);

    await this.AplicarEReprocessar(decisao);
  }

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

  @SkailCommand()
  async AplicarEReprocessar(decisao: Decisao): Promise<void> {
    await aplicarEReprocessar(decisao);
  }
}
```

{% 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.

***

### 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.

<figure><img src="https://1898946878-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJmS5ILPG5x2x5j0WyiV%2Fuploads%2F6JCgjqkB0v99hMDmKRKk%2Fap-agentes-mapa.svg?alt=media" alt="O esqueleto do agente visto por function: o POST de trigger inicia Atender, que classifica e delega e, para executar_acao, chama AgenteDeExecucao; enquanto falta informação, as duas chamam AguardarResposta, que espera EVENTO_NOVA_MENSAGEM ou um delay de 15 minutos. AgenteDeExecucao chama ExecutarOperacao, que espera EVENTO_RETORNO ou um delay de 1 minuto e chama ConsultarPorPolling, com delay de 30 segundos, quando o prazo vence; se o resultado não é concluído, AguardarRevisaoHumana espera EVENTO_REVISAO e depois um delay de 1 minuto. De fora, o usuário, o webhook e a interface disparam os eventos."><figcaption></figcaption></figure>

#### O agente

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

```csharp
using Skail.Platform.Runtime.Standard;
using Skail.Platform.Runtime.Standard.Threading;
using static Skail.Platform.Runtime.Standard.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 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);

        await EnviarParaProvedor(decisao.PedidoCorrigido);
        await DelayMinutes(1);
        await Persistir(await ConsultarProvedor(pedidoId));
    }

    // ── 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;
    }

    // ── 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 Responder(string id, string msg) { await store.SalvarMensagem(id, 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 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);

    await this.EnviarParaProvedor(decisao.PedidoCorrigido);
    await Skail.delayMinutes(1);
    await this.Persistir(await this.ConsultarProvedor(pedidoId));
  }

  // ── 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 | null): 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;
  }

  // ── 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 Responder(id: string, msg: string): Promise<void> {
    await store.SalvarMensagem(id, msg);
    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 `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 é a chave de correlação.
POST {SKAIL_BASE_URL}/trigger/{workload}/{versao}/Atender/{conversaId}
["{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.
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.
POST {SKAIL_BASE_URL}/api/v1/fire/EVENTO_REVISAO/{pedidoId}
{ ...decisao }
```

O corpo de cada evento é o payload que a espera por evento correspondente vai receber, em JSON. Quem dispara 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)                   |
