ADR-0010: Provider adapters and provisioning
- Status: Accepted (milestone 1) for the adapter interface and provisioning flow, tested only against
FakeProvider(simulated). The choice of a first real provider is Proposed. - Date: 2026-10-29
Context
Cloud execution must be a first-class target without coupling the core to one vendor or inventing provider capabilities, prices or APIs. Provisioning an instance does not prove that its GPU runtime works.
Decision
ProviderAdapter(src/gpulayer/providers/base.py) declaresfeatures()and implementslist_offers,provision(offer_id, bootstrap),get_instanceand an idempotentterminate. Errors areProviderError,CapacityUnavailable(retryable) andQuotaExceeded.simulated=Truemarks fakes in events.- An offer is a
WorkerCapabilitiestemplate plus an optionalPriceQuotewith currency, unit, source and timestamp. The provider-specificinstance_typeis metadata only, and the scheduler never sees it. ProvisioningManager(control_plane/provisioning.py) handles jobs that haveplacement.provisioningand no eligible worker:- It filters offers with the same
compatibility_reasonsused for workers, plusmax_hourly_cost. An offer without a price is rejected when a cap is set. - It picks the cheapest known price deterministically and records a
CostRecord(kind= "estimate"). - It provisions
num_nodesinstances. Each gets a single-use instance token bound to an internalinst-…reference and areserved-for=<job>label, passed inWorkerBootstrap. - The job waits in
provisioning. The booted worker registers with that token, and identity fields (provider, region, reservation label) are taken from the control plane's record. - The job is placed only when the worker's reported capabilities are compatible.
- Provisioning failure or timeout fails the job and terminates the instances. When
the job reaches a terminal state, instances with
release=terminate_on_completionare terminated and their workers revoked.
- It filters offers with the same
FakeProvider(providers/fake.py) is deterministic and in-memory, with capacity limits, failure injection and anon_provisionhook that plays the machine's boot script in tests.
Candidate evaluation criteria for the first real provider (no choice made)
- A documented, stable API for create/describe/terminate, with idempotency tokens.
- Bootstrap mechanism (user-data or cloud-init) and secret delivery that keeps the instance token out of logs and metadata.
- GPU types available, with a reliable machine-readable pricing source.
- Scoped credentials (least privilege), and tag-based discovery of orphaned instances.
- Egress model and reachability back to a self-hosted control plane.
- Availability of a low-cost test environment for CI validation.
Alternatives considered
- Provider SDKs inside the scheduler: rejected, because they leak vendor concepts.
- Pre-provisioned pools only: supported already as
remoteworkers, but not sufficient for elastic cloud use.
Consequences and trade-offs
- No real cloud provisioning exists. All cloud-path tests are simulated.
- Known gaps: provider calls run while the DB transaction lock is held (ADR-0001).
There is no orphan reconciliation (scanning the provider for tagged instances we lost
track of), and no retry on
CapacityUnavailable. Termination failure is logged as an event ("may be orphaned") but not retried.
Security and operational implications
- Instance tokens are single-use, hashed at rest, and unusable after registration or for another instance. Provider credentials belong to the adapter configuration, never to job specs or logs.
Follow-up
- An ADR selecting the first real provider using the criteria above, then an adapter with integration tests against a real account.
- Move provider I/O out of transactions, orphan sweeps and retry with backoff.