The greeting that runs when everything is broken
The first thing I wrote was the message that plays when the rest of the system is dead. A caller dials a dental practice, the inbound webhook arrives, and before the handler touches a database, a cache, or any third party, it can already speak a clean, human-aware greeting. That fallback reaches for nothing: no row to read, no token to refresh, no upstream provider to wait on. It is engineered to work precisely when everything else has failed, because in a voice product there is exactly one outcome you cannot ship, and it is a caller hearing silence.
That constraint is the whole thesis. A dashboard can fail quietly and retry later. A background job can die and be replayed at leisure. A voice receptionist fails in front of a person who is listening, and the failure is the product. So the design principle, stamped through every layer, is blunt: fail gracefully and audibly, never silently.
Conversico is the company I co-founded: an AI voice receptionist for UK dental practices. Greeta is the voice agent that powers it. I designed the system and wrote all of its code; co-founders lead the rest. The features—missed-call recovery, booking support, an operator dashboard, privacy-conscious multi-tenancy, and launch-gated operations—are in production and going to market. What follows is the engineering, not the company.
Conversico is in production and going to market. The portfolio describes the engineering capability and launch discipline; private voice mechanics, practice-specific operating rules, sensitive workflows, commercial assumptions, and implementation details remain private.
A public demo video is planned. It will use synthetic or anonymised practice fixtures and will show the product experience without exposing real conversations, sensitive practice materials, or protected product mechanics.
A missed call is not a missed message
Start with what is actually being lost. A UK dental practice runs on thin reception capacity; the line is engaged, the practice is closed, the queue is full, and a new-patient call goes unanswered. That caller does not leave a voicemail and wait. They ring the next practice on the list. A single missed new-patient call can be four figures of lifetime value, the examination plus the treatment plan plus years of recall, walking straight to a competitor. And the worst property of the loss is that it is invisible: the call that never connected leaves no trace, so the practice cannot even count what it is bleeding.
That is the reframe the product turns on. The call is not a message waiting to be returned when someone has a spare minute; it is a customer who has already left. So the job was never "can an AI hold a conversation". It is whether a practice can recover that demand without buying a new category of operational risk in exchange.
A call is always in exactly one state
The way you keep a concurrent, event-driven, provider-dependent process from quietly corrupting itself is to refuse to let it occupy an undefined position. A call in Conversico lives in one of a small set of explicit states, and the only way to move between them is a single transition function that rejects illegal moves. A call cannot leap from "missed-call detected" to "completed". It cannot be marked answered without first having been initiated. The lifecycle is encoded as data and validated in one place, so "what is happening on this call right now" is never a guess assembled from scattered flags.
The state machine is what makes the rest legible. Underneath it sit three correctness primitives I applied everywhere rather than per-handler, because correctness you have to remember to apply is correctness you will eventually forget.
The first is multi-tenancy that fails closed. Many practices share the system, and one must never see another's data. The database itself enforces the boundary through row-level security, where each request binds a tenant and every query is constrained to matching rows. The deliberate part is the default: if no tenant is bound, the policies return nothing. A coding mistake therefore degrades to "no data", never to "all data". The failure mode of a bug is an empty screen, not a breach.
The second is encryption with searchable blind indexes. Sensitive fields are encrypted at rest, which creates the classic problem that you cannot run an equality query against ciphertext. So alongside the encrypted value the system stores a keyed hash of the normalised plaintext, a blind index, the precomputed fingerprint that lets you match a value without ever reading it. "Have we seen this caller before?" resolves by comparing hashes, fast, without decrypting a column or exposing plaintext to a query plan.
The third is a transactional outbox on every side effect, and here the discipline is mechanical rather than cultural. An intent to send an SMS, publish a task, or notify the live feed is written to the database in the same transaction as the business change; only after that commits is the message actually published, and a failed publish leaves a pending row for a recovery task to replay. That closes the "succeeded but vanished" gap where the database commits but the message to the queue is lost. And it is enforced, not encouraged: a CI check rejects any code that calls the SMS path directly from a business flow. You cannot bypass the outbox by accident, because the build will not let you.
Loading system pipeline…
The shape is deliberately legible. Every call passes through a caller-safe envelope; booking support only acts where it is allowed; anything ambiguous is routed to a human with a record left behind. The protected mechanics live inside the boxes; the envelope around them is the product.
The bug that only surfaces when you insist on getting it right
For a while, a prompt was being nested one level too deep in the voice provider's configuration object, and the provider silently dropped it. Nothing errored. The call still connected, the agent still spoke. It was simply running without the instruction it was supposed to carry, and the only signal was a behaviour that was subtly, defensibly wrong. That is the entire class of failure that defines this kind of system: not the crash, which announces itself, but the "almost right", which does not. It surfaces only because someone insists on getting the plumbing exactly right, then checks that it actually is.
The same instinct shows up in booking. Two callers can race for the same nine o'clock slot, and the obvious mitigations make a double-booking merely unlikely. A partial unique database index makes it physically impossible: the second write does not lose a race, it violates a constraint, and the database refuses it. The guarantee lives in the schema, where it cannot be forgotten by a future handler, rather than in application logic that has to remember to check.
What a self-audit surfaced
I ran a structured self-audit against a world-class production-launch bar rather than an MVP one: parallel domain reviews reading the actual code paths and scoring the system as if it were already carrying live traffic.
It confirmed the controls that matter: the idempotency primitives, the encryption-with-blind-index, the audit hash-chain. Every finding it raised became a tracked item with a file behind it and a check that turns green when it is closed. A system built to that bar knows exactly where its edges are and drives them to a passing state; that is the discipline the audit exists to enforce.
Which carries into how launch itself is treated: as discipline, not a date. Launch readiness is something machine-checkable. There are runbooks paired with scripts that check tenant isolation, confirm the audit table is immutable, and reject placeholder secrets before any sign-off is accepted. The posture is that "ready" is a thing you demonstrate with a passing check, not a thing you feel.
What the controls show
This is a pre-launch product, so what I can show publicly is the engineering, not market metrics. And the engineering controls hold on their own terms. Failing closed is a property of the row-level security policy whether or not anyone is using the product. The double-booking constraint either lives in the schema or it does not; the CI check that rejects a direct SMS call either fails the build or it does not. The system is built so those guarantees are demonstrated by checks, not asserted by vibes.
What stays private
I am not publishing the protected mechanics: the voice rules and agent configuration, the routing and callback logic, the integration internals with the practice-management system, the state-transition details, the operational gate thresholds, or anything that would let someone rebuild the company. Commercial planning, launch sequencing, and the detailed contents of the self-audit stay private too.
Fail audibly, never silently
Everything in Conversico comes back to one line: fail gracefully and audibly, never silently. A voice product has no quiet failure mode — it fails in front of a person who is listening — so the silent-failure path is the part that gets written first, the greeting that still plays when the database, the cache, and every provider are gone. The hardest bugs in a system like this are not the crashes, which announce themselves, but the prompt nested one level too deep: present, connected, and quietly doing nothing. That bug does not crash. It just costs the call, the way a missed phone call costs a practice a patient it will never know it lost. The discipline that catches it is the same discipline that lets a greeting still play when everything else is broken.
