Inspiration
Small shop owners in Kenya — dukas, kiosks, mini-marts — run everything from a phone: ringing up sales, tracking Kitabu (credit) customers, chasing payments, watching stock, and staying compliant with KRA/eTIMS. Most of that work happens after the sale: reminders, follow-ups, and “who owes what?”
I built Node because that follow-up work shouldn’t depend on the owner being free, online, or at the till. The agent should run in the background — on WhatsApp where customers already are — and surface what matters in a simple Agent tab when the owner opens the app.
What it does
Node combines an offline-first Flutter POS with a Google ADK + Gemini backend:
- Autonomous credit collections — after a Kitabu credit sale, the agent sends WhatsApp reminders, handles M-Pesa STK push, payment deferrals, and owner alerts — without opening the app
- Owner command center — proactive alerts (low stock, eTIMS, Kitabu), weekly digest, activity feed, and actionable suggestions
- Gemini copilot — ask “shop summary”, “who owes the most?”, “what’s low on stock?” with function tools over live shop data
- Full audit trail — every autonomous action lands as an
AgentTaskin Django admin and the mobile activity feed
How I built it
Mobile: Flutter POS with local SQLite (Drift), catalog sync, Scan/Kitabu/Agent tabs.
Backend: Django REST API on Google Cloud Run, deployed via Cloud Build. Cloud Scheduler hits /api/v1/agent/autonomous-tick/ every 30 minutes for autonomous reminders.
Agent stack (owner chat):
- Google ADK —
LlmAgent,Runner.run_async,FunctionTool, persistent sessions (DatabaseSessionService) - GenAI SDK — direct Gemini function-calling fallback
- Local router — regex/intent fallback when Gemini is unavailable
Kitabu WhatsApp (hybrid ADK): I deliberately split intent from execution:
- ADK parses natural language and structured intents (including multi-turn date confirmations via ADK sessions)
- A deterministic state machine handles money: STK lifecycle, M-Pesa callbacks, menu digits, owner notifications
ADK never moves money — it submits structured intents; deterministic code executes side effects.
Integrations: Twilio WhatsApp, M-Pesa Daraja STK, eTIMS fiscal queue.
Observability: Structured logs (adk.chat.done, adk.tool.start) in Cloud Logging; AgentTask rows for hackathon proof.

What I learned
- Hybrid beats full-LLM for payments — STK/M-Pesa should stay deterministic; ADK is best at understanding, not moving money
- ADK sessions matter for WhatsApp — payment deferrals need multi-turn state (
awaiting_date_confirm); ADK session sync kept context across turns without custom glue code - Autonomous ≠ chatbot — the strongest demo moment is the owner not opening the app while WhatsApp + scheduler run collections
- Offline-first POS + cloud agent — local Drift cache keeps the till working; sync pushes sales that trigger backend agent workflows
Challenges I faced
- WhatsApp routing in Twilio sandbox — separating customer (+254…) from owner (KE peer) required careful
whatsapp_peertenancy on eachShop - Async ADK inside sync Django — wiring
run_adk_chat_asyncthrough DRF without nestedasyncio.run()per request - Cloud Run + SQLite — fast for hackathon deploys, but data resets on redeploy; I added a one-shot
seed-demoendpoint - Production admin on Cloud Run — CSRF trusted origins, WhiteNoise for static files, and HTTPS proxy headers for Django admin login
- Quiet hours vs demo timing — Kitabu message queue respects 8pm–8am Nairobi time; demo commands bypass with
--forcefor video recording
Log in or sign up for Devpost to join the conversation.