Taly com voz no site — plano do funil /talk
Status: canônico · Escopo: Experiência do funil /talk em tactflow.io — o que existe hoje, o plano da Taly com voz e o que fica para depois · Atualizado em: 2026-08-11
Este documento registra o plano do funil do site, decidido em conversa em 11/08/2026. A §1 é levantamento verificado no código — o que a tela promete hoje e o que ela entrega. Da §3 em diante é plano: nada disso está construído, e a ordem da §6 é sugestão de sequência, não compromisso de data. Escopo continua sendo DIRECTION.md; se este doc contradiz aquele, aquele ganha.
Isto não reabre o “wedge web-booking” descartado em
DIRECTION.md§5. Web continua superfície secundária do produto. O que este plano trata é o funil de venda do site — como um lead chega até a call de setup —, não o canal que o cliente usa para atender o cliente dele.
1. O que o funil prometia e o que ele entregava
Seção intitulada “1. O que o funil prometia e o que ele entregava”Levantado em 11/08/2026, lendo o código e a config de produção. A coluna da direita é o estado antes da fase 0.
| Promessa na tela | O que acontecia de fato | Hoje |
|---|---|---|
| Hero “Talk to it” — único CTA da página | Abre um formulário de seis passos. Nada fala: não existe cliente Vapi Web no site, o VoiceDriver de funnel.ts só existe em comentário, e POST /talk/voice-session responde 503 no stub e 501 no live | Rótulo passou a ser “Run your numbers”, que é o que o funil faz. Volta a ser “Talk to it” com a #111 |
| “Book a call” | Não agenda. Grava o lead e manda e-mail com link para uma página externa. Em produção PUBLIC_BOOKING_URL está vazio, então o e-mail degrada para “reply with two times that work this week” — ping-pong manual com o fundador | Inalterado — é a fase 1 |
| “Not now — just email me the breakdown” | Manda por e-mail exatamente a conta que já está na tela, e não grava nada: nem o endereço, nem o consentimento | Fica de pé até a #113 dar o substituto: é a única saída de baixo compromisso que existe, e tirá-la antes da Taly deixaria o funil sem nenhuma |
| Caixa “occasional Tactflow updates. Unsubscribe in one click” | Não existe lista, não existe registro de consentimento e nenhum e-mail carrega link de descadastro | A promessa de descadastro saiu. O consentimento para updates fica: nasce desmarcado e só vira true por clique. A frase volta com a #113 |
GET /talk/availability e POST /talk/book | Ninguém no site chama. O stub devolve horários falsos e o live responde 501. Contrato órfão desde que o agendamento virou página externa | Removidos |
Das quatro coisas que o funil oferecia ao visitante, uma fazia o que diz — e era a menos útil: o e-mail que devolve o número que a pessoa está lendo naquele instante.
O detalhe da não-persistência está documentado no handler, em api/handlers/talk.ts, e o mapa de quem grava o quê está em talk-contract.md.
2. Isso não é uma lista de bugs, é uma classe
Seção intitulada “2. Isso não é uma lista de bugs, é uma classe”Os cinco itens acima têm a mesma forma: a interface promete uma capacidade que o backend não tem. É a mesma raiz das correções #41 (a tela confirmava e-mail que ninguém mandava), #95 (a tela parou de prometer e-mail) e #96 (o e-mail citava números para quem nunca viu número nenhum).
O defeito só aparece testando à mão, e às vezes só em produção: lint, typecheck, boundaries e docs:lint não sabem o que a tela está prometendo. Nenhum gate liga um CTA à capacidade de que ele depende, então promessa e entrega divergem sem nada ficar vermelho.
Consequência para o plano: um funil novo volta a divergir se essa lacuna continuar.
Metade da guarda já entrou: api-worker/src/journey/journey.ts declara a jornada como dado tipado e o journey.test.ts ao lado chama os handlers reais, reprovando a CI quando o mapa e o comportamento discordam. O que ele ainda não alcança é o marketing-web — nada liga a cópia da tela à capacidade que ela promete, e era exatamente ali que as cinco promessas da §1 moravam.
3. O diagnóstico: a Taly nunca ganhou voz
Seção intitulada “3. O diagnóstico: a Taly nunca ganhou voz”O funil não está com uma peça faltando por acidente de escopo. Ele foi desenhado em volta da Taly, e o que foi para o ar é o formulário dela sem ela:
- o passo
introé ela se apresentando (Hi — I'm Taly.), e não coleta nada de propósito; - a máquina de
funnel.tsexiste para “um funil só, dois drivers” — o clique do visitante e a voz da Taly chamando os mesmos métodos; POST /talk/voice-sessionjá está no contrato com a forma de uma sessão Vapi Web ({ assistant, publicKey });- o glossário já separa as duas figuras: Taly é o agente do site, que qualifica o visitante e vende a Tactflow, e é diferente do agente do telefone, que agenda serviço para o cliente do nosso cliente.
Então “Talk to it” no hero não é promessa exagerada — é a promessa certa, sem implementação atrás. O plano abaixo não é redesenho: é terminar o que o desenho já pedia.
E a calculadora, que hoje é o centro da página, não precisa provar nada sozinha. Ela é honesta (âncora conservadora de 15%, floor nos agendamentos, números que o lead confere de cabeça) e continua exatamente como está — muda quando ela aparece, não o que ela calcula.
4. O plano
Seção intitulada “4. O plano”A Taly atende por voz no site, responde dúvidas sobre a Tactflow dentro de limites, e agenda a call de setup na nossa agenda. Sem empresa fictícia, sem role-play: o visitante fala com a empresa que ele está avaliando, e a Tactflow atende o próprio telefone com o produto que vende.
O que ela faz:
- se apresenta e conduz — é ela a anfitriã do funil, não um widget solto num canto;
- responde o que o lead quer saber sobre como o produto funciona;
- pede nome, e-mail e telefone quando for agendar — sempre digitados na tela, nunca ditados, que é a regra que já vale no funil (o STT erra exatamente nos caracteres que precisam estar exatos);
- agenda a
setup-callde 30 minutos na agenda da Tactflow; - nunca fala preço. A config já declara isso e a regra 2 do
funnel.tstambém: preço só nas calls humanas.
Por que no browser e não por telefone:
- o contrato já foi escrito para isso —
{ assistant, publicKey }é sessão Vapi Web, e o comentário do handler já diz que o caminho de erro (microfone negado, WebRTC bloqueado por rede corporativa) tem de existir de qualquer forma; - a sessão do browser é a identidade, então acompanhar a conversa na tela e gravar o lead saem de graça — um telefone fora da aba exigiria casar a ligação por caller ID;
- no desktop, dialar exige trocar de aparelho, e o demo simplesmente não acontece.
O que sai de cena:
- o
POST /talk/breakdown-emailno formato atual. A contrapartida por um endereço deixa de ser a conta que já está na tela e passa a ser a call agendada. Ou ele vira captura de verdade — gravando contato e consentimento — ou sai; GET /talk/availabilityePOST /talk/book, órfãos desde que o agendamento virou página externa. Com a Taly agendando pelo toolset do agente, eles não voltam a ser necessários;- o
PUBLIC_BOOKING_URLcomo caminho principal. Ele passa a ser fallback de quem não quer ou não pode falar.
5. O que já está configurado e o que falta
Seção intitulada “5. O que já está configurado e o que falta”A maior parte da configuração existe. A Tactflow já é tenant dela mesma:
| Já existe | Onde |
|---|---|
Tenant, unit (Fort Worth, America/Chicago), contact capture, greeting | config/tenants/tactflow.json |
Offering setup-call, 30 min, sem preço nem depósito | config/verticals/tactflow.json |
O limite de escopo: “answers questions about the product; it does not quote pricing”, disclosureLevel: brand_assistant | config/verticals/tactflow.json |
| O objetivo da conversa, e a ordem de não repetir as quatro perguntas do funil | config/verticals/tactflow.json |
Tools de consultar disponibilidade e agendar (toolset booking), montagem do assistente, Google Calendar (freebusy + insert) | build-assistant-response.ts e o caminho descrito em voice-runtime-map.md |
O que falta:
POST /talk/voice-sessionde verdade — hoje501. Monta a config do assistente a partir do tenanttactflowe devolve{ assistant, publicKey }.- Vapi Web SDK no site, mais o caminho de erro: microfone negado, permissão revogada, WebRTC bloqueado. Todo passo continua concluível clicando — é regra da máquina do funil, e agora ela deixa de ser hipótese.
- Conexão de calendário do tenant
tactflowativa, para asetup-callcair numa agenda real em vez de virar registro solto. - Base de conhecimento da Taly com limite explícito: nada de preço, nada de promessa de capacidade que o produto não tem. É a falha mais cara possível numa conversa de venda, e o repo já tem o hábito de tratar isso como regra e não como esperança.
- Teto de custo e de abuso.
publicKeyé chave pública de verdade, usável de qualquer origem. O Turnstile já está no/talk/voice-session; falta teto de minutos por sessão, por IP e por dia. Web call custa menos que ligação (sem perna de telefonia), não custa zero. - Um momento de e-mail só, e ele confirma a call agendada — com contato e consentimento gravados, como o
/talk/contactjá faz.
6. Ordem sugerida
Seção intitulada “6. Ordem sugerida”Cada fase entrega valor sozinha e nenhuma deixa promessa quebrada na tela.
| Fase | Issue | O que entra | Por que nessa ordem |
|---|---|---|---|
| 0 ✅ | #109 | Limpeza de honestidade: endpoints órfãos removidos, CTA do hero honesto, promessa de descadastro fora | Não dependia de decidir nada do resto, e parava o sangramento de credibilidade |
| 1 | #110 | POST /talk/voice-session live + calendário do tenant tactflow | É o backend inteiro do plano; sem ele o site não tem o que chamar |
| 2 | #111 | Vapi Web no site, com o caminho de erro e o fallback de clique | Primeira vez que “Talk to it” cumpre o que diz |
| 3 | #112 | Base de conhecimento da Taly, com o limite de preço | Ela pode agendar antes de saber responder tudo; o inverso não é verdade |
| 4 | #113 | E-mail único de confirmação, com contato e consentimento gravados | Depende de existir call agendada para confirmar |
| 5 | #114 | Ligar a cópia da tela ao estado declarado na jornada — o mapa de journey.ts já prova rota e e-mail | Pode entrar em qualquer ponto; quanto mais cedo, menos retrabalho nas fases seguintes |
7. Fica para depois
Seção intitulada “7. Fica para depois”Três coisas foram levantadas, fazem sentido, e não entram agora — com o motivo registrado para não voltarem como novidade:
- Ouvir o agente do telefone. A ideia era a Taly oferecer o número da Arlington HVAC no meio da conversa, para o lead ligar e ouvir o que o cliente dele ouve. Não dá hoje: ela teria que soletrar o número por voz, e não existe nada na interface que mostre esse número na tela. É o espelho da regra que já vale para entrada — telefone e e-mail são sempre digitados porque o STT erra o caractere que precisa estar exato; na saída, o TTS tem o mesmo problema com dígito. Precisa de superfície visual antes, então fica como plano futuro.
- Outbound “me ligue”. Não existe capacidade de outbound no repo — só o webhook de entrada. E um botão que liga para um número digitado na internet aberta é superfície de fraude de pedágio e de assédio, porque qualquer um digita o número de um terceiro.
- Toolset
lead. Para a Taly conduzir as quatro perguntas falando.resolveVapiToolssó implementabookinge avisa alto ao cair de volta, então isso é opcional: ela vende e agenda sem conduzir a calculadora.
8. O que este documento não decide
Seção intitulada “8. O que este documento não decide”- Quanto custa deixar a Taly atendendo, e qual é o teto por mês. É conversa de
pricing.md, não de código. - Se o setor continua sendo perguntado no funil. O mercado de entrada é HVAC em DFW (
market-entry-dfw-hvac.md), mas o funil aceita clínica, estúdio e serviços profissionais, e a máquina usa o setor para escolher a unidade de trabalho na manchete. A decisão de quem atende e por qual transporte, com as alternativas recusadas e o motivo de cada uma, está registrada emADR 0004.