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_inputeresume_from; - como funcionam
$.input,$.vars,$.outpute o estado interno; - como uma
actiondeclarada no YAML se conecta a Python; - como um workflow pausa e continua em outro turno;
- como o classificador semântico de
expected_inputfunciona; - 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:
<nome>.active.yaml— marcador da versão ativa;<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,tracee estado; - ordena edges por
priority; - avalia
when; - implementa pause/resume;
- usa checkpoint do LangGraph em produção;
- retorna
COMPLETED,PAUSEDouFAILED.
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
formatar_invoice_explanationé executada;- a mensagem produzida é obtida de
$.output.mensagem; - o runtime persiste o checkpoint;
- retorna
status=PAUSED; - a mensagem é enviada ao usuário;
- 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
Nó 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.
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:
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:
- regra normal após data de corte + indicador manual;
- 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=falseprecisa 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
Nó 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.
Nó 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=falsepor 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.
Nó 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
Nó 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
Nó 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.
- copie para
vas_estrategico.v4.yaml; - altere internamente
version: 4; - implemente/teste a nova lógica;
- mantenha v3 disponível;
- 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/:
namecorresponde ao arquivo;versioncorresponde ao sufixo.vN;active.yamlaponta para uma versão existente;startexiste;- IDs de nós são únicos;
- todas as actions estão registradas;
- todos os
$.inputnecessá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;
ENDestá alcançável;- effects externos são idempotentes ou protegidos;
- pause não reexecuta action de efeito externo;
resume_fromexiste;allowed_valuessã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:
.active.yamlescolhe a versão;.vN.yamlcontém a lógica.nodesexecutam actions;edgesdecidem o próximo nó.$.inputé entrada;$.vars.<node>é resultado de nó anterior.- Menor
priorityé avaliada primeiro. - Uma edge sem
whennormalmente é o fallback. pausesuspende a jornada;resume_fromdetermina onde continuar.- Tokens
SIM/NAO/CONTINUAR/OUTROsão controle interno, não fraseologia. - Actions fazem domínio/integração; o YAML faz orquestração.
ENDtermina o grafo; finalização de atendimento pode envolver action própria.- Workflow terminado não deve controlar o próximo turno, mesmo quando o
session_idpermanece 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.