# 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. **`.active.yaml`** — marcador da versão ativa; 2. **`.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.` Após cada action retornar um dicionário, o runtime armazena o resultado em: ```text $.vars. ``` 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.` 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.` é 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.**