Skip to content

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:

  1. Sends a heartbeat to keep its registration alive
  2. Runs ReconcileScansUseCase to detect and recover stale running scans
  3. Runs RunQueuedScanUseCase to claim the next queued scan and either execute it locally (scan_worker_execution_mode=local) or launch a vega-scan-runner ECS 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:

  1. Builds a RuntimeContainer for the runner role via build_scan_runner_runtime()
  2. Calls ExecuteScanUseCase with the assigned scan ID and phase
  3. Loads the source snapshot (from S3 or locally)
  4. Calls VegaCoreEngineAdapter which invokes the vega-core library
  5. Writes DomainEvents and FindingRecords to Postgres
  6. Uploads ArtifactRecords to S3
  7. Updates the ScanRecord state 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:

  1. Sends a heartbeat
  2. Runs ReconcileRepositoryIngestUseCase to recover stale ingest jobs
  3. Runs RunQueuedRepositoryIngestUseCase to claim an ingest job and either execute it locally or launch a vega-repo-ingest-runner ECS 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:

  1. Claims the ingest job in Postgres
  2. Calls ExecuteRepositoryIngestUseCase
  3. Clones the git repository or extracts the uploaded zip archive
  4. Stores the materialized source snapshot in S3 (or locally)
  5. Creates a SourceSnapshot record
  6. Transitions the repository state to snapshottedready
  7. 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 running too long and mark them failed
  • 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