SihaLink 🏥

Kenya National Disease Surveillance Swarm

🌐 Live Demo: kephothoagenticai.web.app 🤖 Telegram Bot: @SihaLinkBot

Inspiration

I have grown up watching people in rural Kenya get sick from diseases that are entirely preventable. Not because treatment did not exist, but because by the time anyone in authority knew about the outbreak, it had already spread to the next village.

With over 100,000 Community Health Volunteers in Kenya, ordinary people who walk doortodoor checking on their neighbours. They are the eyes and ears of the entire public health system. And yet in 2024, most of them were still filing paper reports, that doesn't get acted on. A CHW in Homa Bay spots three children with watery diarrhoea in the same week, she writes it down, the paper moves by hand to the subcounty health office. Maybe it gets into their supervisor's hands or entered into a spreadsheet, maybe it doesn't. And three weeks later, Nairobi finds out about a cholera cluster that has already crossed into the county, and the same in Siaya and Kisumu counties.

I kept thinking, what if the moment she noticed those three kids, something actually happened? Not a form submission, not a report that sits in a pile, but real action. A referral dispatched, a facility alerted, an outbreak flagged before it became a headline.

That's what pushed us to build SihaLink.

What it does

SihaLink is a multiagent AI swarm that gives every CHW a direct connection to an intelligent disease surveillance system — through their phone, in their language, in real time.

A health worker speaks a patient report into her phone in Dholuo. Within 30 seconds:

The Intake Agent transcribes and clinically extracts the report, or gets daa entry from web form or Telegra relay syndrome, triage colour, symptoms, vitals using Gemini 3.5 Flash. It handles 11 Kenyan languages including codeswitching between them. The Geo Agent takes the GPS coordinates and returns the full Kenyan administrative hierarchy (ward → subcounty → county) plus the three nearest health facilities with actual driving times from Google Maps. The Data Agent stores the encounter in MongoDB Atlas with a Voyage AI voyage3 vector embedding (1024 dims) so it can be semantically searched later. The Surveillance Agent immediately checks whether this case is part of a growing cluster — comparing against 4week rolling baselines across all 47 counties. It also runs a daily "silent pandemic scan" that catches diseases with a persistent upward trend before they ever hit a spike threshold. The Notify Agent fires an instant Telegram message to the receiving facility: "Incoming RED — 6yr, severe dehydration, ETA 22 min", with Accept/Redirect buttons. The Contact Tracing Agent begins mapping who else the patient may have exposed, calculates the syndromespecific exposure window, and assigns followup visits to nearby CHWs.

For RED and YELLOW triage cases, the system pauses and asks the CHW to confirm the referral before dispatching a human-in-the-loop gate that can be approved via the web dashboard or Telegram, with a WhatsApp share link included. RED cases autoescalate after 5 minutes if no one responds.

How we built it

The backbone is Google ADK orchestrating six Python agents, all running as a FastAPI application on Google Cloud Run. The same container manages both the Python backend (uvicorn) and the Node.js Telegram bot (grammY), coordinated by supervisord.

The clinical extraction uses Gemini 3.5 Flash on Vertex AI. We chose the Flash variant deliberately — it's fast enough that the CHV gets feedback while she's still with the patient. We also built a clarification gate: if extraction confidence is below threshold, the system asks a followup question in the detected language before proceeding.

For the data layer, MongoDB Atlas handles seven collections (encounters, CHWs, alerts, referrals, followups, protocols, contact traces). We integrated Voyage AI voyage3 for multilingual embeddings 1024 dimensions with a document/query input type distinction that makes semantic search meaningfully better than generic embeddings. We reembedded all existing records using a custom script after switching providers.

The Surveillance Agent runs on the swarm's internal event bus every stored encounter triggers an immediate local outbreak check. Scheduled cycles run every 6 hours for full county analysis and daily for silent pandemic detection. When an alert fires, the swarm automatically formulates a WHO/MoH response protocol using Gemini and stores it for CHW reference.

The Angular 21 frontend is deployed on Firebase Hosting with a proper SPA rewrite configuration. The backend streams live swarm events to the dashboard via ServerSent Events with exponential backoff reconnection.

For observability, every agent interaction is traced through Dynatrace via OpenTelemetry, giving us full visibility into latency, failures, and agent decision chains in production.

Challenges we ran into

Getting Gemini to reliably extract structured clinical JSON from multilingual audio was harder than expected. The model would sometimes return the right syndrome but wrong triage, or mix up symptom arrays with strings. We ended up building a disease reference database and a correction layer that validates extractions against known clinical presentations and fixes obvious misclassifications before they reach the database.

The MongoDB Atlas* startup race condition cost us days. The DataAgent.__init__ calls ping() to test connectivity. On Cloud Run, the container sometimes starts before the network is fully ready, the ping times out, and connected = False gets set permanently — meaning every encounter after that returns an empty response even though the database is perfectly reachable. We solved it with lazy reconnection: _check_db() retries the connection on every request if connected = False.

The Angular dev proxy was proxying /encounters (our SPA route) to the FastAPI backend, so page reloads returned raw JSON instead of the app. The fix was renaming the API path to /api/encounters with a proxy rewrite, while keeping the SPA route clean.

The human-in-the-loop gate had a subtle race condition. The startEncounter() poll loop was blocking launchPipeline() for up to 5 minutes while awaiting COMPLETE — which meant the gate card appeared correctly but the startEncounter Promise was still running. When the user clicked "Approve", the gate was working, but the subsequent .then() callback set gateSession = null and overwrote the result. We fixed it by making the pipeline fireandforget and letting the session subscription drive everything.

Accomplishments that we're proud of

The thing we are most proud of is not any individual feature, it is that the swarm actually works as an autonomous system. Submit a form intake, and within 30 seconds an encounter is stored in MongoDB, a followup schedule is created, an outbreak check runs, and if it's RED triage, a Telegram message goes out to the facility. We didn't hardcode that flow anywhere it emerges from the event bus.

Getting Voyage AI embeddings to actually improve search results was satisfying. Using the document/query input type distinction (not just the same model for both) made a measurable difference in semantic recall. Reembedding all 1,189 existing encounters with voyage3 took 295 seconds and zero errors.

Supporting more than 40 Kenyan languages in a clinical context — including codeswitching between Dholuo and English in the same sentence — and getting medically coherent extractions out was something we weren't sure was possible before we tried it.

The Dynatrace + OpenTelemetry integration traces every agent decision chain in production. Being able to see exactly which tool call added latency, or where an extraction failed, in a real distributed system running on Cloud Run, is the kind of observability you normally only see in much larger teams.

What we learned

The hardest part of multiagent systems isn't the agents, it is the coordination. Getting six agents to communicate cleanly without tight coupling required building a proper async event bus. Once we had that, adding new behaviour (like triggering contact tracing automatically when an encounter is stored) was trivial. Before that, it was spaghetti.

We learned that "human in the loop" has to be designed very carefully in healthcare. A gate that autoescalates after 60 seconds sounds reasonable until you're a CHW in a busy clinic trying to find your phone. We extended the timeout to 5 minutes and added WhatsApp sharing so the gate message can be forwarded to whoever needs to approve it. Small UX decisions have real operational consequences.

Voyage AI's input_type distinction between document and query embeddings is not a minor detail. It's the difference between semantic search that works and semantic search that feels slightly off. The MongoDB documentation on this is good — we just had to actually read it carefully.

What's next for SihaLink

The immediate priority is working with an actual county health department to run a pilot with real CHWs. The technology works — what we don't know yet is the operational reality: How do CHWs actually hold their phones when recording? What happens when the Gemini extraction gets a syndrome wrong and the CHW knows it? How do district officers want to receive alerts at 2am?

On the technical side:

Offlinefirst mobile app — the current web frontend works on mobile, but a dedicated PWA with service workers and proper offline sync would be far more reliable in areas with intermittent connectivity. Multilanguage Telegram bot responses — the bot currently replies in English. It should detect the CHW's registered language from their profile and respond in the same language they reported in. Atlas Search integration — beyond vector search, we want full hybrid search (semantic + keyword) so district officers can search clinical records the way they actually think: "cholera cases Kisumu last 30 days under 5 years old". MPESA integration for CHW incentives — Kenya's community health system is largely volunteerdriven. Automating small payments to CHWs for verified encounter submissions could meaningfully improve data quality and coverage.

The vision is simple: no CHW should have to choose between treating a patient and filing a report. SihaLink makes those the same action.

Built With

Share this project:

Updates