Eyvan (ایوان)
Eyvan is a capacity plane for research compute. You state the shape of work you
need — 2 × v5p-16, spot, this fleet — as a digest-verified unsigned Python
callable submitted to an explicitly trusted project. The plane acquires the
machines, boots them, dispatches your callable, and returns its result.
Capacity records show what was requested, what a provider accepted or refused,
and how long the handover took.
The sixty-second version
uv sync --extra dev # workspace install + development dependencies
uv run eyvan up examples/laptop.yaml # start the local loopback CPU fleet before SDK init
uv run eyvan start # find or point at a control plane
uv run eyvan doctor # what is missing, named together
Eyvan supports Python >=3.11,<3.14 (the range declared in pyproject.toml).
from eyvan import CpuConfig, execute, init
init(fleet="laptop")
@execute(CpuConfig())
def train():
return sum(range(10))
Calling train() submits the callable and waits, narrating progress:
queued, provisioning, blocked … the plane is retrying (attempt N),
dispatching, then your result. The job is durable — it keeps running if your
client exits (eyvan workload status <id> to follow).
With a TPU fleet
First configure a fleet with matching TPU pools and a runtime that provides JAX. The laptop fleet above has only CPU capacity.
from eyvan import TpuConfig, execute, init
from eyvan.records import CapacityClass
init(fleet="research-us-central1-a")
@execute(TpuConfig(type="v5p-16", pods=2, capacity_class=CapacityClass.SPOT))
def device_count():
import jax
return jax.device_count()
The SDK in one page
Connecting: init
init(address=None, /, *, fleet=None, org_id=None, project_id=None,
artifact_uri=None, artifact_store=None, ...)
With no arguments the SDK discovers the plane the CLI uses (eyvan start
shows you what exists: a local plane, or heads in your project). Point it at
an explicit plane by address — eyvan.init("http://10.0.0.5:8080") — or
bind a default fleet with eyvan.init(fleet="research-us-central1-a").
Everything else is scope (org_id, project_id), where artifacts go (a
gs:// root or your own ArtifactStore implementation), and fleet-state
location (EYVAN_FLEET_STATE_URI) when agents own distinct capacity
boundaries.
The shape: TpuConfig, GpuConfig, CpuConfig
One decorator takes all three; everything under it is shape-agnostic. The
TPU config's full surface (eyvan/sdk/client/jobs.py):
| field | default | meaning |
|---|---|---|
type |
— | accelerator type, e.g. "v5p-16", "v6e-8" |
pods |
1 |
workers — how many slices of type the job asks for |
atomic |
False |
False (elastic, node-pool style): pack warm cells of the same type and provision only the deficit — going pods 2→4 keeps the two warm slices and asks for two more, one lease group, one hardware world per cell. True: one exact-shape provider resource, one hardware world spanning every pod — what single-mesh training needs |
capacity_class |
Spot preference | SPOT, ON_DEMAND, RESERVED, or FLEX_START; an explicit value is exact. If omitted, a reserved-only fleet may substitute RESERVED |
max_retries |
DEFAULT |
retries for lost capacity attempts |
max_preemption_retries |
10 |
restarts after the provider reclaims spot — deliberately generous; reclamation is the cheap failure |
max_failure_retries |
0 |
retries of the job's own failure — deliberately stingy; a non-zero exit usually repeats, and re-running it costs an accelerator |
secret_mounts |
{} |
secret references resolved at dispatch into env vars on the cell — a reference is what gets persisted, never the value |
checkpointable / checkpoint_hook / checkpoint_output_uri |
False/— |
the scheduler grants preemption-checkpoint time only when a real hook is named; otherwise a reclaimed job restarts from zero |
can_submit_children |
False |
lets the job submit children under your identity — off by default; a credential nobody asked for is one nobody is watching |
dependencies |
() |
packages installed on the cell before your callable loads — adding a library should not mean rebuilding an image |
The decorator: execute
@execute(TpuConfig(type="v6e-8", pods=4), workspace="src/")
def run(epochs: int):
...
workspace= accepts a directory path and ships that tree to the worker's
working directory before the callable imports. Omit it to derive the workspace
from the decorated function's source-file directory; interactive callables
without a source file need an explicit path. Set workspace=False to disable
workspace shipping.
Calling: blocking wait, or detached handle
result = run(epochs=3) # blocks; live progress lines; returns your value
job = run.submit(epochs=3) # EyvanJob — detach, follow, intervene
job.status() # WorkloadState
job.logs() # the flattened, globally-ranked log stream
job.wait(timeout=600, progress=True) # blocking wait, same narration
job.result(timeout=3600) # your value, or EyvanJobError with the reason
job.cancel() # durable cancel
Your callable's return value comes back through the artifact store; its
imports resolve on the cell (image packages + dependencies + whatever
workspace shipped). Identical callables deduplicate by content digest;
a concurrent upload verifies the existing object's digest before using it.
See artifact-safety.
One more client-side knob worth knowing: the returned job exposes its
transport (.base_url) so tooling built on the SDK can follow the same
plane the SDK followed, without re-deriving discovery.
Failure narration is part of the API contract: a refused capacity attempt
renders as blocked | … | the plane is retrying (attempt N) with the
provider's diagnostic, and a terminal failure arrives as an exception whose
message contains the provider's own sentence — not a bare exit code.
Developing on top of Eyvan
Four seams, in order of how often you need them:
- A different shape — no code: a
TpuConfig/GpuConfig/CpuConfigvalue. Check the shape is real for you first:eyvan capacity quota --fits v5p-16:2(quota and admission permission and supply are three different gates). - A different fleet — a YAML config:
eyvan up <config>from a hand-written file (seeexamples/fleet.yaml,examples/laptop.yaml) or a generated fleet (eyvan up --region <region> --preemptible, oreyvan up --quota-based --zone <zone> --preemptible). Node types, runtime env, and autoscaling knobs live in the config, not in your callable. - A different cloud — a provider package with a driver and transport. External plugins default to single-machine cells; multi-worker planning also needs an in-tree capability entry and a supporting pool factory. See providers for the ABI and conformance contract.
- A different workflow — composition:
can_submit_children=Truegrants your job a submission capability under your identity (fan-out workers, coordinator patterns), and checkpointing hooks let long spot jobs survive reclamation by design rather than by luck.
Developing Eyvan itself
git clone <this repo> && cd eyvan
uv sync --extra dev # editable install + dev deps (pytest, pytest-asyncio)
npm --prefix dashboard ci
npm --prefix dashboard run build # build the SPA before Python tests
uv run pytest tests -m fast -q # smoke subset for iteration, not a release gate
uv run pytest tests -q -rs # full suite; real PostgreSQL requires the test fixture setup
uv run ruff check eyvan tests # lint (line 121, py3.11)
uv run eyvan-server --log-level info # run the plane from source
uv run eyvan-agent --host 127.0.0.1 --port 8090 --allow-unauthenticated # local dev only
Verify the dashboard from a fresh checkout with the commands in Contributing — Dashboard:
npm --prefix dashboard ci
npm --prefix dashboard test
npm --prefix dashboard run build
The rules that keep the package honest — enforce them in review, not in memory:
- Provider boundary: nothing that decides (scheduler, planner, durable
model) may name a provider;
tests/test_provider_boundary.pyenforces it. - Claims need receipts: a behavior claim cites a recorded run; hardware
claims go through
eyvan proof(exit 0 pass, 1 fail, 2 did not exercise the behaviour, 3 refusal) — see proofs. - Tests assert public behavior: state transitions, API outputs, parity — not private helper calls or log strings.
- Diagnostics travel with failures: a CLI or provider failure raises with
its own words attached (
GcsCommandErrorcarries command + stderr). - Architecture decisions are design records under docs/design/, each stating its real implementation status.
The ideas everything else follows from
- You state the shape, not the pool. The scheduler and capacity planner decide where that shape can exist.
- Receipts or it did not happen. Capacity claims cite recorded runs.
- Failure narrates itself. Diagnostics render, never
FAILED. - Nothing that decides names a provider. Clouds plug in; the core stays agnostic.
- Durable state survives the plane. A restarted
eyvan-serverreconciles the whole world from its durable store.
Concepts
| term | meaning |
|---|---|
| plane | the control plane (eyvan-server) — scheduler, planner, state |
| agent | a cell agent (eyvan-agent) owning machines on one backend |
| fleet | a named set of capacity pools you can submit into |
| pool | one (backend, accelerator, capacity class, zone) combination |
| cell | one acquired allocation (e.g. one spot queued resource) |
| workload | your callable + its requested shape, moving through durable states |
Where it runs
Registered capacity backends: local (loopback laptop fleets), gcp-tpu
(generated zone fleets driving TPU queued resources, spot or on-demand),
gcp-compute, the gcp umbrella, aws-ec2 (the worked provider example)
and the aws umbrella, azure-vm and the azure umbrella, kubernetes
(k8s) against a real API server, manual, onprem-ssh, and gcp-attach
(an existing Cloud TPU VM attached with eyvan gcp connect; observed, never
created or deleted).
Backend availability is not proof of qualification on your infrastructure. GCP TPU is the backend with live hardware runs; the on-premises SSH driver is code with no run against a real SSH host yet. See proofs for evidence requirements and backend-specific limits.
The docs map
New to the project? Start with the tutorial — seventeen source-grounded parts that walk from "what is Eyvan" through the SDK, the control plane, providers, operations, deployment, and an explicit Spot TPU example.
| page | what it answers |
|---|---|
| tutorial | a part-per-subsystem tutorial, grounded in the source |
| eyvan-explained | the mental model, one workload's full journey |
| install | laptop path, the fifteen, GCP head, secrets |
| operate | the runbook: fleets, workloads, capacity, zone racing |
| runtimes | the two provisioning models, the defaults profile, custom images |
| proofs | what is proven, with receipts; known rough edges |
| providers | writing a capacity provider; the conformance contract |
| security | trust boundaries, tokens, what jobs can do |
| env-surface | every EYVAN_* variable |
| artifact-safety | tenancy, lifecycle, the digest-addressed store |
| troubleshoot | symptom → cause → action tables |
| design records | the ADRs and their real status |
What is proven
See proofs for proof criteria and evidence limits. After a restart, use the intended durable state and matching credentials; inspect quarantined leases before intervening. See operate and troubleshoot for recovery procedures.
Metadata
Release files for eyvan 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| eyvan-0.0.1.tar.gz | 2.1 MB | Without provenance | Details for eyvan-0.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| eyvan-0.0.1-py3-none-any.whl | Python 3 | none | any | Details for eyvan-0.0.1-py3-none-any.whl |
Total release size: 4.4 MB
Release files / eyvan-0.0.1.tar.gz
| Download URL | eyvan-0.0.1.tar.gz |
|---|---|
| Size | 2.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1492841f54ae7218c474e6e6c221724b675802e1beac5135594dee7a7e71b769
|
|
BLAKE2b-256 checksum How to use checksums |
446b5f5c939701001b0c03d58e2ab7a763c6078f36147b16eeecef78335507e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / eyvan-0.0.1-py3-none-any.whl
| Download URL | eyvan-0.0.1-py3-none-any.whl |
|---|---|
| Size | 2.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
08541ec930b1ada3047f33daae6041fc79aa6d3c9997e3302a1ad683ffbb3411
|
|
BLAKE2b-256 checksum How to use checksums |
b999be2d55ebaf0a21794188dae531218c8f90fe248bc9ec291ee28cf095f760
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for eyvan 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.