> 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/aprender/fundamentos/determinismo-e-replay.md).

# Determinismo e replay

Uma `[SkailFunction]` é reexecutada do início toda vez que a execução é retomada. Esta página explica por que isso acontece, o que pode e o que não pode estar dentro de uma function, e o que significa a `SkailNonDeterministicException`.

## Por que a função é reexecutada

O skail não guarda a pilha de execução do seu método. O que existe é o histórico da execução: quais commands foram chamados, com quais argumentos, o que devolveram, quais delays e esperas foram alcançados. Quando a execução precisa continuar (o delay venceu, o evento chegou, o processo reiniciou, um command falhou e vai ser retentado), o runtime chama o método de novo, desde a primeira linha, e vai consultando o histórico a cada `await`:

```
execução chega (início ou retomada)
  → runtime carrega o histórico da execução
  → runtime chama a function desde o início
  → a cada await alcançado:
       • o passo já está no histórico → devolve o resultado gravado, sem executar
       • não existe → executa de verdade (command: executa a operação; delay: agenda; evento: hiberna) e grava um passo
  → quando a function termina ou hiberna, o runtime confirma ao skail
```

Esse processo é o replay. Ele só funciona se o método, dado o mesmo histórico, percorrer o mesmo caminho e alcançar os mesmos `await`s na mesma ordem. É isso que "determinístico" quer dizer aqui.

## O que pode estar dentro de uma function

Lógica pura: `if`, `switch`, laços, cálculos sobre argumentos e sobre resultados de commands, variáveis locais. Chamadas a `[SkailCommand]` e a outras `[SkailFunction]` (que viram orquestrações filhas). As primitivas `SkailTask.Delay`, `DelaySeconds`, `DelayMinutes`, `WhenAll`, `WhenAny` e `WaitForEvent`. `ILogger` e `SkailContext.Current.TaskId`.

Tudo isso ou não muda entre execuções, ou é gravado no histórico e devolvido igual no replay.

## O que não pode

Qualquer coisa cujo valor ou ordem possa ser diferente na segunda execução:

| Dentro da function                                                           | Por quê                                                                                         | Onde colocar                                  |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `DateTime.Now`, `DateTime.UtcNow`, `DateTimeOffset.Now`                      | O relógio muda entre o primeiro run e o replay; um `if (agora > vencimento)` toma outro caminho | Em um `[SkailCommand]` que retorna `DateTime` |
| `Guid.NewGuid()`, `Random`, `Environment.TickCount`                          | Valor diferente a cada execução                                                                 | Command, ou receber como parâmetro            |
| `Environment.GetEnvironmentVariable`, ler arquivo, config, banco, HTTP, fila | É I/O: o resultado pode mudar e o efeito se repete                                              | Command                                       |
| `Task.Delay`, `Thread.Sleep`                                                 | Não registra passo; no replay o runtime não encontra o passo                                    | `SkailTask.Delay`                             |
| `Task.WhenAll`, `Task.WhenAny`, `Task.Run`, `Parallel.ForEach`               | Concorrência fora do controle do runtime, ordem não reproduzível                                | `SkailTask.WhenAll` / `WhenAny`               |

A regra que resume a tabela: se o resultado pode diferir entre duas execuções com as mesmas entradas, é command.

Um exemplo que parece inocente e quebra:

```csharp
[SkailFunction]
public async SkailTask EnviarLembrete(Assinatura assinatura)
{
    var dias = (assinatura.Vencimento - DateTime.UtcNow).TotalDays; // não determinístico
    await SkailTask.Delay(TimeSpan.FromDays(dias - 3));
    await Notificar(assinatura.UsuarioId);
}
```

Na primeira execução `dias` vale, digamos, 30, e o delay agendado é de 27 dias. Vinte e sete dias depois a execução acorda, o método roda de novo, `DateTime.UtcNow` agora dá `dias = 3`, e o código pede um delay de zero dias: o passo gravado (delay de 27 dias) não bate com o passo que o código quer executar. O mesmo método, correto:

```csharp
[SkailFunction]
public async SkailTask EnviarLembrete(Assinatura assinatura)
{
    var agora = await ObterDataAtual();            // gravado no histórico; igual em todo replay
    var dias = (assinatura.Vencimento - agora).TotalDays;
    await SkailTask.Delay(TimeSpan.FromDays(dias - 3));
    await Notificar(assinatura.UsuarioId);
}

[SkailCommand]
public async SkailTask<DateTime> ObterDataAtual() => await Task.FromResult(DateTime.UtcNow);
```

## Dentro do command vale tudo

O command roda uma vez por resultado e o resultado é gravado. Por isso dentro dele você usa `HttpClient`, `DbContext`, `Task.Delay`, `Task.WhenAll`, `Guid.NewGuid()`, `DateTime.UtcNow` normalmente. O determinismo é capturado na fronteira entre a function e o command. A exceção é chamar outro `[SkailCommand]` ou uma `[SkailFunction]`: quem compõe os passos é sempre a function, e commands são chamados só de dentro de functions; para reaproveitar lógica dentro de um command, use métodos comuns, sem atributo.

## SkailNonDeterministicException

É a exceção que o runtime lança quando o replay diverge do histórico: o passo que o código quer executar não é o passo que o histórico tem naquela posição (índice diferente, ou tipo de evento e URI do método diferentes do passo gravado). As causas, em ordem de frequência:

1. Relógio, aleatoriedade, variável de ambiente ou I/O dentro da function.
2. Código mudou depois que a execução já tinha histórico: ordem, quantidade ou tipo dos `await`s diferente (inseriu ou removeu um command, um `Delay`, um `WhenAll`, um `WaitForEvent`). Ver [Versionamento de código com execuções em andamento](/aprender/fundamentos/versionamento-de-codigo-com-execucoes-em-andamento.md).
3. Método renomeado, ou `SKAIL_WORKLOAD` diferente do que gerou a execução.
4. `Task.Delay` ou `Task.WhenAll` no lugar das versões `SkailTask`.

Não capture essa exceção para "seguir em frente". Ela indica que o código e o histórico contam histórias diferentes; capturar esconde o erro e corrompe o fluxo. Corrija a causa e, se houver execuções em andamento com o histórico antigo, versione.

## O que isso muda para quem escreve código

Function fina, command gordo: a function decide e encadeia; todo efeito e toda fonte de não determinismo moram em commands. Nomeie os commands pelo efeito (`CobrarCartao`, `ObterDataAtual`) porque é assim que eles aparecem no Monitor. E lembre que o corpo da function roda várias vezes: um `_logger.LogInformation` no meio dela pode aparecer repetido nos logs; inclua o `TaskId` para agrupar.

## Próximos passos

[Falhas, retries e idempotência](/aprender/fundamentos/falhas-retries-e-idempotencia.md) explica o que acontece quando um command lança exceção. [Antipadrões](/construir/padroes-e-antipadroes/antipadroes-o-que-nao-fazer-e-por-que.md) lista os erros mais comuns com a correção de cada um. A referência de [SkailFunction](/construir/sdk-.net/skailfunction.md) tem as regras de assinatura que o analyzer cobra.
