Operations
What the service reports about itself, the dashboards that read it, and the scheduled jobs that keep accounts moving.
The app exposes Prometheus metrics at /metrics. Alongside the standard HTTP request counters and latency histograms, it records five of its own:
| Metric | Labels | Records |
|---|---|---|
twiga_whatsapp_webhook_requests_total | event | Incoming webhooks by classification, including the ones dropped as outdated. |
twiga_llm_calls_total | provider, model, outcome | Model calls and whether they succeeded. |
twiga_llm_latency_seconds | provider, model | How long inference takes, bucketed from a quarter second to sixteen. |
twiga_ratelimit_hits_total, twiga_ratelimit_blocks_total | scope | Rate limit checks and blocks, per user and globally. |
twiga_messages_generated_total | feature | Replies sent, split by what produced them. |
GET /health returns OK and nothing else. It does not check the database, Redis or the model provider, so it is a liveness probe only. A healthy response does not mean the service can answer a message.
docker compose -f monitoring/docker-compose.monitoring.yml up| Service | Address | Notes |
|---|---|---|
| Prometheus | localhost:9090 | Scrapes the app every thirty seconds. |
| Grafana | localhost:4000 | Log in with admin and admin locally. |
Two dashboards are provisioned: FastAPI Overview for request rate, latency quantiles and error rate, and LLM Performance for call rate by outcome, inference latency and messages generated.
Two alert rules ship as well, on sustained 5xx rate and on slow requests. There is no Alertmanager in the compose file, so they evaluate but have nowhere to send anything.
The monitoring README is out of date in four places
monitoring/prometheus/prometheus.yml. That file does not exist. The configuration is generated at container start, so edits would be overwritten. To retarget, set TWIGA_LOCAL_TARGET, or set ENVIRONMENT=production along with TWIGA_INTERNAL_HOST.There is no central logging configuration and no log level variable. Every module uses a standard logger and output goes to stdout through uvicorn.
Startup tells you most of what you want to know:
Database initialized successfully
Redis initialized successfully (production and staging only)
Starting with mock whatsapp enabled (a warning, and a red flag in production)
Using LLM Provider: ...
Application startup completedThree standalone scripts, each runnable by hand and each writing to its own log file in /tmp. Nothing in the repository schedules them. The suggested timings below come from comments in the scripts themselves.
| Script | Suggested | What it does |
|---|---|---|
approve_users_cron.py | every 5 minutes | Sends the welcome template to newly approved users and activates them. |
mark_inactive_users_cron.py | hourly | Marks users inactive after a day of silence by default. |
send_reminder_messages_cron.py | daily at 09:00 | Sends a re-engagement template to users quiet for a week, with a week of cooldown. |
uv run python scripts/crons/approve_users_cron.pyEach exits with a non-zero status if any individual item failed, so a failure means at least one user was not processed, not that nothing happened.
Reminder settings are hard-coded
The seven day silence threshold, the seven day cooldown and the two message templates are constants in the script rather than configuration. The templates are English only, and both must be approved by Meta for your number or every send fails.