426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
247 lines
6.9 KiB
Python
247 lines
6.9 KiB
Python
"""BackendAdapter protocol — the abstraction every cloud GPU backend
|
|
must implement so the orchestrator can stay backend-agnostic.
|
|
|
|
The vast.ai bash launcher (``scripts/train_vast.sh``) is the reference
|
|
implementation in legacy form. ``VastBackend`` re-expresses every step it
|
|
performs (search → provision → sync → run → status → teardown) as
|
|
typed Python so other backends (Nebius, RunPod, local-docker, Lambda)
|
|
can drop in by satisfying this same Protocol.
|
|
|
|
This module is **non-breaking**: nothing in ``train_vast.sh`` or
|
|
``scripts/lib/vast.py`` imports from here. New code paths build on this
|
|
Protocol; legacy paths keep working unchanged.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Iterable, Mapping
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Protocol, runtime_checkable
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Value objects
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OfferConstraints:
|
|
"""Filter criteria used to narrow a backend's offer catalog.
|
|
|
|
``gpu_target`` is a backend-specific token (e.g. vast's
|
|
``"b200-2x"``). Adapters are responsible for translating it into
|
|
their native search vocabulary; cross-backend mapping lives in the
|
|
orchestrator, not here.
|
|
"""
|
|
|
|
gpu_target: str
|
|
min_disk_gb: int = 500
|
|
min_inet_down_mbps: int = 500
|
|
min_reliability: float = 0.97
|
|
min_duration_days: float = 3.0
|
|
max_dph: float | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Offer:
|
|
"""A normalized rental offer from any backend."""
|
|
|
|
backend: str
|
|
id: str
|
|
gpu_name: str
|
|
num_gpus: int
|
|
gpu_total_ram_gb: int
|
|
dph: float
|
|
reliability: float
|
|
inet_down_mbps: float
|
|
disk_space_gb: float
|
|
geolocation: str
|
|
raw: Mapping[str, object]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class InstanceHandle:
|
|
"""Opaque pointer to a provisioned instance.
|
|
|
|
``backend`` + ``instance_id`` together uniquely identify an
|
|
instance across the world. ``label`` is the human tag the operator
|
|
set at provision time; ``created_at`` is a unix timestamp.
|
|
"""
|
|
|
|
backend: str
|
|
instance_id: str
|
|
label: str
|
|
created_at: float
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class InstanceStatus:
|
|
"""Snapshot of an instance's runtime state.
|
|
|
|
``state`` is normalized to one of:
|
|
``loading`` — booting, image pull, OS init in progress
|
|
``running`` — ready for ssh / training
|
|
``stopped`` — paused but not destroyed (billing may continue)
|
|
``destroyed`` — gone; will not come back
|
|
``unknown`` — backend returned a state we don't recognize
|
|
|
|
``public_endpoint`` is the canonical reachable address (e.g.
|
|
``"ssh://root@host:port"`` or ``"http://host:8080"``); ``None``
|
|
when the instance has no exposed surface yet.
|
|
"""
|
|
|
|
state: str
|
|
gpu_name: str
|
|
num_gpus: int
|
|
uptime_s: float | None
|
|
public_endpoint: str | None
|
|
raw: Mapping[str, object]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ExitCode:
|
|
"""Result of a remote command invocation."""
|
|
|
|
code: int
|
|
duration_s: float
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Typed exceptions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class BackendError(RuntimeError):
|
|
"""Base for every backend-originated failure."""
|
|
|
|
|
|
class NoOffersError(BackendError):
|
|
"""``search_offers`` returned an empty list for the given constraints."""
|
|
|
|
|
|
class ProvisionError(BackendError):
|
|
"""``provision`` failed before an instance handle could be created."""
|
|
|
|
|
|
class InstanceNotFoundError(BackendError):
|
|
"""The referenced instance does not exist (or has been destroyed)."""
|
|
|
|
|
|
class SshUnreachableError(BackendError):
|
|
"""SSH endpoint not yet reachable; usually transient during loading."""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Protocol
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@runtime_checkable
|
|
class BackendAdapter(Protocol):
|
|
"""The contract every cloud GPU backend implements.
|
|
|
|
Implementations must be cheap to construct (no network I/O in
|
|
``__init__``); all I/O happens in the methods below. Methods raise
|
|
``BackendError`` (or a subclass) on failure — adapters must not
|
|
swallow errors silently.
|
|
"""
|
|
|
|
name: str
|
|
|
|
def search_offers(self, c: OfferConstraints) -> list[Offer]:
|
|
"""Return all offers matching ``c``, sorted cheapest first."""
|
|
...
|
|
|
|
def provision(
|
|
self,
|
|
offer_id: str,
|
|
*,
|
|
disk_gb: int,
|
|
image: str,
|
|
ssh_pubkey_path: Path,
|
|
label: str,
|
|
) -> InstanceHandle:
|
|
"""Create an instance from ``offer_id``. Raises ``ProvisionError``."""
|
|
...
|
|
|
|
def wait_running(self, h: InstanceHandle, *, timeout_s: int = 1200) -> None:
|
|
"""Block until the instance reports ``state == 'running'``."""
|
|
...
|
|
|
|
def sync_to(
|
|
self,
|
|
h: InstanceHandle,
|
|
src: Path,
|
|
dst: str,
|
|
*,
|
|
excludes: Iterable[str] = (),
|
|
includes: Iterable[str] = (),
|
|
delete: bool = False,
|
|
) -> None:
|
|
"""Copy ``src`` (local) to ``dst`` (remote path on the instance)."""
|
|
...
|
|
|
|
def run_remote(
|
|
self,
|
|
h: InstanceHandle,
|
|
command: str,
|
|
*,
|
|
env: Mapping[str, str] = {},
|
|
stream: bool = True,
|
|
timeout_s: int | None = None,
|
|
) -> ExitCode:
|
|
"""Execute ``command`` on the instance over SSH."""
|
|
...
|
|
|
|
def sync_from(
|
|
self,
|
|
h: InstanceHandle,
|
|
src: str,
|
|
dst: Path,
|
|
*,
|
|
includes: Iterable[str] = (),
|
|
excludes: Iterable[str] = (),
|
|
) -> None:
|
|
"""Copy ``src`` (remote) to ``dst`` (local path)."""
|
|
...
|
|
|
|
def status(self, h: InstanceHandle) -> InstanceStatus:
|
|
"""Return the current ``InstanceStatus``."""
|
|
...
|
|
|
|
def teardown(self, h: InstanceHandle, *, force: bool = False) -> None:
|
|
"""Destroy the instance. Idempotent — calling on an already-destroyed
|
|
instance must succeed (with a warning) rather than raise."""
|
|
...
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Registry
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
BACKEND_REGISTRY: dict[str, type] = {}
|
|
|
|
|
|
def register_backend(name: str) -> Callable[[type], type]:
|
|
"""Class decorator that registers a ``BackendAdapter`` implementation
|
|
under ``name`` so the CLI can resolve it by string.
|
|
|
|
Usage::
|
|
|
|
@register_backend("vast")
|
|
class VastBackend:
|
|
...
|
|
"""
|
|
|
|
def decorate(cls: type) -> type:
|
|
if name in BACKEND_REGISTRY:
|
|
raise ValueError(
|
|
f"backend {name!r} already registered to {BACKEND_REGISTRY[name]!r}"
|
|
)
|
|
BACKEND_REGISTRY[name] = cls
|
|
return cls
|
|
|
|
return decorate
|