No middlemen: farmers earn more, families get fresh food at lower cost, and citizens deliver to earn
Agent-powered · Direct farm marketplace · Live delivery tracking · Android, WhatsApp & Telegram
Download Android APK · Telegram bot · Quick start · Features · VPS deploy · Architecture · Mobile dev
Built with Agent Kernel · Framework docs
AgriPilot cuts out middlemen from farm-to-table: farmers sell direct and keep more margin, consumers get fresher produce at lower prices, and citizens can deliver locally and earn, all coordinated by multi-agent AI. Crop diagnosis, marketplace listings, plant health, and live rider tracking run on Android, WhatsApp, and Telegram. The backend is an Agent Kernel use-case using LangGraph multi-agent routing, a Postgres marketplace, and Redis-backed durable sessions.
AGENTS.md covers conventions for coding agents working in use-cases/agri-pilot/.
Release APKs are published as draft GitHub Releases by agripilot-mobile-release.
| Step | Action |
|---|---|
| 1 | Download the latest agripilot-*.apk from Releases |
| 2 | On your phone, allow install from your browser or files app |
| 3 | Open the APK. The app talks to the production API baked in at build time |
Maintainers: deploy the backend on a VPS (below), then run Actions → AgriPilot Mobile Release:
| Input | Example | Notes |
|---|---|---|
version |
1.0.1 or 1.0.1+12 |
Baked into the APK; does not modify pubspec.yaml |
api_base_url |
https://agripilot.knurdz.org |
Optional; defaults to production |
The workflow runs tests, builds the APK, generates grouped release notes from mobile commits, and opens a draft release tagged agripilot-mobile-v<version>. Edit the notes on GitHub, then publish when ready.
Mobile release tags use the
agripilot-mobile-v*prefix, separate from Agent Kernel PyPI publish tags (v*).
| Capability | Details |
|---|---|
| Crop diagnosis | Photo-based disease detection (HuggingFace ViT) with quality checks and confidence threshold |
| Treatment advice | Agricultural RAG over ChromaDB (data/chroma_db/) with chemical/dosage safety validation |
| Weather & irrigation | Open-Meteo forecasts: no API key required |
| Conversation memory | Redis-backed sessions, case history, follow-up resolution (“it’s getting worse”) |
| Thread history | Mobile chat threads via Agent Kernel thread routes |
| Safety backstops | Supervisor handoff-loop guard + knowledge-agent treatment validation |
The agent explains marketplace and delivery status but never creates orders, assigns riders, or mutates order state. Those actions are REST-only in the mobile app.
- Sell listings: crop, quantity, price, category, description, harvest date, product photo, analytics (views, connections, revenue)
- Plant tracking: tracked plants with photo timeline and derived insights
- Quick crop scan: one-time ViT analysis without creating a plant
- Import plant to listing: link tracked crop health to a sell listing for buyers
- Orders: confirm quantity, mark ready, live tracking map
- Channels: link WhatsApp or Telegram for advisor chat outside the app
- Browse & match: filter listings by crop, district, category, quantity, price; ranked match API
- Crop-health insights: observation counts and diagnosis timeline on listings linked to tracked plants (no raw photos or chemical advice)
- Connections: express interest; phone numbers revealed only after acceptance
- Checkout: pickup or rider delivery; live order tracking with map, ETA, and rider GPS
- Self-register with vehicle confirmation
- Go online + share GPS to see nearby delivery jobs (weight + distance)
- Accept jobs in the app; one active delivery at a time
- Live tracking: post GPS every few seconds; OSM map tiles (no Google Maps API key)
- PIN handoff: enter buyer PIN on the Deliveries tab at drop-off
Payment is cash/off-platform. Maps use OpenStreetMap in the app and optional OSRM road routing on the server.
| Screen | What it shows |
|---|---|
| Home | Farmer dashboard: tracked plants, sell listings, and one-tap AI advisor |
| Advisor | Chat with photo diagnosis and safety-validated treatment steps |
| Plant detail | Per-crop photo timeline, growth stage, and diagnosis history |
| Orders | Confirm quantity, dispatch riders, and track fulfillment status |
| Delivery | Live OSM map, route ETA, progress steps, and PIN handoff |
| Telegram | Production bot: contact-share account linking, then marketplace and crop queries in chat |
The production site at agripilot.knurdz.org uses these same screenshots on the landing page.
Click the diagram for full size.
·
Interactive diagram ↗
·
Archify source: use-cases/agri-pilot/docs/architecture/agripilot.architecture.json
Runtime flow
- Clients: Flutter Android (JWT), WhatsApp Cloud API, Telegram Bot API
- Edge: Caddy terminates HTTPS (Let’s Encrypt) on the VPS; only ports 80/443 are public
- App: Agent Kernel
RESTAPIserves marketplace REST, authenticated mobile chat, and channel webhooks - Agents: LangGraph supervisor routes to
vision,knowledge,resource, anddeliveryspecialists - Data: PostgreSQL (marketplace/orders), Redis (sessions, attachments, threads), ChromaDB (RAG), on-disk plant/listing media
Supervisor routing lives in use-cases/agri-pilot/agents/supervisor.py. Marketplace order/dispatch logic is deterministic REST in use-cases/agri-pilot/marketplace/order_service.py, dispatch_service.py, and tracking_service.py.
AgriPilot is an end-to-end use-case built on top of Agent Kernel, not a fork of the framework.
agentkernel[cli,langgraph,multimodal,chromadb,openai,api,whatsapp,telegram,redis,thread]>=0.8.1Defined in pyproject.toml. Production Docker reinstalls monorepo ak-py because PyPI 0.8.1 predates auth.authoriser and mobile thread routes: see deploy/Dockerfile.
| File | Role |
|---|---|
demo.py |
CLI: LangGraphModule([triage_agent]) for local testing |
app.py |
Production: REST + WhatsApp + Telegram + marketplace routers |
app.py wiring:
LangGraphModule([triage_agent])
RESTAPI.add(auth_router) # /api/auth, /api/farmer, /api/buyer, …
RESTAPI.add(farmer_router)
# … marketplace routers …
RESTAPI.run([
AuthenticatedMobileChatHandler(), # JWT + thread history
FastAckWhatsAppHandler(),
GatedTelegramHandler(),
])- Supervisor (
use-cases/agri-pilot/agents/supervisor.py):langgraph_supervisortriage with 20+ tools (marketplace, delivery, plan, profile) - Vision: ViT crop diagnosis from multimodal attachments
- Knowledge: ChromaDB RAG +
validate_treatmentsafety gate - Resource: Open-Meteo weather, irrigation, spray timing
- Delivery: read-only order/dispatch explanations
Tools are wrapped with @guarded (use-cases/agri-pilot/tools/tool_guard.py): per-session call limits and timeouts.
| Channel | Session key | Notes |
|---|---|---|
| Mobile | agri:user:{user_id} |
JWT via AuthenticatedMobileChatHandler + MarketplaceJwtAuthoriser |
Sender wa_id (E.164) |
Farmer + active subscription gate | |
| Telegram | chat_id |
Linked via contact-share to users.telegram_chat_id |
Config in config.yaml; override with AK_* env vars (AK_SESSION__TYPE=redis, etc.). Docker and VPS use Redis for sessions, multimodal attachments, and conversation threads.
- OpenAI moderation/jailbreak via
guardrails_input.json/guardrails_output.json - Code-level backstops:
agents/supervisor_guardrails.py,agents/knowledge_guardrails.py - WhatsApp/Telegram hard gates block non-farmer or inactive accounts before any LLM call
- Python
>=3.12,uv - One LLM key:
OPENAI_API_KEY,GEMINI_API_KEY, orOPENROUTER_API_KEY - For production channels: WhatsApp and/or Telegram credentials (see below)
- For marketplace JWT:
AK_MARKETPLACE__JWT_SECRET(≥32 chars in prod)
cd use-cases/agri-pilot
cp .env.local.example .env.local # fill at least one LLM key
./build.sh
docker compose up --build # Postgres + Redis + API on :8000
curl http://localhost:8000/health # {"status":"ok"}cd use-cases/agri-pilot
cp .env.local.example .env.local
./build.sh
uv run python app.py # needs Postgres for marketplace
# or CLI only:
python demo.pyOPENAI_API_KEY=sk-dummy uv run pytest -m "not slow"Run from use-cases/agri-pilot/: repo-root pytest collects the whole monorepo.
cd use-cases/agri-pilot/mobile
flutter pub get
flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8000 # emulator → host DockerSee use-cases/agri-pilot/mobile/README.md for Firebase push (optional) and physical-device LAN URLs.
Production stack: Caddy (automatic HTTPS) → app (Agent Kernel REST + agent runner) → Postgres 16 + Redis 7 on a private Docker network. Only Caddy is public.
- Ubuntu 22.04 or 24.04 VPS with SSH
- DNS
A/AAAAfor your domain pointing at the VPS before first deploy - Firewall: inbound 22, 80, 443 only
- Meta WhatsApp Cloud API + Telegram bot credentials (if using channels)
- One LLM provider API key
export REPO_URL=https://github.com/yaalalabs/agent-kernel.git
export BRANCH=main
export INSTALL_DIR=/opt/agent-kernel
curl -fsSL https://raw.githubusercontent.com/yaalalabs/agent-kernel/main/use-cases/agri-pilot/deploy/deploy-vps.sh \
| bash -s -- setup
# Edit deploy/.env.production on the server, then:
bash /opt/agent-kernel/use-cases/agri-pilot/deploy/deploy-vps.sh deployOr clone first:
git clone --branch main https://github.com/yaalalabs/agent-kernel.git /opt/agent-kernel
cd /opt/agent-kernel/use-cases/agri-pilot
cp deploy/.env.production.example deploy/.env.production
chmod 600 deploy/.env.production
# fill DOMAIN, LLM, WhatsApp, Telegram, and strong secrets
./deploy/deploy-vps.sh deployFirst ./deploy/deploy-vps.sh setup generates strong JWT/Postgres/WhatsApp-verify/Telegram-webhook secrets into deploy/.env.production (mode 600). The script never prints secret values and never overwrites an existing populated env file.
cd /opt/agent-kernel/use-cases/agri-pilot
git pull --ff-only origin main
./deploy/deploy-vps.sh updateupdate rebuilds the app image, runs alembic upgrade head, and probes https://<DOMAIN>/health. Named volumes (Postgres, Redis, Caddy certs, Chroma cache, plant-media, listing-media) are retained.
Without git on the server:
DEPLOY_SKIP_GIT=1 ./deploy/deploy-vps.sh updateAfter deploy, trigger Actions → AgriPilot Mobile Release with your version and API URL, or build locally:
cd use-cases/agri-pilot/mobile
flutter build apk --release --dart-define=API_BASE_URL=https://<DOMAIN>| Command | Purpose |
|---|---|
./deploy/deploy-vps.sh deploy |
Full deploy (build, migrate, start, verify) |
./deploy/deploy-vps.sh update |
Same as deploy |
./deploy/deploy-vps.sh status |
Container status + public /health probe |
./deploy/deploy-vps.sh logs [service] |
Follow logs (db, redis, app, caddy, …) |
./deploy/deploy-vps.sh restart |
Restart app + Caddy |
./deploy/deploy-vps.sh backup |
Timestamped Postgres dump under deploy/backups/ |
./deploy/deploy-vps.sh restore <file.sql.gz> |
Destructive DB restore (requires typing restore) |
./deploy/validate-deploy.sh |
Compose config validation |
./deploy/validate-deploy.sh --smoke |
Local build + migration + /health without Caddy |
After deploy, the script registers Telegram (https://<DOMAIN>/telegram/webhook). WhatsApp Meta console (manual):
- Callback URL:
https://<DOMAIN>/whatsapp/webhook - Verify token:
AK_WHATSAPP__VERIFY_TOKEN - Subscribe to
messages
| Path | Role |
|---|---|
deploy/docker-compose.vps.yml |
Production Compose stack |
deploy/Caddyfile |
Automatic HTTPS reverse proxy |
deploy/Dockerfile |
Hardened app image (non-root, baked knowledge ingest) |
deploy/.env.production.example |
Documented env template |
deploy/deploy-vps.sh |
Idempotent deploy + operations |
demo.py calls load_dotenv(".env.local") before importing agentkernel. Keep that order in new entrypoints. AK_ env vars override config.yaml with __ nesting.
Key knobs (see .env.local.example):
| Var | Purpose |
|---|---|
AK_MARKETPLACE__DATABASE_URL |
postgresql+psycopg://... (compose wires db service) |
AK_SESSION__TYPE / AK_SESSION__REDIS__URL |
Redis session store (compose sets redis) |
AK_MULTIMODAL__STORAGE_TYPE / AK_MULTIMODAL__REDIS__URL |
Redis attachment storage |
AK_THREAD__TYPE / AK_THREAD__REDIS__URL |
Conversation thread store (mobile history) |
AK_MARKETPLACE__JWT_SECRET |
HS256 secret (≥32 chars in prod) |
AK_WHATSAPP__* / AK_TELEGRAM__* |
Messaging channel credentials |
AGRIPILOT_TOOL_MAX_CALLS / AGRIPILOT_TOOL_TIMEOUT_SECONDS |
Tool guard limits |
AGRIPILOT_PLANT_MEDIA_ROOT / AGRIPILOT_LISTING_MEDIA_ROOT |
On-disk media roots |
- Roles:
farmer(sells, WhatsApp+REST),buyer(JWT-only browse/connect),rider(delivery),adminviascripts/seed_admin.py - Phone: E.164
^\+[1-9]\d{7,14}$ - JWT:
Authorization: Bearer <token>on protected routes - Subscription: Farmer
activerequired for/api/farmer/*; WhatsApp/Telegram gate matches
BASE=http://localhost:8000
# Farmer signup + login
curl -s $BASE/api/auth/signup -H 'Content-Type: application/json' \
-d '{"role":"farmer","phone_number":"+94770000001","password":"secret123","name":"Amal","district":"Kandy"}'
curl -s $BASE/api/auth/login -H 'Content-Type: application/json' \
-d '{"phone_number":"+94770000001","password":"secret123"}'
F_TOKEN=<jwt>
# Buyer signup + login
curl -s $BASE/api/auth/signup -H 'Content-Type: application/json' \
-d '{"role":"buyer","phone_number":"+94770000002","password":"secret123","name":"Nimal","district":"Colombo"}'
B_TOKEN=<jwt from login>
# Farmer listing
curl -s $BASE/api/farmer/listings -H "Authorization: Bearer $F_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"crop":"tomato","quantity_kg":500,"price_per_kg":120}'| Method | Path | Auth | Notes |
|---|---|---|---|
GET |
/health |
none | {"status":"ok"} |
GET |
/openapi.json, /docs, /redoc |
none | FastAPI docs |
GET |
/api/v1/agents |
none | list (triage) |
POST |
/api/v1/chat |
JWT (mobile) / none (dev) | {"prompt","session_id","agent":"triage"} |
POST |
/api/v1/chat-multipart |
JWT (mobile) | multipart photos (10 MB limit) |
GET |
/api/v1/threads* |
JWT | Mobile chat history |
GET |
/whatsapp/webhook?hub.* |
verify token | Meta challenge |
POST |
/whatsapp/webhook |
signature + farmer gate | fast-ack |
POST |
/telegram/webhook |
secret token + farmer gate | fast-ack |
| Method | Path | Auth |
|---|---|---|
PATCH |
/api/auth/me |
JWT |
GET |
/api/auth/me/channels |
JWT |
POST |
/api/auth/me/channels/telegram/link-token |
JWT farmer |
DELETE |
/api/auth/me/channels/telegram |
JWT farmer |
GET |
/api/config/public |
public |
POST/DELETE |
/api/devices/register, /unregister |
JWT |
GET/PATCH |
/api/devices/notification-preferences |
JWT |
POST |
/api/farmer/scans |
JWT farmer+active |
GET/POST |
/api/farmer/plants* |
JWT farmer+active |
POST |
/api/farmer/listings/{id}/import-plant |
JWT farmer+active |
GET |
/api/buyer/listings/{id}/insights |
JWT buyer |
POST |
/api/buyer/orders |
JWT buyer |
GET |
/api/buyer/orders/{id}/tracking |
JWT buyer |
GET |
/api/farmer/orders/{id}/tracking |
JWT farmer+active |
POST |
/api/farmer/orders/{id}/confirm, /ready |
JWT farmer+active |
GET/POST |
/api/rider/jobs, /api/rider/online, /api/rider/location |
JWT rider |
POST |
/api/rider/jobs/{order_id}/accept |
JWT rider |
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/auth/signup |
public | Register farmer/buyer/rider |
POST |
/api/auth/login |
public | JWT access token |
GET |
/api/auth/me |
JWT | Profile + subscription |
POST/GET/PATCH/DELETE |
/api/farmer/listings* |
farmer+active | CRUD + photo + analytics |
GET/PATCH |
/api/farmer/connections* |
farmer+active | Inbox + accept/decline |
GET |
/api/farmer/connections/{id}/contact |
farmer+active | Buyer phone after accepted |
GET |
/api/buyer/listings* |
buyer | Browse + filters |
GET |
/api/buyer/match |
buyer | Ranked match by crop/qty/district |
POST |
/api/buyer/listings/{id}/connect |
buyer | Connection request |
GET |
/api/buyer/connections/{id}/contact |
buyer | Farmer phone after accepted |
Errors: 401 invalid JWT, 403 role/subscription, 404 not found, 409 duplicate, 422 validation.
Bound to the supervisor via use-cases/agri-pilot/tools/marketplace_tools.py and use-cases/agri-pilot/tools/delivery_tools.py: all @guarded. Examples:
- Farmer: “I have 500kg tomatoes at 120/kg” →
create_listing_tool - Buyer: “Find 200kg tomato near Kandy” →
match_listings_tool - Rider: “What jobs are nearby?” →
nearby_delivery_jobs_tool
Orders are placed and riders accept jobs only in the mobile app, not via chat.
Under Docker the same session_id survives restarts; sessions, attachments, and case history live in Redis:
curl -s $BASE/api/v1/chat -H 'Content-Type: application/json' \
-d '{"prompt":"My tomato plants have early blight in Kandy.","session_id":"farmer-1"}'
docker compose restart app
curl -s $BASE/api/v1/chat -H 'Content-Type: application/json' \
-d '{"prompt":"It is getting worse. What should I do?","session_id":"farmer-1"}'
# -> references tomato / early blight without re-asking- Fill
AK_WHATSAPP__ACCESS_TOKEN,PHONE_NUMBER_ID,VERIFY_TOKENin.env.local - Start:
uv run python app.pyordocker compose up --build - Expose with ngrok for dev:
ngrok http 8000 - Meta console → callback
https://<host>/whatsapp/webhook, subscribemessages - Only
farmer+subscription_status=activeaccounts reach the agent
- Create bot via
@BotFather→AK_TELEGRAM__BOT_TOKEN+AK_TELEGRAM__WEBHOOK_SECRET - Register webhook:
https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://<host>/telegram/webhook&secret_token=<SECRET> - Unlinked chats get a contact-share keyboard; only active farmers link via phone match
- Weather: Open-Meteo: no API key
- After editing
data/knowledge_docs/:uv run python scripts/ingest_knowledge.py
Postgres-only runtime; schema via Alembic (migrations/). app.py runs migrations at startup. Tests use in-memory SQLite fixtures; no Docker required for pytest.
use-cases/agri-pilot/
├── agents/ # LangGraph supervisor + specialists
├── tools/ # Vision, RAG, weather, marketplace, delivery, guard
├── marketplace/ # Postgres models, routers, order/dispatch services
├── mobile/ # Flutter Android client
├── mobile_api/ # JWT-authenticated chat handler
├── deploy/ # VPS Docker stack + deploy-vps.sh
├── use-cases/agri-pilot/docs/architecture/ # Archify runtime diagram (JSON, HTML, PNG)
├── app.py # Production entry point
├── demo.py # CLI entry point
└── config.yaml # Agent Kernel config defaults
![]()
AgriPilot
Built with Agent Kernel · See LICENSE.




