Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

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