# openalice-scheduler

> GPU/compute lease service — acquire/release/list workers from local-Docker or RunPod backends for renderer and anim workloads.

## At a glance
| field | value |
|---|---|
| kind | `service` |
| priority | `P3` |
| default branch | `mvp` |
| last activity | 2026-06-16T01:44:17+00:00 |
| features catalogued | **17** |
| runtime deps declared | **3** |

## Manifest
`.atlas-deps.yml` — parsed cleanly: **yes**

### Runtime deps
- **`openalice-axum-common`** · `other` · via `cargo` — Shared axum plumbing: verify_internal_secret, cors_from_env_or_default, MetricsBuilder.
- **`openalice-anim`** · `http` · via `INTERNAL_SECRET` · _optional_ — anim service calls POST /v1/lease + DELETE /v1/lease/{id} to provision and release anim workers.
- **`openalice-live-control`** · `http` · via `INTERNAL_SECRET` · _optional_ — live-control calls POST /v1/lease + DELETE /v1/lease/{id} to provision and release renderer workers.

### Consumed env
- `INTERNAL_SECRET`
- `SCHEDULER_PORT`
- `PROVIDERS`
- `RENDERER_IMAGE`
- `ANIM_IMAGE`
- `DOCKER_NETWORK`
- `RUNPOD_API_KEY`
- `RUNPOD_RENDERER_TEMPLATE`
- `RUNPOD_ANIM_TEMPLATE`
- `OTEL_EXPORTER_OTLP_ENDPOINT`
- `OTEL_SERVICE_NAME`
- `RUST_LOG`
- `CORS_ORIGINS`

### Exposes
- `docker_image` — openalice-scheduler
- `http_endpoint` — POST /v1/lease
- `http_endpoint` — DELETE /v1/lease/{lease_id}
- `http_endpoint` — GET /v1/leases
- `http_endpoint` — GET /health
- `http_endpoint` — GET /metrics

## Env drift

**Manifest declares, code unused (2):**
- `CORS_ORIGINS`
- `RUST_LOG`

## Features (17)

### scheduler/api
- **`scheduler.api.acquire`** — POST /v1/lease — X-Internal-Secret gated. Selects the first available provider via `OrderedFirstAvailable` policy, calls `provider.provision(req)`, records the resulting `Lease` in the in-memory `LeaseRegistry`, and returns `{ lease, provider }`. Returns 503 if no provider can satisfy the request, 502 on provision failure. [tracing::instrument(  
  *src/main.rs:257*
- **`scheduler.api.list`** — GET /v1/leases — X-Internal-Secret gated. Returns the full snapshot of active `Lease` objects held in the in-memory registry at the moment of the call. Used by live-control and ops tooling to inspect pool occupancy.  
  *src/main.rs:381*
- **`scheduler.api.release`** — DELETE /v1/lease/{lease_id} — X-Internal-Secret gated. Verifies `X-Lease-Owner` when `Lease.owner_id` is set (403 on mismatch). Atomically removes the lease from the registry via `registry.take`, calls `provider.release(lease)` to stop + remove the container (Docker) or terminate the pod (RunPod), and returns 204 No Content. Returns 404 if the lease is not found. [tracing::instrument(  
  *src/main.rs:308*

### scheduler/health
- **`scheduler.health.endpoint`** · _alpha_ · since 0.1.0 — GET /health returns `{status:"ok", service:"openalice-scheduler"}` — Traefik liveness probe; un-gated so the probe doesn't depend on the internal secret being mounted into the load balancer.  
  *src/main.rs:394*

### scheduler/metrics
- **`scheduler.metrics.endpoint`** · _alpha_ · since 0.1.0 — GET /metrics — Prometheus text with `scheduler_up`, `scheduler_active_leases` (current registry size), and `scheduler_providers` (configured driver count); built via openalice-axum-common's MetricsBuilder for parity with sibling services.  
  *src/main.rs:408*

### scheduler/policy
- **`scheduler.policy.ordered-first-available`** · _alpha_ · since 0.1.0 — Ordered first-available provider selection — iterates declared providers in PROVIDERS order and picks the first whose `available(req)` returns true. Cheapest-first / geo-aware revs are roadmap.  
  *src/policy.rs:9*

### scheduler/provider
- **`scheduler.provider.local-docker`** · _beta_ · since 0.1.0 — Default GpuProvider — spawns renderer/anim containers via bollard on the host Docker engine, attaches them to DOCKER_NETWORK, and tears them down on release. Handles gpu=none + gpu=entry; the free-tier path.  
  *src/providers/local_docker.rs:12*
- **`scheduler.provider.runpod`** · _alpha_ · since 0.1.0 — RunPod GraphQL driver — provisions renderer/anim pods via podFindAndDeployOnDemand (non-interruptable) and releases via podTerminate. Activated when `runpod` is listed in PROVIDERS and RUNPOD_API_KEY is set.  
  *src/providers/runpod.rs:11*
- **`scheduler.provider.trait`** — `GpuProvider` defines the two-method contract (`provision` + `release`) all backends must implement plus a cheap `available` probe used by the policy layer. Public types `LeaseRequest`, `Lease`, `WorkerKind` (`renderer` | `anim`), and `GpuClass` (`none` | `entry` | `mid` | `high`) are the only wire types the scheduler API exposes; callers and providers share nothing else.  
  *src/provider.rs:11*

### scheduler/providers
- **`scheduler.providers.build`** · _alpha_ · since 0.1.0 — build_providers reads the `PROVIDERS` env (comma-separated names, default "local-docker"), instantiates each driver in declared order, and returns them as `Vec<Arc<dyn GpuProvider>>` for the policy layer; bails when an unknown name is listed or no driver is configured.  
  *src/main.rs:200*

### scheduler/registry
- **`scheduler.registry.spawn-sweeper`** · _alpha_ · since 0.1.0 — spawn_sweeper boots the 30-second TTL reaper Tokio task; first tick is dropped so the sweep only runs on the periodic cadence, not at startup before any lease exists.  
  *src/leases.rs:116*
- **`scheduler.registry.store`** · _alpha_ · since 0.1.0 — LeaseRegistry — in-memory `Arc<RwLock<HashMap<lease_id, ActiveLease>>>` shared across HTTP handlers + the sweeper task; `record` / `take` / `list` / `count` are the only mutation/read surface so consistency between provider drivers and the API stays tight.  
  *src/leases.rs:28*
- **`scheduler.registry.sweep`** — Background TTL sweeper spawned at startup via `spawn_sweeper`. Every 30 seconds `sweep_once` reads expired leases (expires_at ≤ now), atomically removes each entry, and calls `provider.release(lease)` on the owning driver. A failed release is logged as a warning; the entry is still removed so the registry stays consistent. Bounds cloud costs on crashed callers.  
  *src/leases.rs:10*

### scheduler/service
- **`scheduler.service.crate`** — GPU/compute lease service for OpenAlice. Exposes a uniform HTTP API (POST /v1/lease, DELETE /v1/lease/{id}, GET /v1/leases) so upstream services (openalice-live-control, openalice-anim) provision and release renderer + anim workers without caring which backend (local-docker, RunPod, Modal, …) runs the work. Lifecycle: provision → optional heartbeat via TTL → explicit release or 30 s sweep auto-reap.  
  *src/lib.rs:5*

### scheduler/tracing
- **`scheduler.tracing.otlp`** · _alpha_ · since 0.1.0 — init_otel_tracer wires an OTLP gRPC span exporter (opt-in via `OTEL_EXPORTER_OTLP_ENDPOINT`) and tags every span with `service.name` + `service.version` so traces land in Jaeger/Tempo under the right service without per-deploy config; failure falls back to logs-only.  
  *src/main.rs:67*

### scheduler/types
- **`scheduler.types.lease`** · _alpha_ · since 0.1.0 — Lease wire shape — { lease_id, provider, kind, endpoint, created_at, expires_at, provider_token, owner_id }; `provider_token` is the opaque id (container id / RunPod pod_id) the driver needs to release.  
  *src/provider.rs:121*
- **`scheduler.types.lease-request`** · _alpha_ · since 0.1.0 — LeaseRequest wire shape — { kind (renderer|anim), gpu (none|entry|mid|high), correlation_id, owner_id, region, ttl_seconds (≤8h), env map }; default ttl=3600, default gpu=None; carries `owner_id` so release can enforce X-Lease-Owner.  
  *src/provider.rs:57*

---

*Generated by [openalice-atlas](https://atlas.blal.pro) — single source of truth for the openalicelabs ecosystem.*
