# Manual do Desenvolvedor — Workflows do Agente Contas

> **Projeto de referência:** `agent_contas_fechado_COMPLETO_corrigido_v4`  
> **Pasta documentada:** `/workflows`  
> **Público:** desenvolvedores que precisam criar, alterar, depurar ou revisar jornadas determinísticas do agente Contas.

---

## 1. Objetivo deste manual

A pasta `workflows/` contém a **orquestração declarativa de jornadas de negócio** do agente Contas. Ela não é apenas uma coleção de YAMLs: cada arquivo descreve um pequeno grafo de execução que o runtime genérico do `agent_framework_oci` carrega, valida, executa, pausa, retoma e finaliza.

O princípio central é:

> **O workflow decide a sequência, as condições e os pontos de pausa. A action executa a operação de domínio. O framework fornece o motor genérico.**

Isso evita que regras de jornada fiquem escondidas em `if/else` dentro dos agentes ou do framework.

Este documento explica:

- como o versionamento dos workflows funciona;
- como ler um arquivo `.vN.yaml`;
- o significado de `name`, `version`, `start`, `nodes`, `edges`, `when`, `priority`, `pause`, `expected_input` e `resume_from`;
- como funcionam `$.input`, `$.vars`, `$.output` e o estado interno;
- como uma `action` declarada no YAML se conecta a Python;
- como um workflow pausa e continua em outro turno;
- como o classificador semântico de `expected_input` funciona;
- como um workflow termina sem trocar o `session_id`;
- como cada arquivo atual da pasta `workflows/` funciona, ponto a ponto;
- como adicionar uma nova versão com segurança.

---

## 2. Estrutura atual da pasta

```text
workflows/
├── buscar_fatura.active.yaml
├── buscar_fatura.v1.yaml
├── buscar_informacao.active.yaml
├── buscar_informacao.v2.yaml
├── cancelamento_vas_avulso.active.yaml
├── cancelamento_vas_avulso.v1.yaml
├── contestacao_tool.active.yaml
├── contestacao_tool.v2.yaml
├── finalizar_atendimento.active.yaml
├── finalizar_atendimento.v1.yaml
├── invoice_explanation.active.yaml
├── invoice_explanation.v2.yaml
├── pro_rata.active.yaml
├── pro_rata.v3.yaml
├── termino_desconto.active.yaml
├── termino_desconto.v1.yaml
├── valor_divergente.active.yaml
├── valor_divergente.v1.yaml
├── vas_estrategico.active.yaml
└── vas_estrategico.v3.yaml
```

Há sempre dois papéis diferentes:

1. **`<nome>.active.yaml`** — marcador da versão ativa;
2. **`<nome>.vN.yaml`** — definição completa e versionada do grafo.

Exemplo:

```yaml
# invoice_explanation.active.yaml
version: 2
```

Esse arquivo não contém a lógica. Ele informa ao `FileWorkflowRepository` que, quando alguém pedir o workflow ativo `invoice_explanation`, deve ser carregado:

```text
invoice_explanation.v2.yaml
```

### Regra prática de versionamento

Não altere silenciosamente a semântica de uma versão já publicada quando a mudança for incompatível ou material. Prefira:

```text
invoice_explanation.v2.yaml   # versão atual
invoice_explanation.v3.yaml   # nova implementação
invoice_explanation.active.yaml -> version: 3
```

Assim rollback e auditoria permanecem simples.

---

## 3. Quem faz o quê

A arquitetura pode ser entendida em quatro camadas.

```mermaid
flowchart LR
    U[Usuário] --> AG[Agente / Router]
    AG --> W[Workflow YAML]
    W --> RT[WorkflowRuntime do framework]
    RT --> A[Actions Python do domínio Contas]
    A --> S[Services / MCP / APIs legadas]
    A --> RT
    RT --> AG
```

### 3.1 Workflow YAML

Responsável por:

- sequência dos passos;
- branching;
- prioridade das transições;
- definição de pausa;
- contrato da resposta esperada do usuário;
- nó de retomada;
- declaração de valores fixos da jornada;
- escolha de qual action executar.

### 3.2 `WorkflowRuntime`

É genérico e pertence ao framework. Ele:

- carrega o YAML;
- valida o grafo;
- resolve expressões `$.…`;
- executa actions;
- mantém `vars`, `output`, `trace` e estado;
- ordena edges por `priority`;
- avalia `when`;
- implementa pause/resume;
- usa checkpoint do LangGraph em produção;
- retorna `COMPLETED`, `PAUSED` ou `FAILED`.

O runtime não deve conhecer regras específicas da TIM ou do Contas.

### 3.3 Actions Python

No Contas, a maioria das actions declaradas nos YAMLs é registrada em:

```text
app/domain/contas/workflow_actions.py
```

por meio de:

```python
reg = WorkflowActionRegistry()

@reg.action("nome_da_action")
def nome_da_action(params, state):
    ...
    return {...}
```

Uma action deve receber:

```python
params: dict
state: dict
```

E deve retornar **sempre um `dict`**.

### 3.4 Services / MCP / legado

As actions podem chamar `ContasDomainService`, clientes HTTP, integrações TIM, mocks ou outros componentes. O YAML não deve conter código de transporte.

---

## 4. Anatomia de um workflow

Um workflow mínimo é:

```yaml
name: exemplo
version: 1
start: primeiro

nodes:
  - id: primeiro
    action: minha_action
    input:
      msisdn: $.input.msisdn

edges:
  - from: primeiro
    to: END
```

### 4.1 `name`

Nome lógico do workflow.

Precisa ser coerente com o nome do arquivo:

```text
exemplo.v1.yaml
name: exemplo
version: 1
```

O repository valida essa correspondência.

### 4.2 `version`

Número inteiro da versão do contrato do workflow.

### 4.3 `start`

ID do primeiro nó executado.

### 4.4 `nodes`

Cada nó representa uma unidade de execução.

```yaml
- id: preparar
  action: preparar_invoice_explanation
  input:
    msisdn: $.input.msisdn
```

O `id` é o nome do nó dentro do grafo. A `action` é o nome registrado no `WorkflowActionRegistry`.

### 4.5 `edges`

Definem para onde o grafo segue após um nó.

```yaml
- from: preparar
  to: formatar
```

ou condicionalmente:

```yaml
- from: preparar
  to: formatar
  priority: 10
  when:
    eq: [$.vars.preparar.success, true]
```

### 4.6 `END`

`END` representa término do grafo.

```yaml
- from: finalizar
  to: END
```

---

## 5. Modelo de estado e expressões `$.…`

Essa é uma das partes mais importantes para quem altera a pasta `workflows/`.

### 5.1 `$.input`

Representa os dados de entrada da execução.

Exemplo:

```yaml
input:
  msisdn: $.input.msisdn
  invoice_id: $.input.invoice_id
```

Se o workflow foi iniciado com:

```json
{
  "msisdn": "11999999999",
  "invoice_id": "3000131180"
}
```

os dois valores serão passados à action.

### 5.2 `$.vars.<node>`

Após cada action retornar um dicionário, o runtime armazena o resultado em:

```text
$.vars.<id_do_no>
```

Exemplo:

```yaml
- id: registrar_protocolo
  action: registrar_protocolo
```

Se a action retornar:

```json
{
  "success": true,
  "protocolo_id": "1234567890"
}
```

então outro nó pode usar:

```yaml
protocolo_id: $.vars.registrar_protocolo.protocolo_id
```

### 5.3 `$.output`

Aponta para o último resultado de action colocado como output corrente.

É muito usado em `pause.return_from`:

```yaml
pause:
  return_from: $.output.mensagem
```

### 5.4 `$.nodes`

O runtime também mantém resultados por nó em uma estrutura de nós. Na maior parte dos workflows do Contas, `$.vars` é a forma declarativa utilizada para encadear dados.

### 5.5 Exemplo encadeado

```yaml
- id: preparar
  action: preparar

- id: formatar
  action: formatar
  input:
    dados: $.vars.preparar.dados
```

Fluxo:

```text
preparar() -> {dados: X}
                 |
                 v
$.vars.preparar.dados
                 |
                 v
formatar(dados=X)
```

---

## 6. Conditions e prioridade de edges

O runtime agrupa as edges por nó de origem e ordena por `priority` crescente.

Portanto:

```yaml
priority: 10
```

é avaliada antes de:

```yaml
priority: 99
```

### 6.1 Fallback padrão

Um padrão comum é:

```yaml
- from: action_x
  to: caminho_especial
  priority: 10
  when:
    eq: [$.vars.action_x.alguma_flag, true]

- from: action_x
  to: caminho_padrao
  priority: 99
```

A edge de prioridade 99 funciona como fallback porque não possui `when`.

### 6.2 Operadores encontrados nos workflows atuais

Exemplos:

```yaml
when:
  eq: [$.vars.preparar.success, true]
```

```yaml
when:
  neq: [$.vars.x.barcode, ""]
```

```yaml
when:
  exists: $.vars.x.barcode
```

```yaml
when:
  all:
    - eq: [$.vars.x.a, true]
    - eq: [$.vars.x.b, true]
```

```yaml
when:
  any:
    - eq: [$.vars.x.a, true]
    - eq: [$.vars.x.b, true]
```

### Regra importante

As edges devem ser mutuamente compreensíveis. Se nenhuma edge corresponder, o runtime pode falhar com:

```text
Nenhuma transição do workflow correspondeu ao estado
```

Por isso fluxos condicionais normalmente possuem uma edge final sem `when`.

---

## 7. Pause / resume

Workflows conversacionais podem parar no meio da execução para pedir uma resposta ao usuário.

Exemplo simplificado:

```yaml
- id: formatar
  action: formatar_invoice_explanation
  pause:
    enabled: true
    return_from: $.output.mensagem
    expected_input:
      key: resposta_usuario
      allowed_values: ["SIM", "NAO"]
      normalize: upper_strip
    resume_from: decisao
```

### 7.1 O que ocorre no primeiro turno

1. `formatar_invoice_explanation` é executada;
2. a mensagem produzida é obtida de `$.output.mensagem`;
3. o runtime persiste o checkpoint;
4. retorna `status=PAUSED`;
5. a mensagem é enviada ao usuário;
6. o workflow fica aguardando input.

### 7.2 O que ocorre no turno seguinte

A nova fala é validada contra `expected_input`.

Se aceita:

```text
resposta do usuário
        ↓
normalize
        ↓
$.input.resposta_usuario
        ↓
resume_from: decisao
```

### 7.3 Por que pause é separado da action

No runtime atual, pause/resume é implementado em um nó técnico separado. Isso é deliberado.

Ao retomar, **a action anterior não é reexecutada**. Isso evita repetir efeitos externos como:

- abrir protocolo duas vezes;
- cancelar duas vezes;
- enviar SMS novamente;
- criar duas SRs.

---

## 8. `expected_input`

Exemplo:

```yaml
expected_input:
  key: resposta_usuario
  allowed_values: ["SIM", "NAO", "OUTRO"]
  normalize: upper_strip
```

### `key`

Nome em que o valor normalizado será gravado em `$.input`.

### `allowed_values`

Valores internos permitidos para a decisão do workflow.

Esses valores são **tokens de controle**, não necessariamente texto exibido ao cliente.

### `normalize: upper_strip`

Remove espaços laterais e converte para maiúsculas.

Exemplo:

```text
" sim " -> "SIM"
```

### `reprompt`

Mensagem utilizada quando o input não pode ser interpretado pelo contrato.

---

## 9. Semantic classifier de `expected_input`

O `invoice_explanation` possui um classificador semântico declarativo.

Ele existe porque respostas reais do usuário raramente são somente `sim` ou `não`.

Exemplo:

```text
"entendi, obrigado, era só isso"
```

semanticamente é `SIM`.

Já:

```text
"então no mês que vem vou pagar menos?"
```

não é `SIM`, mesmo contendo sinal de compreensão; é uma continuação da pergunta.

O YAML define:

```yaml
semantic_classifier:
  enabled: true
  include_relevant_context: true
  option_actions:
    CONTINUAR:
      action: contextual_reentry
  prompt: |
    ...
```

### 9.1 Responsabilidade correta

- **Framework:** executa o classificador e garante que a saída esteja entre os valores permitidos.
- **Workflow/agente:** define o significado de `SIM`, `NAO`, `CONTINUAR`.

### 9.2 `contextual_reentry`

Quando a opção classificada possui:

```yaml
CONTINUAR:
  action: contextual_reentry
```

o workflow pausado não deve simplesmente tratar a fala como confirmação. A utterance é liberada para nova interpretação pelo roteamento normal, com contexto delimitado.

Isso é particularmente importante para evitar que hipóteses do usuário virem fatos confirmados.

---

## 10. Estado terminal e nova interação na mesma sessão

Um workflow pode terminar sem encerrar tecnicamente o `session_id`.

Isso significa:

```text
mesma sessão técnica
        !=
mesmo workflow ativo
```

No `invoice_explanation`, por exemplo:

```yaml
workflow_response_final: true
```

indica que aquela action produz a resposta final daquele workflow.

Depois de uma execução terminal, o framework deve eliminar o latch operacional do workflow para que o próximo turno seja uma nova entrada, ainda na mesma sessão.

Deve permanecer:

- `session_id`;
- `session_key`;
- `conversation_key`;
- identidade do cliente;
- auditoria e telemetria;
- long-term memory.

Não deve continuar controlando a próxima entrada:

- `pending_domain_workflow`;
- `expected_input`;
- pause antigo;
- active transaction antiga;
- confirmação antiga;
- route stickiness da jornada terminada;
- short-term operational context do workflow fechado.

Esse detalhe é fundamental ao depurar cenários como:

```text
Usuário: entendi, obrigado, era só isso
Agente: Seu número de protocolo é ...
Usuário: ah espera
```

O terceiro turno é uma nova entrada na mesma sessão, e não um resume do workflow anterior.

---

# 11. Workflows atuais — explicação arquivo por arquivo

---

## 11.1 `buscar_fatura.active.yaml`

```yaml
version: 1
```

Seleciona `buscar_fatura.v1.yaml` como versão ativa.

## 11.2 `buscar_fatura.v1.yaml`

### Objetivo

Executar uma busca de fatura em um único passo.

### Cabeçalho

```yaml
name: buscar_fatura
version: 1
start: buscar_fatura
```

### Nó `buscar_fatura`

```yaml
- id: buscar_fatura
  action: buscar_fatura
  input:
    invoice_id: $.input.invoice_id
    msisdn: $.input.msisdn
    customer_id: $.input.customer_id
    output: $.input.output
```

A action Python está em `app/domain/contas/workflow_actions.py`.

Com `invoice_id` e `customer_id`, ela tenta buscar a fatura detalhada. Sem os identificadores necessários, usa `consultar_faturas` como fallback.

### Edge

```yaml
- from: buscar_fatura
  to: END
```

Não há branch nem pausa.

### Modelo mental

```text
INPUT
  |
  v
buscar_fatura
  |
  v
END
```

### Quando usar

Quando a operação é atômica e não precisa de confirmação do usuário.

---

## 11.3 `buscar_informacao.active.yaml`

```yaml
version: 2
```

Ativa `buscar_informacao.v2.yaml`.

## 11.4 `buscar_informacao.v2.yaml`

### Objetivo

Preparar uma consulta RAG e, em seguida, preparar a resposta para composição pelo framework.

### Nó 1 — `buscar_informacao`

```yaml
id: buscar_informacao
action: buscar_informacao_rag
```

Recebe:

- `query`;
- `queries`;
- `top_k`;
- `segment`.

A action atual devolve uma estrutura declarando `delegate_to_framework_rag=true`, isto é, o domínio sinaliza que a capacidade RAG deve ser executada pelo framework.

### Nó 2 — `reescrever_resposta`

Consome os resultados do primeiro nó:

```yaml
queries: $.vars.buscar_informacao.queries
documents: $.vars.buscar_informacao.documents
answer: $.vars.buscar_informacao.answer
noMatchRag: $.vars.buscar_informacao.noMatchRag
ragRetrievedDocuments: $.vars.buscar_informacao.ragRetrievedDocuments
ragSelectedDocuments: $.vars.buscar_informacao.ragSelectedDocuments
```

A action `reescrever_resposta_buscar_informacao` devolve a mensagem e `delegate_to_framework_llm=true`.

### Fluxo

```text
buscar_informacao_rag
        |
        v
reescrever_resposta_buscar_informacao
        |
        v
END
```

### Conceito importante

A pasta `workflows/` orquestra, mas não deve implementar o mecanismo RAG. O framework continua responsável pelo runtime RAG.

---

## 11.5 `cancelamento_vas_avulso.active.yaml`

```yaml
version: 1
```

## 11.6 `cancelamento_vas_avulso.v1.yaml`

### Objetivo

Executar cancelamento em lote de VAS avulso.

### Nó único

```yaml
id: cancelar_vas_avulso
action: cancelamento_vas_avulso_batch
```

Entradas:

- `items`;
- `csp_id`;
- `channel`;
- `social_sec_no`;
- `data_credito_proxima_fatura`;
- `idempotency_key`.

O YAML também fixa valores do contrato operacional:

```yaml
request_status: "Fechado"
status: "CLOSED"
```

### Fluxo

```text
cancelamento_vas_avulso_batch -> END
```

### Ponto de atenção

É uma operação transacional. A confirmação do usuário e as políticas de tool podem ocorrer antes da entrada no workflow. Não mova para o YAML uma duplicação de confirmation policy que pertença ao framework.

O `idempotency_key` é especialmente importante em operações com efeito externo.

---

## 11.7 `contestacao_tool.active.yaml`

```yaml
version: 2
```

## 11.8 `contestacao_tool.v2.yaml`

Este é o workflow mais complexo da pasta atual.

### Objetivo

Orquestrar a contestação de cobrança, incluindo protocolo, status da fatura, abertura da contestação, SMS quando aplicável, regra de corte, Conta Certa Manual e atualização de status.

### Visão geral

```mermaid
flowchart TD
    A[registrar_protocolo] --> B[check_invoice_status]
    B --> C[abrir_contestacao_cliente]
    C -->|success=false| Z[END]
    C -->|tem barcode| D[enviar_sms]
    C -->|sem SMS| E[consultar_contrato_corte]
    D --> E
    E -->|Conta Certa Manual elegível| F[abrir_sr_conta_certa_manual]
    E -->|caso padrão| G[atualizar_status_sr]
    F --> H[atualizar_status_sr_registro]
    H --> Z
    G --> Z
```

### Nó 1 — `registrar_protocolo`

Primeiro efeito da jornada:

```yaml
action: registrar_protocolo
```

Configura:

```yaml
scenario: "contestacao"
request_status: "Aberto"
status: "OPENED"
```

O protocolo retornado fica acessível em:

```text
$.vars.registrar_protocolo.protocolo_id
```

### Nó 2 — `check_invoice_status`

Consulta ou reaproveita o `CompleteInvoices` já obtido em prefetch.

O comentário do YAML deixa clara a intenção arquitetural: evitar uma segunda chamada desnecessária quando o payload já existe na sessão.

### Nó 3 — `abrir_contestacao_cliente`

Recebe grande parte do contexto necessário para a operação financeira, inclusive:

- cliente;
- fatura;
- serviço/item;
- valor;
- descrição;
- protocolo;
- tipo da contestação;
- motivo do ajuste;
- opção de devolução;
- regras de Conta Certa Manual;
- `double_refund`;
- dados de atendimento;
- `skip_invoice_item_validation`.

O `invoice_status` vem do nó anterior:

```yaml
invoice_status: $.vars.check_invoice_status.invoice_status
```

### Branch de falha financeira

```yaml
- from: abrir_contestacao_cliente
  to: END
  priority: 1
  when:
    eq: [$.vars.abrir_contestacao_cliente.success, false]
```

É avaliado primeiro. Se a validação financeira bloquear a contestação, não deve executar SMS, contrato ou SR.

### Branch de SMS

```yaml
when:
  all:
    - exists: $.vars.abrir_contestacao_cliente.barcode
    - neq: [$.vars.abrir_contestacao_cliente.barcode, ""]
```

Se a contestação produzir código de boleto, o fluxo passa por `enviar_sms`.

Caso contrário, a edge `priority: 99` segue diretamente para `consultar_contrato_corte`.

### Nó `consultar_contrato_corte`

Determina a regra de data de corte e também trata particularidade de item dependente de plano família.

### Branch Conta Certa Manual

O branch é propositalmente composto:

```yaml
when:
  any:
    - all:
        - apos_data_corte == true
        - contestation_success == true
        - manual_conta_certa_indicator == true
    - all:
        - dependent_invoice_item == true
        - contestation_registered == true
```

Isso expressa duas formas de elegibilidade:

1. regra normal após data de corte + indicador manual;
2. item de dependente cuja contestação foi registrada.

### `abrir_sr_conta_certa_manual`

Cria a SR de Conta Certa Manual.

### `atualizar_status_sr`

Caminho padrão, fecha/atualiza o protocolo principal.

### `atualizar_status_sr_registro`

Caminho usado após Conta Certa Manual, atualizando a SR correspondente.

### Pontos de atenção

- Não trocar prioridades sem revisar todos os branches.
- `success=false` precisa continuar precedendo qualquer efeito posterior.
- Reaproveitamento de prefetch evita chamadas duplicadas.
- É um workflow com múltiplos efeitos externos; qualquer retry deve ser analisado com idempotência.

---

## 11.9 `finalizar_atendimento.active.yaml`

```yaml
version: 1
```

## 11.10 `finalizar_atendimento.v1.yaml`

### Objetivo

Centralizar o fechamento final do atendimento.

### Nó único `finalizar`

```yaml
action: finalizar_atendimento_action
```

Entradas relevantes:

- `status`;
- `summary`;
- `msisdn`;
- `social_sec_no`;
- `message_id`;
- tipos informacionais de VAS;
- protocolo;
- flags de supressão/deferimento de eventos.

### Fluxo

```text
finalizar_atendimento_action -> END
```

### Observação

Finalizar atendimento é conceitualmente diferente de simplesmente alcançar `END` em qualquer workflow. `END` encerra aquele grafo; `finalizar_atendimento_action` implementa a semântica de negócio de fechamento de atendimento.

---

## 11.11 `invoice_explanation.active.yaml`

```yaml
version: 2
```

## 11.12 `invoice_explanation.v2.yaml`

### Objetivo

Explicar variação de fatura, aguardar a confirmação semântica do cliente e então:

- registrar aceite e protocolo final; ou
- registrar negativa e transferir para atendimento humano.

Também possui caminhos de falha de serviço e validação de tentativa.

### Visão principal

```mermaid
flowchart TD
    A[preparar] -->|success| B[formatar]
    A -->|service_failed| F[resposta_falha_servico]
    A -->|success=false| C[checar_tentativa]
    C -->|limite excedido| D[fim_intencao_invalida]
    C -->|caso contrário| E[texto_intencao_invalida]
    B --> P{{PAUSE}}
    P --> G[decisao]
    G -->|SIM| H[registrar_sim]
    H --> I[registrar_protocolo_aceite]
    I --> Z[END]
    G -->|NAO| J[registrar_nao]
    J --> K[handoff_pos_explicacao_nao]
    K --> Z
```

### Nó `preparar`

Action:

```text
preparar_invoice_explanation
```

Responsável por obter ou reutilizar a explicação base.

Recebe dados da fatura atual/passada e também:

```yaml
tentativa_anterior: $.vars.preparar.tentativa
```

Isso permite controlar tentativas dentro do estado do workflow.

### Nó `formatar`

Action:

```text
formatar_invoice_explanation
```

Ela monta a mensagem, mas **não controla a pausa**. A pausa está declarada no YAML.

Essa separação é intencional: apresentação e controle de fluxo não devem ficar acoplados.

### Pause do `formatar`

Sempre pausa após apresentar a explicação.

```yaml
allowed_values: ["SIM", "NAO", "CONTINUAR"]
```

O semantic classifier diferencia confirmação, negativa e continuação contextual.

#### Exemplos

```text
"sim"                         -> SIM
"entendi, obrigado"           -> SIM
"não resolveu"                -> NAO
"é a cobrança de 14,99"       -> CONTINUAR
"mês que vem fica mais barato?" -> CONTINUAR
```

### `decisao`

É um `no_op`. Sua função é fornecer um ponto explícito no grafo para branching após o resume.

### Caminho SIM

```text
decisao
  -> registrar_sim
  -> registrar_protocolo_aceite
  -> END
```

`registrar_protocolo_aceite` usa:

```yaml
workflow_response_final: true
```

para indicar que a mensagem retornada é a resposta final do workflow.

### Caminho NAO

```text
decisao
  -> registrar_nao
  -> handoff_pos_explicacao_nao
  -> END
```

`preparar_handoff_invoice_explanation` materializa:

```text
session_control = HUMAN_HANDOFF
```

A decisão de jornada está no workflow do Contas; a primitive de handoff é do framework.

### Caminhos de erro

`preparar` distingue:

- `success=true`;
- `service_failed=true`;
- `success=false` por validação/tentativa.

`checar_tentativa` decide se ainda pode pedir novamente ou se o limite foi excedido.

### Nós `checar_vas_variacao` e `finalizar_nao_resolvido`

Esses nós estão declarados para a política de VAS variado/não resolvido. Observe que, na versão atual, não existe edge de entrada para `checar_vas_variacao` partindo do fluxo principal SIM/NAO mostrado acima. Antes de reutilizar ou alterar esses nós, valide a intenção de jornada e os testes associados.

### Ponto crítico de lifecycle

Quando `registrar_protocolo_aceite` produz `workflow_response_final=true`, o workflow deve ser considerado terminal mesmo que alguma integração legada devolva metadata antiga com `PAUSED`. O próximo turno na mesma sessão não deve reutilizar `expected_input` desse workflow.

---

## 11.13 `pro_rata.active.yaml`

```yaml
version: 3
```

## 11.14 `pro_rata.v3.yaml`

### Objetivo

Explicar cobrança proporcional (`pro rata`) e tratar de forma diferente clientes com Plano Controle.

### Nó `preparar`

Action:

```text
preparar_pro_rata
```

Recebe planos e `has_plano_controle`.

A action decide se a jornada precisa interagir com o usuário:

```text
await_user_input = true/false
```

### Nó `formatar`

Possui pause condicional:

```yaml
pause:
  enabled: true
  when:
    eq: [$.vars.preparar.await_user_input, true]
```

Portanto, diferente do `invoice_explanation`, este workflow só pausa quando necessário.

### Expected input

```yaml
allowed_values: ["SIM", "NAO", "OUTRO"]
```

### `decisao_esclarecimento`

Branch:

- `SIM` -> `registrar_aceitou`;
- `NAO` -> `devolver_orquestrador`;
- qualquer outra situação -> `reperguntar_esclarecimento`.

### `reperguntar_esclarecimento`

Formata novamente e pausa de novo, retomando em `decisao_esclarecimento`.

Isso forma um pequeno loop conversacional controlado:

```text
reperguntar
   |
 pause
   |
   +----> decisao_esclarecimento
```

### Caso sem Plano Controle

Se `await_user_input=false`, a edge de prioridade 20 sai de `formatar` para:

```text
registrar_nao_controle -> END
```

### Caso SIM

```text
registrar_aceitou -> END
```

Essa action também registra protocolo/evento apropriado.

---

## 11.15 `termino_desconto.active.yaml`

```yaml
version: 1
```

## 11.16 `termino_desconto.v1.yaml`

### Objetivo

Formatar a resposta da capability de término de desconto.

### Nó único

```yaml
id: formatar
action: formatar_capability_resposta
```

Com:

```yaml
tipo: termino_desconto
```

Além de dados do plano/fatura/evidência de desconto.

### Conceito

A mesma action genérica `formatar_capability_resposta` é parametrizada pelo `tipo` da capability.

### Fluxo

```text
formatar_capability_resposta(tipo=termino_desconto) -> END
```

---

## 11.17 `valor_divergente.active.yaml`

```yaml
version: 1
```

## 11.18 `valor_divergente.v1.yaml`

### Objetivo

Formatar resposta para a capability de valor divergente.

### Nó único

```yaml
action: formatar_capability_resposta
input:
  tipo: valor_divergente
  msisdn: $.input.msisdn
```

É estruturalmente semelhante ao `termino_desconto`, mas com outro `tipo` e conjunto de inputs.

### Ponto de atenção

Se a capability passar a exigir dados adicionais, prefira explicitá-los no YAML, mantendo claro o contrato entre workflow e action.

---

## 11.19 `vas_estrategico.active.yaml`

```yaml
version: 3
```

## 11.20 `vas_estrategico.v3.yaml`

### Objetivo

Tratar VAS estratégico e bundle, apresentar explicação, coletar aceite/negativa e registrar o resultado.

### Visão geral

```mermaid
flowchart TD
    A[preparar] -->|await_user_input| P{{PAUSE}}
    A -->|sem pausa| B[resposta_bundle]
    P --> C[decisao]
    C -->|SIM| D[resposta_sim]
    C -->|NAO e estratégico| E[explicar_cancelamento]
    C -->|NAO bundle puro| B
    C -->|fallback| F[registrar_outro]
    D --> G[registrar_sim]
    E --> H[registrar_nao]
    B --> I[registrar_bundle]
    G --> Z[END]
    H --> Z
    I --> Z
    F --> Z
```

### Nó `preparar`

Action:

```text
preparar_vas_estrategico
```

Recebe `items` e `linhas`.

Possui pausa condicionada à saída da própria action:

```yaml
when:
  eq: [$.output.await_user_input, true]
```

### Pause

```yaml
allowed_values: ["SIM", "NAO", "OUTRO"]
resume_from: decisao
```

### `resposta_bundle`

Monta texto a partir de:

```yaml
$.vars.preparar.mensagem_bundle_fechamento
```

### `decisao`

É o ponto de branching depois da pausa.

#### SIM

Sempre vai para `resposta_sim`, seja bundle ou estratégico.

#### NAO + estratégico

```yaml
all:
  - has_estrategico_items == true
  - resposta_usuario == NAO
```

vai para `explicar_cancelamento`.

#### NAO + bundle puro

Quando não há item estratégico, vai para `resposta_bundle`.

#### fallback

`priority: 99` -> `registrar_outro`.

O comentário do arquivo ressalta que, em operação normal, `OUTRO` deveria ser interceptado/reperguntado pelo runtime antes de entrar nessa decisão; o fallback continua existindo como proteção.

### Registro final

Há actions diferentes para preservar o caminho de negócio:

- `registrar_sim`;
- `registrar_nao`;
- `registrar_bundle`;
- `registrar_outro`.

Todas terminam em `END`.

---

# 12. Mapa workflow -> actions Python

| Workflow | Action(s) principais | Implementação |
|---|---|---|
| `buscar_fatura` | `buscar_fatura` | `app/domain/contas/workflow_actions.py` |
| `buscar_informacao` | `buscar_informacao_rag`, `reescrever_resposta_buscar_informacao` | mesmo arquivo |
| `cancelamento_vas_avulso` | `cancelamento_vas_avulso_batch` | mesmo arquivo |
| `contestacao_tool` | `registrar_protocolo`, `check_invoice_status`, `abrir_contestacao_cliente`, `enviar_sms`, `consultar_contrato_corte`, `abrir_sr_conta_certa_manual`, `atualizar_status_sr` | mesmo arquivo |
| `finalizar_atendimento` | `finalizar_atendimento_action` | mesmo arquivo |
| `invoice_explanation` | `preparar_invoice_explanation`, `formatar_invoice_explanation`, `checar_tentativa_cvn`, `registrar_atendimento_invoice_explanation`, `registrar_protocolo_inicio`, `preparar_handoff_invoice_explanation`, `checar_vas_variado` | mesmo arquivo |
| `pro_rata` | `preparar_pro_rata`, `formatar_pro_rata`, `registrar_atendimento_pro_rata` | mesmo arquivo |
| `termino_desconto` | `formatar_capability_resposta` | mesmo arquivo |
| `valor_divergente` | `formatar_capability_resposta` | mesmo arquivo |
| `vas_estrategico` | `preparar_vas_estrategico`, `montar_resposta_texto`, `montar_explicacao_cancelamento_vas_estrategico`, `registrar_atendimento_vas_estrategico` | mesmo arquivo |

`no_op` e `montar_resposta_texto` são actions utilitárias registradas pelo mesmo registry de domínio.

---

# 13. Como criar um novo workflow

## Passo 1 — definir a responsabilidade

Pergunte:

- há mais de uma etapa?
- existe branching?
- existe efeito externo?
- existe pausa conversacional?
- precisa ser retomado em outro turno?

Se a operação for uma única função sem jornada, talvez uma tool/action simples seja suficiente.

## Passo 2 — registrar as actions

Em `workflow_actions.py`:

```python
@reg.action("consultar_exemplo")
def consultar_exemplo(params, state):
    result = service.consultar(...)
    return {
        "success": True,
        "dados": result,
    }
```

## Passo 3 — criar `nome.v1.yaml`

```yaml
name: meu_workflow
version: 1
start: consultar

nodes:
  - id: consultar
    action: consultar_exemplo
    input:
      msisdn: $.input.msisdn

edges:
  - from: consultar
    to: END
```

## Passo 4 — criar marcador ativo

```yaml
# meu_workflow.active.yaml
version: 1
```

## Passo 5 — adicionar branches

Sempre pense em fallback explícito.

```yaml
- from: consultar
  to: sucesso
  priority: 10
  when:
    eq: [$.vars.consultar.success, true]

- from: consultar
  to: falha
  priority: 99
```

## Passo 6 — adicionar pause somente quando a jornada exige input

Não coloque pausa dentro da lógica Python da action se ela faz parte do contrato do fluxo.

## Passo 7 — testar

No mínimo:

- happy path;
- cada branch;
- falha da integração;
- input ausente;
- pause;
- resume;
- resposta inválida;
- idempotência de efeitos externos;
- terminalidade;
- novo turno após finalização.

---

# 14. Como criar uma nova versão

Suponha que `vas_estrategico.v3.yaml` precise mudar materialmente.

1. copie para `vas_estrategico.v4.yaml`;
2. altere internamente `version: 4`;
3. implemente/teste a nova lógica;
4. mantenha v3 disponível;
5. altere somente depois:

```yaml
# vas_estrategico.active.yaml
version: 4
```

### Rollback

Basta voltar o marker:

```yaml
version: 3
```

sem apagar a v4.

---

# 15. Como depurar um workflow

## 15.1 Comece pelo status

Procure:

```text
COMPLETED
PAUSED
FAILED
```

## 15.2 Confira `workflow_name` e `workflow_version`

Isso confirma qual YAML realmente foi carregado.

## 15.3 Confira `trace`

Exemplo:

```text
preparar -> COMPLETED
formatar -> COMPLETED
formatar -> pause_resume RESUMED
decisao -> COMPLETED
registrar_sim -> COMPLETED
registrar_protocolo_aceite -> COMPLETED
```

O trace responde rapidamente:

- qual action executou;
- qual nó foi o último;
- se houve resume;
- se alguma action foi repetida.

## 15.4 Confira `vars`

Ao investigar uma edge:

```yaml
when:
  eq: [$.vars.consultar_contrato_corte.apos_data_corte, true]
```

primeiro valide o conteúdo real de:

```text
vars.consultar_contrato_corte.apos_data_corte
```

Não conclua que a edge está errada sem verificar a saída da action.

## 15.5 Confira `pause.expected_input`

Se o sistema está tratando uma frase como resposta de um fluxo anterior, procure:

```text
pending_domain_workflow
expected_input
transaction_status
workflow_resume
```

Após workflow terminal, esses latches não devem sequestrar o próximo turno.

## 15.6 Confira o marker `.active.yaml`

Um erro comum é editar `v3.yaml`, mas o marker continuar apontando para v2.

---

# 16. Regras de desenho recomendadas

## 16.1 Workflow orquestra; action executa

Bom:

```yaml
when:
  eq: [$.vars.validar.success, false]
```

Action retorna a evidência; YAML escolhe o próximo passo.

Evite colocar toda a jornada dentro de uma única action gigante.

## 16.2 Não colocar regra TIM no runtime genérico

Se a regra pertence a contestação, VAS ou fatura, ela deve ficar no domínio/configuração do agente, não hardcoded no framework.

## 16.3 Side effects precisam de idempotência

Especialmente:

- cancelamento;
- contestação;
- protocolo;
- SMS;
- criação de SR.

## 16.4 Pausa não deve reexecutar action anterior

Mantenha o desenho em que o pause é um contrato do nó e o resume segue para `resume_from`.

## 16.5 Prioridade deve ser intencional

Use números que deixem clara a hierarquia:

```text
1   bloqueio terminal crítico
10  caminho específico
20  segundo caminho específico
99  fallback
```

## 16.6 Não use output textual para decidir operação financeira

Branching deve usar campos estruturados como:

```text
success
barcode
apos_data_corte
dependent_invoice_item
```

não palavras encontradas em uma frase produzida por LLM.

---

# 17. Anti-patterns

### 17.1 Alterar `.active.yaml` sem criar a versão

Errado:

```yaml
version: 4
```

sem existir `nome.v4.yaml`.

### 17.2 Action não registrada

Se o YAML contém:

```yaml
action: minha_action
```

mas o registry não possui esse nome, o runtime falhará com action não registrada.

### 17.3 Referenciar `$.vars` de nó que ainda não executou

Exemplo incorreto:

```yaml
start: B

B:
  input:
    protocolo: $.vars.A.protocolo
```

se `A` nunca foi executado.

### 17.4 Branch sem fallback

Pode provocar falha de transição.

### 17.5 Usar `pause` para esconder estado de domínio

`pause` deve indicar interação com usuário, não substituir persistência correta de transação.

### 17.6 Reutilizar workflow terminal como contexto ativo

Um workflow terminado pode permanecer no histórico para auditoria, mas não deve continuar fornecendo `expected_input` ao próximo turno.

---

# 18. Checklist de code review

Antes de aprovar alteração em `workflows/`:

- [ ] `name` corresponde ao arquivo;
- [ ] `version` corresponde ao sufixo `.vN`;
- [ ] `active.yaml` aponta para uma versão existente;
- [ ] `start` existe;
- [ ] IDs de nós são únicos;
- [ ] todas as actions estão registradas;
- [ ] todos os `$.input` necessários são fornecidos pelo caller;
- [ ] referências `$.vars.<node>` apontam para nós que executaram antes;
- [ ] branches específicos têm prioridade anterior ao fallback;
- [ ] existe fallback quando necessário;
- [ ] `END` está alcançável;
- [ ] effects externos são idempotentes ou protegidos;
- [ ] pause não reexecuta action de efeito externo;
- [ ] `resume_from` existe;
- [ ] `allowed_values` são tokens internos coerentes;
- [ ] semantic classifier não transforma pergunta/hipótese em confirmação;
- [ ] workflow terminal limpa latch operacional;
- [ ] próximo turno na mesma sessão é testado;
- [ ] testes de happy path e todos os branches existem.

---

# 19. Resumo conceitual para novos desenvolvedores

Se você lembrar somente destas dez regras, já consegue navegar pela pasta com segurança:

1. **`.active.yaml` escolhe a versão; `.vN.yaml` contém a lógica.**
2. **`nodes` executam actions; `edges` decidem o próximo nó.**
3. **`$.input` é entrada; `$.vars.<node>` é resultado de nó anterior.**
4. **Menor `priority` é avaliada primeiro.**
5. **Uma edge sem `when` normalmente é o fallback.**
6. **`pause` suspende a jornada; `resume_from` determina onde continuar.**
7. **Tokens `SIM/NAO/CONTINUAR/OUTRO` são controle interno, não fraseologia.**
8. **Actions fazem domínio/integração; o YAML faz orquestração.**
9. **`END` termina o grafo; finalização de atendimento pode envolver action própria.**
10. **Workflow terminado não deve controlar o próximo turno, mesmo quando o `session_id` permanece igual.**

---

# 20. Referências de código dentro do projeto

Para aprofundar a implementação:

```text
/workflows/
    definições declarativas do Contas

/app/domain/contas/workflow_actions.py
    implementação das actions usadas pelos workflows

/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/models.py
    schema Pydantic do DSL

/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/repository.py
    resolução de active version

/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/runtime.py
    executor, branching, pause/resume e LangGraph

/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/registry.py
    registro e resolução das actions

/app/workflows/agent_graph.py
    grafo principal do agente Contas e integração com router/guardrails/judges
```

---

## Apêndice A — Exemplo completo comentado

```yaml
name: exemplo_confirmacao
version: 1
start: preparar

nodes:
  # Executa domínio e produz mensagem + dados estruturados.
  - id: preparar
    action: preparar_exemplo
    input:
      msisdn: $.input.msisdn

  # Apenas apresenta a mensagem e pausa.
  - id: apresentar
    action: montar_resposta_texto
    input:
      dados:
        texto_usuario: $.vars.preparar.mensagem
    pause:
      enabled: true
      return_from: $.output.mensagem
      expected_input:
        key: resposta_usuario
        allowed_values: ["SIM", "NAO"]
        normalize: upper_strip
      resume_from: decidir

  # Nó estrutural para branch pós-resume.
  - id: decidir
    action: no_op
    input: {}

  - id: confirmar
    action: executar_exemplo
    input:
      msisdn: $.input.msisdn

  - id: cancelar
    action: montar_resposta_texto
    input:
      dados:
        texto_usuario: "Operação não realizada."

edges:
  - from: preparar
    to: apresentar

  - from: apresentar
    to: decidir

  - from: decidir
    to: confirmar
    priority: 10
    when:
      eq: [$.input.resposta_usuario, SIM]

  - from: decidir
    to: cancelar
    priority: 20
    when:
      eq: [$.input.resposta_usuario, NAO]

  - from: confirmar
    to: END

  - from: cancelar
    to: END
```

Leitura em português simples:

> Prepare os dados, mostre uma mensagem, pare e aguarde SIM/NAO. Quando o usuário responder, continue em `decidir`. Se SIM, execute a operação; se NAO, responda que nada foi feito. Depois encerre o workflow.

---

**Fim do manual.**
