Files
agent_contas/tests/docs/MANUAL_DESENVOLVEDOR_WORKFLOWS_CONTAS.md
2026-08-31 21:11:57 -03:00

39 KiB

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

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:

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

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:

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.

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:

app/domain/contas/workflow_actions.py

por meio de:

reg = WorkflowActionRegistry()

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

Uma action deve receber:

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 é:

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:

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.

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

- from: preparar
  to: formatar

ou condicionalmente:

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

4.6 END

END representa término do grafo.

- 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:

input:
  msisdn: $.input.msisdn
  invoice_id: $.input.invoice_id

Se o workflow foi iniciado com:

{
  "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:

$.vars.<id_do_no>

Exemplo:

- id: registrar_protocolo
  action: registrar_protocolo

Se a action retornar:

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

então outro nó pode usar:

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:

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

- id: preparar
  action: preparar

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

Fluxo:

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:

priority: 10

é avaliada antes de:

priority: 99

6.1 Fallback padrão

Um padrão comum é:

- 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:

when:
  eq: [$.vars.preparar.success, true]
when:
  neq: [$.vars.x.barcode, ""]
when:
  exists: $.vars.x.barcode
when:
  all:
    - eq: [$.vars.x.a, true]
    - eq: [$.vars.x.b, true]
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:

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:

- 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:

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:

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:

" 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:

"entendi, obrigado, era só isso"

semanticamente é SIM.

Já:

"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:

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:

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:

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

No invoice_explanation, por exemplo:

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:

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

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

name: buscar_fatura
version: 1
start: buscar_fatura

buscar_fatura

- 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

- from: buscar_fatura
  to: END

Não há branch nem pausa.

Modelo mental

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

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

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ó:

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

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

version: 1

11.6 cancelamento_vas_avulso.v1.yaml

Objetivo

Executar cancelamento em lote de VAS avulso.

Nó único

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:

request_status: "Fechado"
status: "CLOSED"

Fluxo

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

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

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:

action: registrar_protocolo

Configura:

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

O protocolo retornado fica acessível em:

$.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:

invoice_status: $.vars.check_invoice_status.invoice_status

Branch de falha financeira

- 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

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.

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:

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

version: 1

11.10 finalizar_atendimento.v1.yaml

Objetivo

Centralizar o fechamento final do atendimento.

Nó único finalizar

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

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

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

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

preparar

Action:

preparar_invoice_explanation

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

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

tentativa_anterior: $.vars.preparar.tentativa

Isso permite controlar tentativas dentro do estado do workflow.

formatar

Action:

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.

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

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

Exemplos

"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

decisao
  -> registrar_sim
  -> registrar_protocolo_aceite
  -> END

registrar_protocolo_aceite usa:

workflow_response_final: true

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

Caminho NAO

decisao
  -> registrar_nao
  -> handoff_pos_explicacao_nao
  -> END

preparar_handoff_invoice_explanation materializa:

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

version: 3

11.14 pro_rata.v3.yaml

Objetivo

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

preparar

Action:

preparar_pro_rata

Recebe planos e has_plano_controle.

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

await_user_input = true/false

formatar

Possui pause condicional:

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

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:

reperguntar
   |
 pause
   |
   +----> decisao_esclarecimento

Caso sem Plano Controle

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

registrar_nao_controle -> END

Caso SIM

registrar_aceitou -> END

Essa action também registra protocolo/evento apropriado.


11.15 termino_desconto.active.yaml

version: 1

11.16 termino_desconto.v1.yaml

Objetivo

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

Nó único

id: formatar
action: formatar_capability_resposta

Com:

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

formatar_capability_resposta(tipo=termino_desconto) -> END

11.17 valor_divergente.active.yaml

version: 1

11.18 valor_divergente.v1.yaml

Objetivo

Formatar resposta para a capability de valor divergente.

Nó único

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

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

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

preparar

Action:

preparar_vas_estrategico

Recebe items e linhas.

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

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

Pause

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

resposta_bundle

Monta texto a partir de:

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

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:

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

Passo 3 — criar nome.v1.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

# meu_workflow.active.yaml
version: 1

Passo 5 — adicionar branches

Sempre pense em fallback explícito.

- 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:
# vas_estrategico.active.yaml
version: 4

Rollback

Basta voltar o marker:

version: 3

sem apagar a v4.


15. Como depurar um workflow

15.1 Comece pelo status

Procure:

COMPLETED
PAUSED
FAILED

15.2 Confira workflow_name e workflow_version

Isso confirma qual YAML realmente foi carregado.

15.3 Confira trace

Exemplo:

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:

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

primeiro valide o conteúdo real de:

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:

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:

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:

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:

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:

version: 4

sem existir nome.v4.yaml.

17.2 Action não registrada

Se o YAML contém:

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:

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:

/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

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.