> 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/quickstart/design-patterns/human-in-the-loop.md).

# Human-in-the-loop

Muitos processos de negócio exigem intervenção humana no meio do caminho: um reembolso aprovado pelo gerente, um documento revisado pelo jurídico, uma transação acima de um limite liberada pelo compliance. Essas etapas podem levar minutos, horas ou dias — o tempo de resposta de uma pessoa é o oposto do tempo de resposta de um sistema.

Tradicionalmente isso vira um quebra-cabeça: tabela de "pendências de aprovação", job rodando de tempos em tempos para identificar quem demorou demais, e-mails de lembrete, endpoint para receber a decisão, lógica para destravar o fluxo original. Em skail, tudo isso é um `WaitForEvent` no meio de uma função.

### Aprovação com prazo

O caso mais direto: uma solicitação precisa ser aprovada em até 48 horas; se não for, ela é cancelada automaticamente e quem solicitou é avisado.

```csharp
[SkailFunction]
public async SkailTask SolicitarReembolso(Guid solicitacaoId, Guid aprovadorId)
{
    await RegistrarSolicitacao(solicitacaoId);
    await NotificarAprovador(aprovadorId, solicitacaoId);

    var decisao = SkailTask.WaitForEvent<DecisaoAprovacao>(
        "REEMBOLSO_DECIDIDO",
        solicitacaoId.ToString()
    );
    var prazo = SkailTask.Delay(TimeSpan.FromHours(48));

    await SkailTask.WhenAny(decisao, prazo);

    if (!decisao.IsCompleted)
    {
        await CancelarPorExpiracao(solicitacaoId);
        await NotificarSolicitante(solicitacaoId, "expirou");
        return;
    }

    var resposta = await decisao;
    if (resposta.Aprovado)
        await EfetuarReembolso(solicitacaoId, resposta.DecidIdoPor);
    else
        await RegistrarRecusa(solicitacaoId, resposta.DecidIdoPor, resposta.Justificativa);
}
```

A UI do aprovador, no momento em que ele clica em "aprovar" ou "recusar", aciona uma `[SkailFunction]` fina que serve só como porta de entrada para disparar o evento:

```csharp
[SkailFunction]
public async SkailTask RegistrarDecisaoReembolso(Guid solicitacaoId, DecisaoAprovacao decisao)
{
    await PersistirDecisao(solicitacaoId, decisao);

    await SkailTask.FireEvent(
        "REEMBOLSO_DECIDIDO",
        solicitacaoId.ToString(),
        decisao
    );
}
```

### Aprovação em cadeia

Processos com vários níveis — gerente, diretor, VP, conforme o valor — viram uma sequência linear de esperas. Cada aprovação libera a próxima:

```csharp
[SkailFunction]
public async SkailTask SolicitarGastoExcepcional(Guid solicitacaoId, decimal valor)
{
    await RegistrarSolicitacao(solicitacaoId, valor);

    if (!await AguardarAprovacao(solicitacaoId, "gerente", TimeSpan.FromHours(24)))
        return;

    if (valor > 10_000 && !await AguardarAprovacao(solicitacaoId, "diretor", TimeSpan.FromHours(48)))
        return;

    if (valor > 100_000 && !await AguardarAprovacao(solicitacaoId, "vp", TimeSpan.FromDays(5)))
        return;

    await LiberarGasto(solicitacaoId);
}

[SkailFunction]
public async SkailTask<bool> AguardarAprovacao(Guid solicitacaoId, string nivel, TimeSpan prazo)
{
    await NotificarAprovadoresDoNivel(solicitacaoId, nivel);

    var decisao = SkailTask.WaitForEvent<DecisaoAprovacao>(
        $"APROVACAO_{nivel.ToUpper()}",
        solicitacaoId.ToString()
    );
    var expiracao = SkailTask.Delay(prazo);

    await SkailTask.WhenAny(decisao, expiracao);

    if (!decisao.IsCompleted)
    {
        await CancelarPorExpiracao(solicitacaoId, nivel);
        return false;
    }

    var resposta = await decisao;
    if (!resposta.Aprovado)
    {
        await RegistrarRecusa(solicitacaoId, nivel, resposta.Justificativa);
        return false;
    }

    return true;
}
```

A hierarquia de aprovação fica explícita no próprio método — não em tabelas de configuração, não em máquinas de estado externas. Lendo o código, qualquer pessoa entende exatamente em que condições cada nível é consultado.

### Escalonamento automático

Um padrão muito comum no dia a dia de aprovações: notificar o primeiro aprovador, e se ele não responder dentro de um prazo, passar automaticamente para o próximo da cadeia — sem que ninguém precise acompanhar manualmente. Em skail isso é um laço:

```csharp
[SkailFunction]
public async SkailTask SolicitarAprovacaoComEscalonamento(Guid solicitacaoId, Guid[] cadeia)
{
    foreach (var aprovador in cadeia)
    {
        await NotificarAprovador(aprovador, solicitacaoId);

        var decisao = SkailTask.WaitForEvent<DecisaoAprovacao>(
            "APROVACAO_CADEIA",
            solicitacaoId.ToString()
        );
        var prazoIndividual = SkailTask.Delay(TimeSpan.FromHours(24));

        await SkailTask.WhenAny(decisao, prazoIndividual);

        if (decisao.IsCompleted)
        {
            var resposta = await decisao;
            if (resposta.Aprovado)
                await ExecutarSolicitacao(solicitacaoId);
            else
                await RegistrarRecusa(solicitacaoId, resposta);
            return;
        }

        await RegistrarNaoResposta(solicitacaoId, aprovador);
    }

    await CancelarPorAbandonoDaCadeia(solicitacaoId);
}
```

Cada iteração espera 24 horas pela resposta do aprovador da vez. Se ele não se manifestar, a função simplesmente avança para o próximo da lista, registrando no histórico que a vez dele passou em branco. Se ninguém da cadeia toda responder, a solicitação é cancelada.

Se o primeiro aprovador responder em 10 minutos, a execução segue em 10 minutos. Se o terceiro da cadeia decidir, a execução ficou hibernada por cerca de 48 horas no caminho e voltou a rodar quando o `FireEvent` correspondente chegou. A lógica é a mesma — o tempo vivido pela execução é que muda.

### Observabilidade

No Monitor do portal da skail, uma solicitação aguardando aprovação aparece como uma execução hibernada, com indicação clara do evento que está esperando e do prazo que ainda falta para expirar. Em aprovações em cadeia, cada nível aparece como um ponto distinto da execução — fica visível em qual etapa a solicitação está parada e há quanto tempo.

Isso substitui uma dor comum em sistemas de workflow: saber rapidamente *em qual mesa* um documento está engasgado, sem precisar cruzar logs de notificação, tabela de pendências e caixa de entrada de e-mails.
