Service Roles
In production, Vega runs as seven distinct service roles. Each role is a separate Docker container with its own entry point, permissions, and scaling behavior. This separation means a failing scan doesn't crash the API, ingest failures don't block scans, and sensitive credentials are isolated to specific containers.
vega-api
Entry point: uvicorn app.main:app (→ entrypoints.api.create_app())
Dockerfile: docker/api/Dockerfile
The API is the only role that handles user-facing HTTP traffic. It owns:
- Authentication (login, token refresh, Cognito JWT validation)
- Project and repository management
- Scan creation, cancellation, pause, resume, and retry
- Reading findings, events, and artifacts
- Billing management (Stripe, promotions, spend limits)
- GitHub App integration
- Health and operations endpoints
The API never runs scans or ingest directly in production. When a scan is created with scan_execution_mode=sqs, the API writes the scan record to Postgres and sends a message to SQS. When a repository is added, it enqueues an ingest job. The API then returns immediately.
On startup, the API calls build_api_runtime() in composition/wiring/ which builds a RuntimeContainer with all ports wired to their production adapter implementations.
Run locally:
uvicorn app.main:app --reload --reload-dir app
vega-scan-worker
Entry point: python scripts/run-scan-worker.py
Dockerfile: docker/worker/Dockerfile
The scan worker is a long-running process that loops continuously:
- Sends a heartbeat to keep its registration alive
- Runs
ReconcileScansUseCaseto detect and recover stale running scans - Runs
RunQueuedScanUseCaseto claim the next queued scan and either execute it locally (scan_worker_execution_mode=local) or launch avega-scan-runnerECS task (scan_worker_execution_mode=ecs)
In ECS mode, the worker calls AWS ECS RunTask to start a vega-scan-runner container for each scan phase. The scan ID and phase are passed as environment variables.
Run locally (for production-like behavior without ECS):
VEGA_SCAN_EXECUTION_MODE=external python scripts/run-scan-worker.py
vega-scan-runner
Entry point: python scripts/run-scan-runner.py
Dockerfile: docker/vega-core-runner/Dockerfile
The scan runner executes exactly one scan phase and exits. It is launched by the scan worker via ECS RunTask. The runner:
- Builds a
RuntimeContainerfor the runner role viabuild_scan_runner_runtime() - Calls
ExecuteScanUseCasewith the assigned scan ID and phase - Loads the source snapshot (from S3 or locally)
- Calls
VegaCoreEngineAdapterwhich invokes the vega-core library - Writes
DomainEvents andFindingRecords to Postgres - Uploads
ArtifactRecords to S3 - Updates the
ScanRecordstate and exits
The runner image includes Node.js and the codex npm package because vega-core invokes Codex as a subprocess.
Run locally to re-execute a specific scan phase:
python scripts/run-scan-runner.py <scan-id> --phase scan
python scripts/run-scan-runner.py <scan-id> --phase plan
python scripts/run-scan-runner.py <scan-id> --phase audit
vega-repo-ingest-worker
Entry point: python scripts/run-repo-ingest-worker.py
Dockerfile: docker/repo-ingest-worker/Dockerfile
The ingest worker polls the ingest SQS queue for repository ingest jobs. It:
- Sends a heartbeat
- Runs
ReconcileRepositoryIngestUseCaseto recover stale ingest jobs - Runs
RunQueuedRepositoryIngestUseCaseto claim an ingest job and either execute it locally or launch avega-repo-ingest-runnerECS task
This is a separate worker from the scan worker so that repository ingest operations (which can involve large git clone operations) don't block scan execution capacity.
vega-repo-ingest-runner
Entry point: python scripts/run-repo-ingest-runner.py
Dockerfile: docker/repo-ingest-runner/Dockerfile
The ingest runner executes exactly one repository ingest job and exits. It:
- Claims the ingest job in Postgres
- Calls
ExecuteRepositoryIngestUseCase - Clones the git repository or extracts the uploaded zip archive
- Stores the materialized source snapshot in S3 (or locally)
- Creates a
SourceSnapshotrecord - Transitions the repository state to
snapshotted→ready - Exits
The ingest runner image includes Git and the necessary tools to handle git operations and archive extraction safely.
vega-llm-proxy
Entry point: uvicorn app.llm_proxy.main:app (→ entrypoints.services.llm_proxy)
Dockerfile: docker/llm-proxy/Dockerfile
The LLM proxy is a small FastAPI service that sits between scan runners and the AI provider. Its purpose is credential isolation: runners receive a short-lived, scan-scoped token, and the proxy holds the real provider API key.
The proxy:
- Validates the scan-scoped token on every request
- Forwards the request to the configured AI provider
- Tracks token and cost usage per scan via
Sub2APILLMUsageAdapter - Rejects requests that exceed per-scan usage limits
- Passes all
/{path}requests through unchanged to maintain OpenAI API compatibility
Run locally (only needed if you want proxy-mediated AI calls):
uvicorn app.llm_proxy.main:app --port 8001
vega-maintenance
Entry point: python scripts/run-maintenance.py
Dockerfile: docker/maintenance/Dockerfile
The maintenance role is not a long-running service — it is a one-off ECS task you run on demand. It is used for:
- Database migrations:
scripts/run-db-migrations.py - Stale scan reconciliation: detect scans that have been
runningtoo long and mark themfailed - Cleanup jobs: removing expired planning artifacts, orphaned sessions, old scan artifacts per retention policy
- Worker heartbeat cleanup: remove stale worker registrations
In AWS, you run the maintenance task with scripts/aws/run-migrations.sh, which triggers the ECS task and waits for it to complete.
Use cases invoked by maintenance: ReconcileScansUseCase, OperationsMaintenanceUseCase.
Why separate roles?
| Without role separation | With role separation |
|---|---|
| Scan crash kills the API | API stays up; scan logs to CloudWatch |
| Ingest failure blocks scan capacity | Ingest and scan workers scale independently |
| Provider key in every container | Provider key only in the LLM proxy |
| Can't scale scan workers independently | Workers and runners scale per-demand |
| Migrations run during startup | Migrations run on-demand before deploy |
| One log stream for everything | Per-service log groups for easy debugging |
Docker images
| Image | Dockerfile | Entry script |
|---|---|---|
vega-api |
docker/api/Dockerfile |
uvicorn app.main:app |
vega-worker |
docker/worker/Dockerfile |
scripts/run-scan-worker.py |
vega-core-runner |
docker/vega-core-runner/Dockerfile |
scripts/run-scan-runner.py |
vega-repo-ingest-worker |
docker/repo-ingest-worker/Dockerfile |
scripts/run-repo-ingest-worker.py |
vega-repo-ingest-runner |
docker/repo-ingest-runner/Dockerfile |
scripts/run-repo-ingest-runner.py |
vega-llm-proxy |
docker/llm-proxy/Dockerfile |
uvicorn app.llm_proxy.main:app |
vega-maintenance |
docker/maintenance/Dockerfile |
scripts/run-maintenance.py |
vega-codex-runner |
docker/codex-runner/Dockerfile |
local Codex isolation |