An SMS-driven platform for the gig economy
Workers text in an earnings goal (e.g. "I need $1200 by Thursday of next week."). Job posters text in a job listing with the relevant details (desc, pay, duration, expected done date). GPT classifies each inbound message and extracts structured data from both. A matching engine then selects the combination of gigs that reaches the worker's earnings goal in the least time and texts it back.
- FastAPI async webhook API with
/webhook/sms,/health,/readyz, and/metricsendpoints - Twilio inbound SMS ingestion with signature validation, idempotency, and per-sender rate limiting
- OpenAI GPT extraction pipeline for parsing unstructured SMS into job postings and work goals
- Pinecone vector index for semantic job/worker matching (async SDK)
- Temporal workflow orchestration with a cron-scheduled Pinecone sync worker
- PostgreSQL persistence via SQLAlchemy / SQLModel with Alembic migrations
- OpenTelemetry tracing (OTLP → Jaeger) and Prometheus metrics, with pre-built Grafana dashboards
- Structured logging via structlog with automatic OTel trace correlation
- Pulumi-on-GKE infrastructure-as-code under
infra/for production deployments
| Layer | Technology |
|---|---|
| Runtime | Python 3.12+ |
| Package manager | uv |
| Web framework | FastAPI + Uvicorn |
| ORM / migrations | SQLModel / SQLAlchemy (async) + Alembic |
| Database | PostgreSQL 16 (asyncpg driver) |
| Workflows | Temporal 1.26 |
| Vector search | Pinecone (async SDK) |
| LLM | OpenAI |
| SMS gateway | Twilio |
| Observability | OpenTelemetry, Jaeger 2.16, Prometheus 3.1, Grafana 11, Braintrust |
| Infra | Pulumi on Google Kubernetes Engine |
- Python 3.12 or newer
- uv (installs and manages the virtualenv and lockfile)
- Docker and Docker Compose (for the local full-stack environment)
- Credentials for: Twilio, OpenAI, Pinecone (see
docs/CONFIGURATION.mdfor the complete list)
Clone the repository and install dependencies with uv:
git clone <repository-url> vici
cd vici
uv syncThis creates a .venv/ and installs the runtime and dev dependencies pinned in uv.lock.
The fastest path to a working local stack is Docker Compose, which brings up PostgreSQL, Temporal, Jaeger, Prometheus, Grafana, and the FastAPI app together.
-
Create the required env files (
.env.app,.env.postgres,.env.temporal,.env.temporal-ui,.env.jaeger-query,.env.grafana,.env.opensearch). Seedocs/GETTING-STARTED.mdanddocs/CONFIGURATION.mdfor variables. -
Start the stack:
docker compose up --build
-
The app container automatically runs
alembic upgrade headbefore launching Uvicorn. Once healthy, endpoints are available at:URL Purpose http://localhost:8000/health Liveness probe http://localhost:8000/readyz Readiness probe (DB connectivity) http://localhost:8000/metrics Prometheus metrics http://localhost:8000/webhook/sms Twilio inbound SMS webhook http://localhost:8080 Temporal Web UI http://localhost:16686 Jaeger UI http://localhost:9090 Prometheus http://localhost:3000 Grafana -
Configure a Twilio webhook pointing at
${WEBHOOK_BASE_URL}/webhook/smsto start ingesting SMS.
To run the API outside Docker against an existing Postgres and Temporal:
uv run alembic upgrade head
uv run uvicorn src.main:app --reloadvici/
├── src/
│ ├── main.py # FastAPI app factory + lifespan (OTel, Temporal worker, gauges)
│ ├── config.py # Pydantic Settings (flat env vars remapped into sub-models)
│ ├── database.py # Async SQLAlchemy engine + session factory
│ ├── sms/ # Twilio webhook: gate chain, router, rate-limit, audit
│ ├── extraction/ # OpenAI GPT extraction + Pinecone sync-queue repository
│ ├── pipeline/ # Orchestrator (owns the unit of work) + handlers
│ │ # (job posting, work goal, unknown)
│ ├── jobs/ # Job posting domain (Job model, PayTerms, repository)
│ ├── work_goals/ # Work goal domain (models, repository)
│ ├── matches/ # Matching engine: knapsack job selection + SMS formatting
│ ├── users/ # User domain
│ ├── temporal/ # Temporal client, worker, cron schedules
│ ├── metrics.py # Prometheus gauges/counters
│ ├── money.py # Money convention: integer cents everywhere
│ └── models.py # Imports every table model so metadata is registered
├── migrations/ # Alembic revision scripts
├── tests/ # Pytest (async mode) suite
├── infra/ # Pulumi-on-GKE stack (Python)
├── docs/ # ARCHITECTURE, CONFIGURATION, DEPLOYMENT, DEVELOPMENT, GETTING-STARTED, TESTING
├── grafana/ # Dashboard + datasource provisioning
├── jaeger/ # Jaeger collector / query configs
├── prometheus/ # Prometheus scrape config
├── docker-compose.yml # Local dev stack
├── Dockerfile # Multi-stage production image (uv + python:3.12-slim)
├── alembic.ini
└── pyproject.toml
uv run pytestpytest-asyncio is configured in auto mode (see pyproject.toml), so async tests require no explicit marker. Coverage is available via pytest-cov.
See docs/TESTING.md for detailed testing guidance.
Ruff is the single source of truth for lint and format:
uv run ruff check --fix src tests
uv run ruff format src testsConfiguration lives in pyproject.toml under [tool.ruff] (target py312, rules E, F, I).
| Document | Description |
|---|---|
| docs/ARCHITECTURE.md | System architecture and component overview |
| docs/GETTING-STARTED.md | First-run walkthrough |
| docs/DEVELOPMENT.md | Local development workflow |
| docs/CONFIGURATION.md | Environment variables and settings |
| docs/TESTING.md | Test framework and conventions |
| docs/DEPLOYMENT.md | Deployment and infrastructure |
| AGENTS.md | Working agreement for AI-agent contributors |
| CONTRIBUTING.md | Contribution guidelines |
Contributions are welcome. See CONTRIBUTING.md for guidelines. AI agents working in this repo should also read AGENTS.md.
Vici is licensed under the GNU General Public License v3.0. See LICENSE for the full text.