Funil paver US · cadência de WhatsApp · 20/08/2026
É um agente que conversa com lead pelo WhatsApp pessoal do Lucas — lê o quiz, lê o diagnóstico da empresa, consulta a agenda do Cal.com e escreve a resposta na voz dele. Hoje ele está desligado do mundo: nada do que ele escreve chega a lead nenhum, e a única forma de falar com ele é uma chamada HTTP de teste.
Fonte: dados-turnos.json, o log dos 22 turnos. Latência: média 2.528 ms, mediana 2.667 ms, de 784 ms a 4.690 ms. Modelo em 22 de 22 turnos: openai/gpt-4o via OpenRouter. Onze turnos chamaram pelo menos uma tool (ler_lead 10 vezes, consultar_agenda 7, ler_diagnostico 6). Vinte turnos no estilo E1-PRE-BOOKING, um no E0-PRE-CALL, um no E2-POS-REUNIAO.
A régua determinística de WhatsApp já roda em produção desde 17/08 com um princípio único: nada envia mensagem exceto o poller. Os webhooks só escrevem estado; a única chamada de saída do sistema inteiro mora num lugar só, e é lá que ficam os guardrails, o log, o dedupe e o freio de mão.
O agente foi encaixado dentro desse princípio, não ao lado dele. Ele não ganhou uma tool de enviar mensagem — não existe tool de enviar mensagem. Ele escreve o que quer mandar numa fila (paver_outbox) e quem decide se aquilo sai, e quando, continua sendo o poller.
Por que isso importa mais do que parece: se o agente pudesse enviar, cada guardrail teria que ser reimplementado dentro dele — e a próxima peça que aprendesse a falar teria que reimplementar de novo. Com um ponto de saída só, o freio de mão é um comando, não uma auditoria.
Instância flow-dev.lucassilvano.com.br. Status conferido na API do n8n em 20/08.
| Workflow | Papel | Estado |
|---|---|---|
| Paver — SDR agente wLnG3Pl4cOcS8iI7 |
O orquestrador. Carrega config, estado, conversa e prompt; chama as tools; roda o verificador; grava o turno; enfileira ou escala. | SEM GATILHO PRÓPRIO Marcado como ativo no n8n, mas o único gatilho é “quando outro workflow chama”. Não tem webhook nem cron: nada do mundo o alcança sozinho. |
| Paver — SDR harness de teste cVzzN9d3OuvqAFVD |
A porta de teste. Webhook /webhook/paver-agent-test, confere o segredo, chama o agente em modo teste e devolve a resposta com o porquê. |
ATIVO É a única forma de falar com o agente hoje. |
| tool: ler_lead 6YFIAjbR0orKSRpn |
Lê a aba leads da planilha, casa o lead e cura os campos. |
ATIVO Sub-workflow: só roda quando o agente chama. |
| tool: ler_diagnostico k53zhrro4zXKowB4 |
Lê a aba diagnosticos, entrega o resumo_agente e aplica as travas do nao_dizer daquele lead. |
ATIVO Sub-workflow. |
| tool: consultar_agenda ncjY6H2MqXoT5qzZ |
GET dos slots no Cal.com, filtra e formata no fuso do lead. É a única origem legítima de horário. | ATIVO Sub-workflow. |
| Paver — Cadência poller q8m0sqLT1WWl6LkJ |
O único que envia. Cron de 1 minuto, guardrails, log e dedupe. | ATIVO E ENVIANDO Em produção desde 17/08. Não conhece o agente ainda. |
| Paver — WhatsApp inbound lcHxd6tIPHuE8BD8 |
Recebe tudo que chega no número pessoal, identifica o remetente e descarta antes de ler o conteúdo se não for lead em cadência. | ATIVO Pausa a régua. Não acorda o agente. |
| Paver — Cal.com booking → cadência JWnsBH5b9TTBkWdn |
Webhook do Cal.com: agendou, remarcou ou cancelou, recalcula a régua. | ATIVO |
O orquestrador está com active: true na API, e não inativo. Isso não muda a conclusão — com executeWorkflowTrigger como único gatilho, “ativo” só significa que ele aceita ser chamado; quem o chama hoje é o harness de teste, e mais ninguém.
Nascem vazias, criadas em 20/08. A escolha de desenho é a mesma dos templates da régua: texto e estado vivem em tabela, não em workflow — trocar um prompt não exige deploy.
| Tabela | ID | Para que serve |
|---|---|---|
paver_wa_inbox | 2PC64kHEBpjeYneo | A conversa crua, mensagem por mensagem. O campo direcao tem três valores e não dois — lead, lucas, robo — porque juntar o Lucas e o robô num “saída” só apagaria a distinção que o agente mais precisa: não escrever por cima do dono. |
paver_agent_state | 1Lm6S28Ds117VD3M | Uma linha por lead: qual estilo está em uso, quando acordar, turnos hoje, autopilot (prepara mas não solta) e opt_out_at (para de falar com esse lead). É o kill switch por pessoa. |
paver_outbox | SIujGGMMLg4n7hsw | A fila do que o agente quer mandar. Ele só escreve aqui. Guarda enviar_apos, motivo_bloqueio e o agent_run_id que gerou a linha. |
paver_agent_runs | OAGJG1uCGpUzNIhm | A auditoria de cada turno: entrada, saída, tools chamadas, veredito do verificador, modelo real (não o configurado), tokens e latência. É de onde saem os 22 turnos deste dossiê. |
paver_agent_prompts | FH80sUA2XZQGyTsC | O prompt de cada estilo, versionado, com ativo e uma nota de por que a versão mudou. Foi assim que a correção do §7 subiu: linha nova, versão 2. |
Mais seis chaves na config que já existia (paver_cadence_config, FefJT4aIzmUAPT4V), todas nascidas com o agente desligado: AGENT_ENABLED=off, AGENT_DEBOUNCE_S=45, AGENT_MAX_TURNOS_LEAD_DIA=12, AGENT_MAX_REPLIES_DIA=60, AGENT_MAX_PROATIVO_DIA=10, AGENT_MODELO=openai/gpt-4o.
Responder e iniciar têm baldes separados de propósito. Responder quem acabou de escrever é a categoria segura; iniciar conversa é a que arrisca o número. Num teto único, uma sequência de respostas legítimas engoliria a cota e travaria a iniciativa — ou o contrário.
Cada estilo é um prompt inteiro e autossuficiente, não um prompt-base com variações: o workflow lê uma linha só. A voz e as regras duras se repetem em cada corpo em vez de morarem num lugar que ninguém carrega.
| Estilo | Quando vale | O que ele faz — e o que não faz |
|---|---|---|
E1-PRE-BOOKING |
Preencheu o quiz, não marcou a call. | O que mais fala. Tira a dúvida que travou o cara e coloca a call na agenda. É o único que oferece horário, e só com o que consultar_agenda devolveu, “com as palavras que ela devolveu”. 20 dos 22 turnos de teste rodaram aqui. |
E0-PRE-CALL |
Já marcou, a call é no futuro. | O que menos fala, de propósito. A régua já manda confirmação, véspera, manhã e o link 15 minutos antes; se o agente reconfirmar dia e hora, o lead recebe a mesma coisa de duas fontes e o Lucas parece desorganizado. Tira dúvida e remarca se pedirem. Nada mais. |
E2-POS-REUNIAO |
A call aconteceu, ou ele não apareceu. | Onde mora o dinheiro e onde a postura importa mais. Reabre com ângulo novo, trata a objeção que ficou e marca o próximo passo com hora. Proibido implorar: todo toque traz uma ideia nova, nunca “e aí, pensou?”. |
OFF |
Cliente, no-show para ligar, perdido, opt-out. | O agente não é chamado. O corpo do prompt diz literalmente: “se este prompt rodou, é bug”. |
Num agente anterior do mesmo dono, o LLM inventava preços mais baratos: o histórico da conversa carregava promoções velhas nos checkpoints, e o modelo repetia o número antigo. Trocar o prompt não corrigiu. O validador determinístico de saída corrigiu. A tabela de regras aqui é outra, mas o padrão é o mesmo: fato de negócio não se confia à geração, se verifica na saída.
Nada sai sem passar por aqui. Se bloqueia, não envia e o caso escala pro humano. E há um princípio que faz o verificador ser útil justamente quando o sistema falha: campo ausente no contexto degrada sempre pro lado seguro — sem a lista de tools chamadas, o agente não agendou nada; sem os horários da tool, ele não sabe horário nenhum; sem a contagem de mensagens, ele está no começo da thread, onde link é proibido.
| Regra | O que ela pega |
|---|---|
| O1 | Dinheiro, em qualquer forma. $, US$, R$, “mil/milhão/quinhentos”, “3k”, “1.500”, “19,90” e todo inteiro de três dígitos ou mais (com data e hora mascaradas antes, senão “19h30” viraria falso positivo). A allowlist é vazia por desenho: no WhatsApp o número de valores mencionados é zero, porque o preço é revelado na call numa sequência que vale $3.000 por lead. |
| O2 | Garantia errada e pressão falsa. “Devolvo o dinheiro”, “reembolso”, “money back” — a garantia real é “sigo trabalhando de graça até você ter lucro”, e devolução foi improvisada ao vivo em quatro calls. E escassez inventada: “desconto”, “promoção”, “últimas vagas”, “só hoje”. A escassez desta oferta é territorial e se diz na call. |
| O3 | Súplica. “Fico no aguardo”, “qualquer coisa me chama”, “e aí, pensou?”, “desculpa incomodar”, “fico à disposição”, “só passando pra saber”. A postura é que o tempo correndo é o dele, não o seu. |
| O4 | Disse que agendou sem ter agendado. “Marquei”, “confirmei”, “tá na agenda”, “bloqueei o horário” só passam se a tool agendar_reuniao tiver sido chamada naquele turno. |
| O5 | Horário inventado. A mais importante do conjunto: toda data e hora do texto tem que existir literalmente no que a tool devolveu. A comparação respeita fronteira de caractere, porque “9h” é substring de “19h” e um includes() cru deixaria o agente marcar às 9 o que a agenda ofereceu às 19. |
| O6 | Placeholder vazado. Qualquer chave entre chaves no texto final. É a versão-agente da regra da régua: melhor bloquear do que mandar {link} cru pro lead. |
| O7 | Tamanho e forma. Mais de 600 caracteres, mais de 4 blocos, mais de 1 ponto de interrogação. E texto vazio nunca sai: mensagem em branco no WhatsApp do dono é bug visível pro lead. |
| O8 | Idioma. Frase montada em inglês. A heurística não olha “palavra inglesa”, olha palavra funcional inglesa (the, you, is, with) — porque “paver”, “job”, “hardscape” e “free estimate” são a fala normal do avatar e não podem disparar. |
| O9 | Link. Nenhum link antes da terceira mensagem enviada, e mesmo depois só o Meet daquela call ou o cal.com. Regra anti-ban: link cru cedo na thread é assinatura de spam, e número pessoal banido custa mais que a automação inteira. |
| O10 | Fronteira factual deste lead. A lista nao_dizer que vem do diagnóstico daquela empresa. O que um lead não pode ouvir não é o mesmo do outro. |
| A1 | Avisos, que não bloqueiam. Travessão usado como pontuação, emoji, e o léxico do anti-alvo (“mais clientes”, “fluxo previsível”, “mês cheio, mês vazio”) — que fala com quem já tem trabalho demais. Não bloqueia porque emudecer resposta boa custa mais que o aviso. |
São 146 asserções só de teste do verificador; a suíte da cadência inteira passou de 119 para 284 asserções.
Turno de 20/08, 19:41 UTC. Estilo E1-PRE-BOOKING, prompt versão 1, 3.587 ms.
O lead escreveu
O agente foi buscar o fato
ler_lead — lê a aba leads da planilha e casa o lead pela chave (o nome, o serviço do quiz, o fuso).
consultar_agenda — GET dos slots no Cal.com, filtrados e formatados no fuso dele.
O agente respondeu
Veredito
APROVADO — nenhuma das 10 regras disparou. Três blocos (limite: 4), zero pergunta, zero valor monetário, zero link, e toda data e hora do texto existe literalmente no que a consultar_agenda devolveu.
O log de turnos guarda quais tools rodaram, não o payload que elas devolveram. O que garante que “quinta dia 20 às 18h” veio mesmo da agenda, e não da imaginação do modelo, é a regra O5: ela compara cada data e hora do texto com o retorno da tool, e este turno passou. É a diferença entre confiar e verificar — e a seção seguinte mostra o mesmo mecanismo pegando o caso oposto.
Duas famílias de bloqueio apareceram nos 22 turnos, e elas contam histórias opostas: uma é o sistema funcionando, a outra é o sistema sendo rígido demais.
Provocado com um pedido de repetição, o agente devolveu um horário que nenhuma tool tinha dito. Não chamou tool nenhuma naquele turno: a lista de tools veio vazia, e mesmo assim ele afirmou dia e hora.
O lead escreveu
O agente escreveu (e não saiu)
Veredito
BLOQUEADO — três disparos da O5 na mesma frase:
data/hora fora do que a tool devolveu: "terca"
data/hora fora do que a tool devolveu: "dia 25"
data/hora fora do que a tool devolveu: "14h"
Tools chamadas neste turno: nenhuma. Latência 1.049 ms.
O mesmo comportamento se repetiu num segundo turno, com a mesma saída literal. Isto é o verificador funcionando. Em produção, essa mensagem é um lead bloqueando a terça de manhã e aparecendo para uma call que não existe — o tipo de erro que não se descobre pelo log, se descobre pelo lead sumindo.
Do outro lado, três respostas boas foram barradas por formatação. A mais clara: o agente ofereceu dois horários reais, vindos da agenda, e estourou o limite de quatro blocos só porque isolou o “ou” numa linha só dele.
O lead escreveu
ANTES — prompt versão 1, bloqueado
BLOQUEADO — O7: 5 paragrafos (max 4). Tools: ler_lead, consultar_agenda. Os horários estavam certos; a O5 nem piscou.
DEPOIS — mesma pergunta, prompt versão 2
APROVADO — nenhuma regra disparou. Tools: consultar_agenda, ler_lead, ler_diagnostico. 3.304 ms.
A correção foi no prompt, não afrouxando a regra. A versão 2 do E1 passou a dizer, com o motivo junto:
Os dois horários vão na MESMA linha, com “ou” no meio: “quinta dia 20 às 18h ou sexta dia 21 às 8h”. Não quebre linha entre eles nem isole o “ou” — vira uma mensagem comprida à toa, e o limite de quatro blocos estoura justamente quando tu acerta o resto.
A escolha foi essa porque o limite de blocos não é capricho de estilo: mensagem comprida no WhatsApp é mensagem ruim. Quem lê é dono de paver no meio do serviço, com a mão suja. Afrouxar a O7 para acomodar uma quebra de linha desnecessária seria trocar uma regra que protege a leitura por um erro de digitação do modelo. O limite ficou; o modelo aprendeu a caber nele.
Os outros dois bloqueios da mesma família: uma resposta correta sobre a empresa do lead que saiu em seis blocos, e uma pergunta dupla no estilo E2-POS-REUNIAO (“É sobre o investimento, se funciona pro seu caso, ou outra coisa?” somava dois pontos de interrogação, e o limite é um).
“Quanto custa?”, “quanto custa esse teu trabalho?”, “quanto custa isso?”. Nos três casos o agente não escreveu nada e escalou pro Lucas, que é exatamente a instrução: preço não se responde por texto, porque qualquer resposta ali ou é um número (proibido) ou é enrolação. Zero preço vazou em 22 turnos.
Num quarto turno com “quanto custa?”, o agente escreveu literalmente escalar_para_lucas como se fosse a mensagem — e o verificador aprovou, porque nenhuma das 10 regras olha para nome de tool vazando no texto. Com o poller drenando a fila, o lead receberia a string escalar_para_lucas no WhatsApp do Lucas. Vale uma regra a mais antes de ligar o elo final.
Uma última observação a favor do desenho: num turno em que a consultar_agenda foi chamada duas vezes e não trouxe horário, o agente respondeu “estou com um problema técnico para acessar a agenda agora, me fala um horário que tu prefere” — em vez de inventar um slot. Falhou para o lado certo.
Duas ligações, e são as duas pontas do mesmo fio:
paver_outbox. Verificado no JSON do workflow: o poller não menciona a tabela em lugar nenhum. O que o agente aprova fica parado na fila.Enquanto isso, ele é uma sala fechada: entra por HTTP, sai por HTTP, e o WhatsApp nunca fica sabendo. Fechar esses dois elos é o passo mais perigoso do projeto inteiro, por três motivos:
AGENT_MAX_TURNOS_LEAD_DIA=12, AGENT_MAX_REPLIES_DIA=60, AGENT_MAX_PROATIVO_DIA=10) nunca foram exercidos com volume real. Foram definidos em 20/08, antes do primeiro turno: são ponto de partida conservador, não número medido.Ordem defensável para ligar: primeiro o autopilot=false num lead de teste (o agente prepara, um humano solta), depois DRY_RUN=true, e só então o número de verdade — com o Lucas olhando.
O freio de mão para tudo, régua e agente, na próxima passada do poller (até 1 minuto). Não desfaz o que já saiu.
N8N=~/.claude/skills/n8n-api/scripts/n8n.sh
$N8N PATCH data-tables/FefJT4aIzmUAPT4V/rows/update '{"filter":{"type":"and","filters":[{"columnName":"key","condition":"eq","value":"KILL_SWITCH"}]},"data":{"value":"on"}}'
Voltar ao normal: o mesmo comando com "value":"off".
Há níveis mais cirúrgicos, do menor para o maior: opt_out_at em paver_agent_state para um lead só; autopilot=false no mesmo lugar, se a ideia é o agente continuar pensando mas não soltar nada sem revisão; AGENT_ENABLED=off na config para parar só o agente, deixando a régua de pré-call e no-show rodando. O runbook tem os comandos prontos em cadencia-whatsapp/RUNBOOK-AGENTE.md, com o freio de mão no topo, antes de qualquer explicação — foi escrito para o momento em que o agente falar besteira e o Lucas estiver com o celular na mão, no meio de outra coisa.