Skip to content

lubobali/Sentinel-AI

Repository files navigation

Sentinel-AI

Production-grade AI inbox triage agent. Hardened with prompt-injection defenses, sender allowlisting, idempotent processing, and fail-closed secret handling. FastAPI · Gmail · LLM · Postgres.

Tests Coverage Mypy Python License: MIT

Sentinel-AI architecture


What it does

Sentinel-AI watches a Gmail inbox, classifies each unread email into Meeting / Action / FYI (or Needs-Review when uncertain), applies a Gmail label, and creates a tentative calendar event for meeting requests. It refuses to send mail or delete anything. The classifier uses an LLM with automatic provider failover, falling back to a deterministic keyword classifier when both providers are down.

Why this exists

Most AI agent tutorials skip the safety patterns that matter when the agent runs against your real inbox in production. Sentinel-AI demonstrates four safety patterns most production agents need:

  1. Sender allowlist enforced at fetch time — emails from anyone not on the explicit allowlist never reach the classifier or any outbound action. The trust boundary lives at the entrance, not somewhere deep in the pipeline.

  2. Prompt-injection defense — content matching known injection patterns is detected, logged, labeled Sentinel-Blocked, and dropped before classification. Defense in depth even after the allowlist.

  3. Idempotent processing — every processed message id is persisted to Postgres. Restarts, retries, double polls — none of them double-process or double-label an email.

  4. Fail-closed defaults — when the LLM returns malformed JSON, when both providers fail, when a required env var is missing — the system refuses to act rather than acting on bad data. Untrusted output gets Sentinel-Needs-Review for human review.

Fallback chain — primary → fallback → keyword last-resort

Plus: PII hashing in logs, no send/delete capability anywhere, non-root containers with read-only root filesystems, structured JSON logs with trace IDs, Prometheus metrics, rate-limited API with key rotation support, and a runbook.

Architecture

                  ┌────────────┐
        cron ───→ │ FastAPI    │ ── /healthz /readyz /metrics (no auth)
                  │            │ ── /heartbeat /digest /urgent /status (auth)
                  └─────┬──────┘
                        │ heartbeat()
                        ▼
                  ┌────────────┐
                  │ Sentinel   │
                  │ Agent      │
                  └─────┬──────┘
                        │
        ┌───────────────┼───────────────┬──────────────────┐
        ▼               ▼               ▼                  ▼
   Gmail (read       Allowlist     Injection        LLM Classifier
   + label only)    (sender filter)  Guard          ┌─────────────┐
                                                    │ Provider 1  │
                                                    └─────┬───────┘
                                                          │ on fail
                                                          ▼
                                                    ┌─────────────┐
                                                    │ Provider 2  │
                                                    └─────┬───────┘
                                                          │ on fail
                                                          ▼
                                                    Keyword fallback
                                                    (always returns
                                                     needs-review)
                        │
                        ▼
                  ┌────────────┐
                  │ Postgres   │ processed_emails (idempotency)
                  └────────────┘                  audit_log

Full diagrams (mermaid) in docs/ARCHITECTURE.md. Operational procedures in docs/RUNBOOK.md.

Quickstart

Prerequisites

  • Python 3.13
  • Postgres 16+ (or docker run postgres:16-alpine)
  • A Gmail account + Google Cloud OAuth client (Desktop app type)
  • API key for at least one LLM provider (OpenAI-compatible)

Setup

git clone https://github.com/lubobali/Sentinel-AI.git
cd Sentinel-AI
python3.13 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env  # fill in your values

OAuth (one time)

Place your Google OAuth client_secret JSON at secrets/credentials.json.

python scripts/smoke_gmail.py  # opens browser, saves secrets/token.json

Run locally

uvicorn sentinel.api.main_runner:app --port 8097
# Or with Docker:
docker compose -f docker-compose.prod.yml up -d

Verify

curl http://localhost:8097/healthz
curl -H "X-API-Key: $SENTINEL_API_KEY_PRIMARY" http://localhost:8097/status

Live /heartbeat against the deployed Hetzner instance:

Heartbeat response showing zero processed and zero injections blocked

Configuration

All config via environment variables (or .env). See .env.example for the full list. Highlights:

Variable Purpose
ALLOWED_SENDERS Comma-separated allowlist (mandatory)
URGENT_SENDERS Subset that gets Sentinel-Urgent label
RECIPIENT Your own address (ignored from triage)
PRIMARY_LLM_* Primary LLM provider config
NVIDIA_* Fallback LLM provider config
SENTINEL_API_KEY_PRIMARY Required, X-API-Key for app endpoints
SENTINEL_API_KEY_SECONDARY Optional, for zero-downtime rotation
DRY_RUN When true, classify but skip outbound actions

Testing

pytest                  # all tests
pytest -m fast          # fast unit tests only
pytest --cov=sentinel   # with coverage
  • 181 tests, currently passing
  • 93% line coverage on business logic
  • mypy --strict clean
  • ruff check clean
  • bandit -ll clean (no medium+ security issues)
  • pip-audit clean (no known CVEs)

Coverage report — 93.0% overall

Code quality

ruff format .            # format
ruff check .             # lint
mypy --strict sentinel/  # type-check
pre-commit install       # gate every commit

CI runs all of the above on every push.

Deployment

Multi-stage Docker (python:3.13-slim runtime, non-root user, read-only root filesystem). Postgres in a separate container. Service binds to 127.0.0.1 only — expose via reverse proxy or Tailscale, not directly to the internet.

App and Postgres containers running healthy on Hetzner

See docs/RUNBOOK.md for deploy + rollback + restore drill procedures.

License

MIT. See LICENSE.

About

Production-grade AI inbox triage agent. Hardened with prompt-injection ▎ defenses, sender allowlisting, idempotent processing, and fail-closed secret ▎ handling. FastAPI · Gmail · LLM · Postgres.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors