X-Admin-Key), em /admin/tenants/{id}.
Quem decide
O ZapMeow não escreve a resposta nem avalia a condição: ele pergunta ao decisor e executa. O decisor é configurado na seçãoflow do tenant:
kind: "http": o ZapMeow fazPOSTnaurlcom o turno e espera a decisão. Se existir o segredoflow_decider_tokenno cofre do tenant, vai emAuthorization: Bearer.kind: "anthropic": chama a API da Anthropic com o segredoanthropic_api_keydo tenant (modelo emdecider.model, padrãoclaude-sonnet-5-5).
DecideIn): mode (shadow, simulate ou live), nodeKey, nodeTitle, instructions, options[] (to, title, when), collect[] (campos a extrair), messages[] (from: lead ou ia), contactId, opportunityId.
Saída (DecideOut): reply (texto ao lead ou vazio), next (chave de um nó de options, ou vazio para ficar), handoff (passar para uma pessoa), fields (campo → valor, só dos pedidos em collect), reason. Passagem para um nó que não é saída do atual e campo não pedido são descartados, com nota no evento.
Desenhar
tenantId e ghlPipelineId. Regras: o pipeline e os estágios precisam existir no espelho do GHL (sincronize antes: 409 se não); um nó por estágio; kind é stage (precisa de ghlStageId) ou end (fim; não sai); até 100 nós, 300 arestas, 10 ações por lista. O pipeline de um fluxo não muda depois de criado. Um fluxo em sombra ou ao vivo por pipeline.
Catálogo de ações
As tools respeitam o modo do tenant (
config/tools): em pede_ok (padrão) o fluxo propõe e uma pessoa aprova.
Sombra antes do ao vivo
draft: só desenho; não processa conversa.shadow: observa as conversas reais do pipeline e grava emzm_flow_eventso que faria (ações ao sair, passagem, ações ao entrar). Não envia, não mexe no GHL e não chama nenhuma tool. O mundo segue como está; quando o card muda de estágio, o eventocomparediz se bateu com a previsão (match).live: executa. Só se chega aqui peloshadow:draft → livedá409, eshadow → liveconfere o relatório (GET /flows/{fid}/shadow): dias em sombra, conversas reais e eventos mínimos (padrão 7 dias, 3 conversas, 10 eventos; configuráveis na seçãoflow, com piso de 1 conversa e 1 evento). Conversa de teste (simulação), eventos de erro eskippednão contam.- Editar o desenho de um fluxo ao vivo o devolve para a sombra, na versão seguinte (a contagem recomeça).
live → shadowé o botão de reversão.
Conversa de teste (simulação)
POST /flows/{fid}/simulate com {"chat": "teste-1", "message": "tenho interesse", "startNode": "novo"} roda um turno de uma conversa que não existe no WhatsApp: o histórico fica guardado, o decisor responde e o nó segue as passagens escolhidas. Nada é enviado e o GHL não é tocado. Funciona em qualquer estado, inclusive rascunho. Responde {event, run}.
Acompanhar
Como a conversa encontra o fluxo
A mensagem do lead (chat individual) procura o contato do GHL pelo telefone (/contacts/search/duplicate, vínculo guardado em zm_flow_links), pega a oportunidade aberta mais recente num pipeline com fluxo em sombra ou ao vivo e começa no nó do estágio dela. Lead sem contato no GHL ou em estágio fora do desenho não é acompanhado (o evento skipped explica). Várias mensagens seguidas geram uma resposta só, sobre a conversa inteira.
O humano manda no card
- Pessoa move o card (webhook do GHL, ou o espelho ao começar o turno): o nó reposiciona sem executar ações (evento
reposition; na sombra,compare). Oportunidade ganha, perdida ou abandonada encerra o acompanhamento. - Passagem em
pede_ok: o nó só muda depois do OK do pedido; aprovado, o eventoconfirmedroda as ações ao entrar; recusado, a conversa fica onde estava (rejected). - Handoff pausa só este motor. Respostas que outro sistema (ex.: o Convex do tenant Glow) manda pela fila
outboxnão são bloqueadas pela pausa.
Limites conhecidos
- O motor guarda estado no Postgres, mas as travas por conversa são em memória: mantenha 1 réplica.
- Condições são avaliadas pelo decisor, não pelo ZapMeow: a qualidade vem do decisor.
- Mensagem de áudio do lead entra no histórico como
[audio](sem transcrição).