ReCognition: A Due-Diligence Analyst On Call and On Text
Inspiration
Every VC associate we talked to had the same complaint: portfolio check-ins are death by a thousand small asks. "What's Acme's burn multiple this month?" "Can you grab 20 minutes with Nimbus's CFO?" Each one means opening a spreadsheet, cross-referencing a data room, or playing calendar tag over email. None of it is hard — it's just constant, and it interrupts real work.
We wanted something a partner could just talk to — over the exact channels they already live in, text and phone — and get a verified number back in seconds, or a booked meeting without the back-and-forth. Not a chatbot that guesses. An analyst that only speaks from the data room, cites the number, flags the one thing worth flagging, and gets out of the way.
What it does
ReCognition answers over SMS (SendBlue) and phone calls (Vapi + Twilio), backed by one orchestrator so both channels share the same memory and the same facts:
- Financial diligence: burn multiple, Rule of 40, LTV:CAC, runway, customer concentration — pulled live from Supabase, never estimated. If a number isn't in the data room, ReCognition says so instead of inventing it.
- Calendar booking: checks real open slots for a company's CFO or a customer reference, presents 2–3 options, books atomically (no double-booking races), and fires a real cal.com confirmation email.
- Same-day memory: a call or text picks up where you left off today — but conversation state resets each calendar day, so yesterday's thread never bleeds into a fresh morning call.
Formally, ReCognition's job is to answer a query $q$ against a company's metric set $M = {m_1, \dots, m_n}$ such that every cited value $v$ satisfies $v \in M$ — never $v = f(M)$ for some invented $f$. That constraint is enforced at the prompt level (numbers must come verbatim from a tool result) and doubles as the entire trust model of the product.
How we built it
The architecture is deliberately split into frozen, independently-testable contracts:
- Section 4 — data layer: a Supabase schema with
companies,calendar_slots,conversations, andmessages, plus an atomicbook_slot()Postgres RPC so two channels can't win the same slot. - Section 5 — the envelope contract:
{ channel, from_number, text, external_id, timestamp }. Both channel adapters (SendBlue text, Vapi voice) normalize into this exact shape before hitting/orchestrate— the orchestrator never knows or cares which channel it's talking to. - The orchestrator loop: a tool-calling LLM loop (Groq/Llama, OpenAI-compatible) with two tools —
financial_agentandcalendar_agent— each its own Hono route, each backed by Zod-validated request/response schemas so a malformed tool call fails loud instead of silently. - The channel adapter: an Express server translating SendBlue's webhook shape and Vapi's OpenAI-style custom-LLM protocol into the shared envelope, and relaying replies back out.
Idempotency was a first-class concern from the start — webhook retries (SendBlue and Twilio both retry aggressively) get replayed from a stored reply keyed by external_id, never re-run through the LLM.
Challenges we ran into
The two hardest bugs were both protocol mismatches invisible from the docs:
- Vapi's "silent hang." Calls connected, the assistant said its first line, then went dead after the caller spoke. HTTP 200 the whole time. Turned out Vapi's OpenAI-compatible client always sends
stream: trueand expects Server-Sent-Eventchat.completion.chunkframes — a perfectly valid single JSON blob just gets silently discarded because it's not a stream. We traced it by pulling Vapi's raw call logs and diffing the actual request path (/voice/chat/completions, not/voice— the client appends the OpenAI convention path itself) against what the server exposed. - Rerouting without collateral damage. Midway through testing we discovered the Twilio number we'd bound to the assistant already belonged to a different, live project. Rather than repoint it and risk breaking someone else's demo, we bought a fresh Twilio number, imported it into Vapi, and left the original untouched — a good reminder that "just reroute it" is rarely actually "just."
What we learned
Voice-AI platforms borrow OpenAI's wire format so heavily that "OpenAI-compatible" quietly means "behaves exactly like the OpenAI SDK, including implicit streaming and implicit path conventions," not just "same JSON shape." The fastest way to debug that gap wasn't the docs — it was pulling the actual call logs and looking at bytes on the wire.
Built With
- cal.com
- cloudflare-workers
- express.js
- groq
- hono
- javascript
- llama-3.3
- ngrok
- node.js
- postgresql
- rest-apis
- sendblue
- supabase
- tsx
- twilio
- typescript
- vapi
- vitest
- websockets
- wrangler
- zod
Log in or sign up for Devpost to join the conversation.