Decisões de arquitetura vigentes¶
Estas decisões foram reconstruídas a partir do código atual, usando context/decisoes-atuais.md e context/arquitetura/ como apoio histórico. Elas descrevem o que o sistema faz e suas consequências; não constituem aprovação de produção nem uma proposta de refatoração.
Infraestrutura declarada com AWS SAM¶
Contexto: a plataforma tem handlers HTTP, consumidores assíncronos e jobs periódicos com permissões distintas. Decisão: template.yaml declara funções Lambda, HTTP API, DynamoDB, SQS/DLQ e agendamentos; samconfig.toml contém perfis de execução.
Motivação: manter o contrato de implantação junto ao código e permitir build/deploy repetível. Consequências: parâmetros e variáveis precisam ser propagados para cada consumidor; uma alteração em .env local não muda a stack. Trade-off: a simplicidade operacional de Lambda traz limites de timeout, cold start e acoplamento aos serviços AWS. SSM escrito pelo script fora da stack não participa do mesmo rollback. Veja deploy e tests/unit/test_template_scoping.py.
DynamoDB single-table¶
Contexto: sessão, histórico, dedupe, configurações e projeção comercial precisam de acesso rápido por chave e operações condicionais. Decisão: uma tabela com PK, SK e GSI1; cada repository define seus prefixos e padrões de consulta.
Motivação: concentrar a persistência e combinar itens relacionados em transações sem administrar vários bancos. Consequências: mudanças em chaves/índices têm impacto amplo; o domínio precisa tolerar itens históricos sem campos novos. Trade-off: consultas ad hoc e relatórios não são equivalentes a SQL, e nem todos os itens têm TTL. Disponibilidade usa expiração lógica; sessões/mensagens não são apagadas automaticamente pela mera existência do atributo TTL na tabela. Consulte DynamoDB e os repositories em src/core/integrations/dynamodb/.
FIFO por telefone¶
Contexto: mensagens rápidas de uma conversa devem ser ordenadas sem serializar clientes diferentes. Decisão: o webhook publica wake-ups em SQS FIFO com grupo derivado do telefone; o atraso atual é zero.
Motivação: ordenar o consumo por conversa e permitir paralelismo entre números. Consequências: o mesmo telefone em instâncias diferentes compartilha a chave/grupo; FIFO não implementa isolamento por marca. Trade-off: uma falha pode atrasar o grupo e precisa de visibility timeout, redrive e DLQ coerentes. A deduplicação durável de inbound está em receipts DynamoDB, além das propriedades da fila. Valores e testes estão em SQS, src/webhook_handler/app.py e src/orchestrator/app.py.
Latest-message-wins¶
Contexto: o modelo pode estar gerando resposta enquanto chega uma mensagem que completa ou corrige o pedido. Decisão: receipts, sequência inbound e TurnGuard invalidam respostas superadas e condicionam o commit da sessão.
Motivação: evitar resposta e persistência baseadas em contexto antigo. Consequências: uma chamada LLM já iniciada pode consumir tokens e ser descartada; é preciso manter separado o que foi recebido, preparado, persistido e enviado. Trade-off: não existe transação entre DynamoDB e a entrega HTTP ao WhatsApp. O código reduz janelas de corrida e conserva resposta pronta para retry, mas não garante exatamente uma entrega externa em toda falha. Veja Orchestrator, TurnGuard e tests/unit/lambdas/test_orchestrator.py.
Três agentes e orquestração programática¶
Contexto: triagem, qualificação técnica e coleta cadastral têm objetivos diferentes. Decisão: três builders especializados, um invocador comum e dispatchers determinísticos que interpretam s.
Motivação: separar instruções e tornar efeitos testáveis sem depender do modelo. Consequências: adicionar comportamento exige revisar contrato, enum, dispatcher e estado, além do prompt. Trade-off: há mais manutenção coordenada e alguns turnos invocam mais de um agente. A saída do modelo é uma proposta; serviços desconhecidos, JSON inválido ou transições ilegais não viram ações arbitrárias. Fontes: src/core/agents/, src/core/application/action_mapping.py e agentes.
Contrato textual com JSON inicial¶
Contexto: cada resposta precisa combinar dados de controle e mensagem ao cliente. Decisão: o parser lê {JSON}Mensagem; o transporte LLM usa Responses API, mas o core mantém LLMClient.chat().
Motivação: um contrato comum serve aos três agentes e permite separar instrução de texto visível. Consequências: espaços ou texto antes de {, s inválido e JSON malformado são falhas de protocolo; o use case tenta reparo de formato antes do fallback. Trade-off: não é um schema imposto pelo provider, então validação e recuperação continuam necessárias na aplicação. Veja parser e resposta, src/core/application/parser.py e tests/unit/application/test_parser.py.
Context Packages sob demanda (s=99)¶
Contexto: a base inclui portfólio, amostras, preços e regras que nem toda conversa utiliza. Decisão: enviar catálogo resumido e permitir ao agente pedir packages por ID, com limite de enriquecimentos por mensagem.
Motivação: reduzir contexto irrelevante e disponibilizar conhecimento específico no momento necessário. Consequências: metadata e conteúdo precisam permanecer coerentes; package inexistente/inativo ou limite atingido tem comportamento definido de fallback. Trade-off: enriquecimento pode adicionar chamadas e latência. O repository dinâmico permite overrides no banco, com fallback nos arquivos versionados. Fontes: src/core/knowledge_base/, src/core/application/process_message_use_case.py e packages.
Confirmação de mudança de assunto (s=97 / s=98)¶
Contexto: uma dúvida lateral não deve descartar uma qualificação em andamento. Decisão: s=97 registra uma possível mudança; o fluxo s=98 encerra a sessão anterior e inicia outra com vínculo de histórico, conforme guardas do dispatcher.
Motivação: manter as solicitações separadas e preservar dados coletados. Consequências: IDs de sessão importam no replay do histórico e na projeção CRM. Trade-off: a validação de intenção depende parcialmente do contrato do modelo; o dispatcher também aceita s=98 quando há uma mudança pendente, portanto a regra textual de confirmação não equivale a um classificador independente do texto do cliente. Veja ações, pending_subject_change_text e testes do caso de uso.
Adapters e Protocols¶
Contexto: o mesmo fluxo deve funcionar em testes sem efeitos reais e em operação com diferentes providers. Decisão: Protocols e factories escolhem cliente real/stub, e casos de uso recebem Dependencies.
Motivação: testar decisões e contratos sem rede e limitar conhecimento de HTTP à integração. Consequências: o tipo StubResponse também é usado em produção, e nomes de interface podem sobreviver à troca de provider. Trade-off: nomes históricos e Protocols localizados junto aos adapters tornam a organização menos uniforme; documentar o código real evita inventar uma camada ports/ que não existe. Consulte como adicionar integração e tests/unit/integrations/test_factory.py.
CRM compatível com HubSpot v3¶
Contexto: a evolução do negócio migrou a intenção de integração HubSpot para um CRM GoGenetic compatível. Decisão: manter interface e variáveis HUBSPOT_*, mas permitir URL/configuração de outro servidor com o contrato esperado.
Motivação: reutilizar payloads e consumidores. Consequências: HUBSPOT_BASE_URL define o endpoint; o nome da variável sozinho não comprova o provider real. Trade-off: compatibilidade precisa abranger busca, criação, atualização, associação, stages e erros, não apenas endpoints com nomes parecidos. A configuração remota não foi consultada nesta edição. Fontes: src/core/integrations/hubspot/, context/integrations/crm-comercial-gogenetic.md e CRM.
Projeção CRM durável e assíncrona¶
Contexto: evolução de sessão e estado comercial podem falhar independentemente. Decisão: persistir a projeção desejada e reconciliá-la no CrmSyncProcessor, acionado a cada minuto por src/crm_sync/app.py. O worker cria/atualiza deals; o caminho de confirmação ainda pode escrever contato CRM e eGestor de forma síncrona.
Motivação: tornar pendências observáveis e permitir recuperação sem bloquear cada mensagem em HTTP comercial. Consequências: estado de conversa e status de sync são eixos diferentes; PENDING, BLOCKED e UNCERTAIN precisam de diagnóstico próprio. Trade-off: há consistência eventual e reconciliação operacional; criar novamente após um timeout incerto pode duplicar negócios. O worker não usa DynamoDB Streams nem a fila inbound. Fontes: src/core/application/crm_sync.py, repository sync, SAM e CRM.
WhatsApp oficial e atendimento humano autenticado¶
Contexto: a conversa automatizada precisa ser recebida/enviada pela API oficial e continuada por pessoas. Decisão: Cloud API Meta com verificação/assinatura, e painel /atendimento autenticado por link único e sessão com CSRF.
Motivação: centralizar a continuidade no histórico e no estado da aplicação. Consequências: HANDOFF_PENDING/HUMAN interrompem o fluxo LLM; operadores autorizados podem atuar de forma colaborativa, e disponibilidade diária determina notificações. Trade-off: a existência de assigned_to não impõe exclusividade entre operadores; limites de janela/envio Meta e segurança dos links continuam relevantes. Fontes: src/core/integrations/whatsapp/, src/core/application/operator_auth.py, src/core/application/human_conversation.py e atendimento humano.
Como evoluir estas decisões¶
Para mudar uma decisão, registre o problema concreto, as alternativas consideradas, compatibilidade de dados/contratos, novos testes e o plano de migração. Atualize a página do componente e esta seção no mesmo conjunto de mudanças. Não transforme pendências de LGPD, credenciais ou disponibilidade de provider em afirmações de aprovação.