IA que negocia dívidas via WhatsApp 24/7, controlada por feature flag, auditada por LangGraph, observada por Langfuse. ~99% feature-complete; 101 commits em duas semanas; smoke real validado em produção há horas.
Não é o blurb do README — é o instantâneo do estágio.
Sistema FastAPI/Python que recebe mensagens via webhook Twilio, redireciona pelo estado da
conversa, e — em NEGOTIATION — executa um pipeline
cascata FAQ retrieval (Qdrant) → Sonnet 4.6 planner → domain agent fallback,
com PII redaction síncrona, output guard, persistência de propostas (PROPOSAL_COHERENCE)
e CRM conversacional vetorial (Supabase pgvector). Pagamento real via Asaas (PIX)
com retry queue Redis ZSET. Observabilidade via Langfuse + Sentry (release attribution
desde hoje). Toda orquestração CrewAI legacy substituída por LangGraph 1.1.10 declarativo
no estado mais crítico, atrás de feature flag default-OFF.
services/conversation/langgraph/ · 5 arquivosNão a arquitetura idealizada — o que está rodando às 22h53 de 2026-05-08.
flowchart TD
classDef edge fill:#e8eef4,stroke:#3d6e94,color:#2a4d6e,stroke-width:1px
classDef core fill:#fbf9f4,stroke:#1c2330,color:#1c2330,stroke-width:2px
classDef llm fill:#e7efe9,stroke:#2f5d4a,color:#2f5d4a,stroke-width:1px
classDef store fill:#f5ead4,stroke:#8a5a1f,color:#8a5a1f,stroke-width:1px
classDef ext fill:#f4e1d8,stroke:#a04a35,color:#a04a35,stroke-width:1px
classDef obs fill:#e8eef4,stroke:#3d6e94,color:#3d6e94,stroke-width:1px,stroke-dasharray: 5 3
User[Devedor
WhatsApp]:::edge
Twilio[Twilio
WhatsApp BR]:::edge
User -- mensagem --> Twilio
Twilio -- webhook POST --> NGINX[nginx
+ Caddy HTTPS]:::edge
NGINX --> APP
subgraph CORE [API · FastAPI 0.136.1 · Sentry release 196d8aa]
APP[webhook handler
+ middleware Sentry scope]:::core
ROUTER[conversation_router
state-aware]:::core
APP --> ROUTER
ROUTER -- ONBOARDING/AUTH/PLANS
SALES/CONTRACT/CHECKOUT --> CREWAI[domain_agent
CrewAI 1.14.4]:::core
ROUTER -- NEGOTIATION --> FLAG{flag
MIGRATE_NEGOTIATION_
TO_LANGGRAPH}:::core
FLAG -- false default --> IMP[handle_negotiation_flow
imperativo]:::core
FLAG -- true --> LG[run_graph
LangGraph 1.1.10]:::core
end
subgraph PIPELINE [Pipeline NEGOTIATION · FAQ → Sonnet → fallback]
PERC[perceive
PII redact + intent
+ sentiment via Haiku 4.5]:::llm
DOM[domain_agent_run
template-based]:::llm
BUILD[build_planner_ctx
+ proposal_history
+ similar_interactions]:::llm
FAQ[faq_check
Qdrant 1.15.5]:::llm
SONNET[plan_sonnet
Claude Sonnet 4.6]:::llm
FINAL[finalize_telemetry
output_guard]:::llm
IMP --> PERC
LG --> PERC
PERC --> DOM --> BUILD --> FAQ
FAQ -- hit similarity ≥ 0.60 --> FINAL
FAQ -- miss --> SONNET
SONNET -- success --> FINAL
SONNET -- empty --> FINAL
end
subgraph DATA [Camada de dados]
MONGO[(MongoDB 7.0.31
contractors · debtors
· negotiations · audit_log
TTL 5y LGPD ✓)]:::store
REDIS[(Redis 7.4
locks · payment retry ZSET
· msg buffer)]:::store
QDRANT[(Qdrant 1.15.5
30 entries FAQ
cosine HNSW)]:::store
SUPA[(Supabase pgvector
debtor_interactions
1536-dim cosine)]:::store
end
BUILD --> SUPA
FAQ --> QDRANT
SONNET --> MONGO
FINAL --> MONGO
APP --> REDIS
subgraph EXT [Externos]
ASAAS[Asaas
multi-tenant subaccount
PIX + boleto]:::ext
STRIPE[Stripe
checkout legacy]:::ext
WHISPER[OpenAI
gpt-4o-mini-transcribe
language=pt]:::ext
EMBED[OpenAI
text-embedding-3-small]:::ext
end
SONNET --> ASAAS
FINAL --> Twilio
Twilio -- audio --> WHISPER
WHISPER --> APP
BUILD --> EMBED
EMBED --> SUPA
EMBED --> QDRANT
subgraph OBSERV [Observabilidade]
LANGFUSE[Langfuse 4.5.1
trace negotiation_turn
+ spans manuais]:::obs
SENTRY[Sentry 2.59
release tracking
+ Anthropic/Redis/Pymongo
integrations]:::obs
end
PERC -.-> LANGFUSE
SONNET -.-> LANGFUSE
FINAL -.-> LANGFUSE
APP -.-> SENTRY
Twilio --> User2[Devedor recebe
resposta]:::edge
↑ Diagrama interativo · scroll para zoom · arrastar para mover · botão ↗ abre em janela cheia
Migração arquitetural, hardening de segurança, observabilidade e UX.
Pipeline NEGOTIATION encapsulado em grafo LangGraph declarativo, controlado
por MIGRATE_NEGOTIATION_TO_LANGGRAPH (default false).
6 mensagens reais validadas em prod via Sandbox; rollback drill em ~50s.
c136312 langgraph subpackage (state · nodes · graph · runtime)698ea8b 8 testes T-1..T-7 cobrindo cada pathedcacc5 early-branch em handle_negotiation_flowfc76977 instrumentation log marker [LANGGRAPH]d2139eb ship + archive + memoryAuditoria via 4 subagents paralelos contra docs oficiais. Top 10 ações priorizadas; 5 pacotes shipped no mesmo dia (cleanup, Sentry, TTL audit, Qdrant bump, Redis lock).
caba17f AUDIT_2026-05-08.md baseline5261fd9 remove 4 OTel deps (dead-weight)4ae48ec TTL 5y em domain_audit_logs (LGPD)27bfcb0 Sentry release + AnthropicIntegration19f69fa Qdrant 1.12.4 → 1.15.5 (snapshot + recreate)c40b3e7 redis.lock.Lock ownership-safe
Smoke #3 do audit revelou que domain_audit_logs em prod tinha
apenas _id_. $ne em partialFilterExpression
não é suportado pelo MongoDB e abortava todo o try/except.
196d8aa partialFilterExpression sem $neb8156a5 _get_anthropic_embedding broken path removidofadad42 Pydantic model_validate (Rust core)daa7b85 Whisper language=pt (anti-alucinação)
Endpoint GET /api/admin/crm/metrics retornando 8 métricas
agregadas, página React consumindo com TanStack Query, Layout menu
Administração → Métricas CRM (admin only). Smoke real via Playwright
validou render em prod.
b802874 backend admin_crm endpoint + 5 testes2198244 frontend AdminCRMMetrics.jsx + Layout entrycb7ddee adaptive threshold search_similar 0.70/0.55STATE_MANAGER_DECOMPOSITION (1930→1234, -36%). PROJECT_STRUCTURE_REORG (83 arquivos em 8 subpastas). handle_auth_flow decomposto em 1 dispatcher + 4 helpers (29 testes).
eca07a2 handle_auth_flow refactor + 2 fixes (1B+2C)c7d4063 search_similar integrado no PlannerContext49e9535 requirements pin to prod versions5cb8115 cleanup LangChain 0.x dead code (-57 LOC)
Archive stale _LANGGRAPH_MIGRATION_v1_NOT_APPLIED marcado e depois
deletado. Pre-DEFINE checklist agora obrigatório no CLAUDE.md.
7 features shipped foram migradas de features/ para
archive/.
4377476 mark stale archive + pre-define rulee1913b0 archive 7 shipped feature docsfb32a5b retroactive ship MULTI_TENANT_ASAASc0bb7c3 remove stale V1 langgraph archiveMineradas dos commits, audit doc e conversas das últimas 14 dias.
Grafo LangGraph decide resposta + telemetria. Caller imperativo
(run_graph) executa execute_negotiation_domain_side_effects,
persist_negotiation_domain_context e check_and_trigger_handoff
após graph.invoke().
Por quê: side effects pós-resposta dependem fortemente de state_manager
(objeto vivo). Movê-los para nós aumentaria acoplamento e tornaria rollback arriscado.
Confirmado pelo Audit A — válido enquanto não houver checkpointer.
LangfuseCallbackHandler)
Cada nó wrappa _emit_span com nomes idênticos aos do imperativo
(perception, domain_agent, faq_lookup, planner).
Zero regressão em test_langfuse_tracer.py.
Por quê: o callback handler oficial captura via abstrações LangChain (BaseChatModel),
mas usamos anthropic SDK direto — não capturaria as LLM calls. Audit C confirmou
que migrar perderia usage_details com tokens.
Audit B estimou ROI -71% custo Sonnet via cache_control.
Após validação factual: _SYSTEM_PROMPT tem ~150 tokens, totalizando
~200 com _FIRST_TURN_BLOCK.
Por quê: Anthropic exige mínimo 1024 tokens para Sonnet caching. Premissa do audit estava errada. Reabrir só se prompt crescer organicamente. Aprendizado: audits subsequentes medem o tamanho real antes de estimar ROI.
_create_indexes() escondia falhas reais
domain_audit_logs em prod tinha apenas o índice _id_.
message_templates.create_index falhava com
$ne in partialFilterExpression (não suportado por Mongo)
e abortava silenciosamente todos os créates seguintes do mesmo bloco.
Por quê: o pattern try: a; b; c; d; except: log.debug(...)
transforma erro em qualquer linha em "todos os índices subsequentes não existem".
Fix corrigiu apenas o índice problemático; refactor pra try/except per-collection
é V2.2 deferred (Pacote 6.1).
domain_audit_logs
expireAfterSeconds=157_680_000 na collection. Defesa em juízo
de cobranças exige retenção mínima do registro de comunicação.
Por quê: collection crescia ilimitada — descoberto pelo audit (R-A1). Time-series collection migration (compactação bucket-based) deferida para V2.2 quando volume produtivo justificar.
hnsw.ef_search=100 bloqueado em Supabase managed
Tentativas v1 (ALTER DATABASE) e v2 (SET em
CREATE FUNCTION) falharam com ERROR 42501 — pgvector GUCs
são superuser-only no Supabase managed.
Por quê: o role postgres do SQL Editor não é superuser real.
Aceitar default ef_search=40 (recall ~85-90%) com volume
atual baixo. Reabrir via iterative scan pgvector 0.8+ ou Supabase support
quando volume justificar.
Snapshot do que cabe na cabeça antes de pegar a próxima task.
Coisas que se violadas quebram o sistema de formas não-óbvias.
MIGRATE_CONVERSATIONAL_AI=true (default) e
MIGRATE_NEGOTIATION_TO_LANGGRAPH=false (default OFF).
Mudança requer docker compose up -d --force-recreate app — restart
simples não recarrega env_file.
Hot-fix via scp + docker cp deixa working tree do servidor dirty.
Próximo CI deploy falha com "Your local changes would be overwritten by merge".
Reset hard no servidor antes de cada push subsequente.
$ne em partialFilterExpression
MongoDB whitelist: $eq, $exists, $gt/$lt,
$type, $and, $or. $ne e
$exists: false rejeitados. Try/except gigante esconde isso —
validar índices via list_indexes() direto no driver.
Embeddings são via Voyage AI ou OpenAI text-embedding-3-small.
Path anthropic.embeddings.create com modelo retired
claude-3-sonnet-20240229 existia e quebrava silenciosamente
sob LLM_PROVIDER=anthropic — corrigido em b8156a5.
Antes de propor feature SDD nova, validar archives em
.claude/sdd/archive/ e ler precedentes. Archives podem
ser herdados de outras branches (_LANGGRAPH_MIGRATION_v1_NOT_APPLIED
veio do V1 e nunca foi aplicado no V2).
ADR-2 do LangGraph Fase 1: graph.invoke() não chama
execute_negotiation_domain_side_effects nem
persist_negotiation_domain_context. Quem chama é o caller
run_graph após o invoke. Reabrir essa decisão se introduzir
interrupt() ou checkpointer.
requirements.txt
47 deps com == exato (49e9535). Bump intencional
agora exige edição explícita. Eliminou risco de "bump cascata" descoberto
durante CREWAI_TO_LANGGRAPH P1 (langchain 0.1→1.2, anthropic 0.25→0.100).
Subagents general-purpose não conseguem escrever em
.claude/visuals/ (descoberto hoje gerando os HTMLs).
Workaround: salvar em /tmp/ e mover via main agent.
Main agent tem write OK (testado via touch).
Não é dívida técnica de código. É dívida de compreensão.
mongodb_provider._create_indexes()
Cada bloco try cobre múltiplas collections. Falha em uma
cancela o resto silenciosamente (nível DEBUG). Já causou um bug crítico
(índices nunca criados em prod). Pacote 6.1 do audit propunha refactor mas
foi deferido.
Sugestão: quebrar em try/except per-collection e gritar via logger.error,
não logger.debug. Adicionar smoke test que valida count de
índices esperados no boot.
/auth/me chamado em 15+ páginas
Dashboard, Pagamentos, Devedores, Layout, Negociações etc. cada um chama
apiClient.getCurrentUser() no mount. Layout já chama;
filhas chamam de novo. Cada navegação SPA dispara N requests redundantes.
Sugestão: centralizar em React Context populado uma vez no AuthProvider. Páginas filhas leem do context. V2.2.
Decomposição em 1 dispatcher + 4 helpers funcional, mas total subiu 22% por inflate em docstrings/type hints. O design pretendia diminuir. Ainda é melhor que God function — mas não é "fim".
Sugestão: revisar próximas extrações com olho na concisão. Targets ≤200 LOC no dispatcher original. Extrair lógica não-auth (e.g. session_manager calls) pra helpers separados.
Tools com @tool CrewAI em utils/: chamar função
direto (bypass CrewAI) ou redecorar como @langchain_core.tools.tool?
Sem decisão, BUILD da Fase 4 estoura timebox.
Sugestão: spike de 2h pré-Fase 4 prototipando ambas opções num único tool. Documentar trade-off em mini-DEFINE. Audit A já marcou esse risco (R-Roadmap-2).
project_audit_2026_05_08.md ainda lista P4 como ROI alto
O memory inicial dizia "Pacote 4 prompt caching Sonnet -71% custo". Após validação, P4 foi marcado N/A no audit doc — mas o memory pode ter menção residual desatualizada.
Sugestão: revisar memory ao final da sessão, conferir consistência com audit doc. Ou adicionar bullet "P4 N/A — premissa errada".
DESIGN_CREWAI_TO_LANGGRAPH.md está em archive/ e não foi atualizado.
Quem ler o DESIGN sem passar pelo audit doc vai assumir 1 dia.
Sugestão: adicionar nota no topo do DESIGN apontando pra audit. Ou copiar a nota de re-estimativa pra um anexo do DESIGN.
Não é prescrição — é o vetor da última semana de trabalho.