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 AgentTask in 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):

  1. Google ADK — LlmAgent, Runner.run_async, FunctionTool, persistent sessions (DatabaseSessionService)
  2. GenAI SDK — direct Gemini function-calling fallback
  3. 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.

Node architecture

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_peer tenancy on each Shop
  • Async ADK inside sync Django — wiring run_adk_chat_async through DRF without nested asyncio.run() per request
  • Cloud Run + SQLite — fast for hackathon deploys, but data resets on redeploy; I added a one-shot seed-demo endpoint
  • 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 --force for video recording

Built With

  • agentic-ai
  • cloud-build
  • cloud-logging
  • cloud-scheduler
  • dart
  • django
  • django-rest-framework
  • docker
  • etims
  • flutter
  • function-calling
  • google-adk
  • google-cloud-run
  • google-genai-sdk
  • gunicorn
  • m-pesa
  • python
  • rest-api
  • retail-tech
  • sqlite
  • twilio
  • whatsapp
  • whitenoise
Share this project:

Updates

Submission history