A clinic group runs lead follow-up with an AI agent that books appointments inside its rules.
Enquiries arrive from Google and Meta ads through forms and WhatsApp. Before, they were assigned from a spreadsheet the next morning and many never got a first call. Now an agent replies with open slots, confirms the booking and hands the lead to a consultant. It works inside rules the group set once, it can only act through a short list of allowed actions, and every booking has a record.
The situation
- Leads landed in a form, a WhatsApp number and two ad platforms. Nobody owned the join.
- Assignment happened once a day, by hand. Replies after 6 PM waited until morning.
- A CRM meant a heavy tool nobody updated. The phone number was the real system of record.
- An agent could already read WhatsApp and draft a reply. Nobody could say what it was allowed to promise, or to whom.
Before the agent could act, it had to know
The agent could reason and use tools from day one. It became reliable once this list was written down: which information to trust, what the terms mean, which rules apply, and what it is allowed to do.
- The lead's timeline, from the ad click to the last message
- Open slots at the nearest clinic
- Who is on shift, and the group's working hours
- The lead's confirmation, in their words
- That the slot is still open
- Which consultant to notify, and when to remind
- It can only send what the plan drafted, through an allowed action. It never composes freely.
- Nothing. It can't. It stages the change for a person to approve.
What they already had, and what we built on it
Where a system already gave the agent what it needed, we used it as it was. We built only what was missing.
The system, layer by layer
How a booking happens
- EventA lead replies on WhatsApp with a time preference. The agent wakes on the event, not on a schedule.
- ContextIt reads the lead’s timeline, the nearest clinic’s open slots, who is on shift and the working-hours rule. All from the context layer, nothing re-derived.
- PlanIt drafts a reply with the next open slots at the nearest clinic. The action is low risk and reversible, so it runs on its own.
- ConfirmOn a confirmation it creates the appointment, notifies the consultant and schedules a reminder.
- No replyAfter a set wait it queues a follow-up for the owner. Nothing is dropped.
- RecordThe run record holds the message, the slots offered, the rule that allowed it and the calendar read-back.
The automation, in outline
{
"automation": "appointment-booking",
"trigger": { "event": "lead.replied", "channel": "whatsapp" },
"condition": { … },
"context": ["lead.timeline", "clinic.capacity", "owner.on_shift", …],
"plan": ["calendar.slot.propose", "appointment.create", "notify.owner", "reminder.schedule"],
"policy": { "risk": "low", "requires_approval": ["appointment.reschedule"] }
}Each stateful automation is a typed contract: a trigger, a condition, the context it may read, the registered actions in its plan, and its policy. The model fills fields. Our code validates, resolves and binds. A person approves what the policy says needs approval. The full contract carries more than we show here.
Who can do what
- Consultants see their own queue. Managers see every queue. Patient details are visible by role, not by default.
- Messages to a lead are drafted from the plan and sent through a registered action, never composed freely by the model.
- A confirmed appointment is a protected entity: changing it stages a plan for a person to approve.
- The agent has its own identity with those rights and no more. The group can narrow or revoke them without touching anything else.
Bring us one workflow. We’ll start there.
We connect the systems you already run, write down what the agent must know before it acts, and start with recommendations before actions. The next workflow reuses what the first one built.