jambu-gpu 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jambu_gpu-0.5.0/PKG-INFO +27 -0
- jambu_gpu-0.5.0/README.md +12 -0
- jambu_gpu-0.5.0/pyproject.toml +24 -0
- jambu_gpu-0.4.1/.env.example +0 -11
- jambu_gpu-0.4.1/.gitignore +0 -17
- jambu_gpu-0.4.1/CONTRIBUTING.md +0 -75
- jambu_gpu-0.4.1/PKG-INFO +0 -90
- jambu_gpu-0.4.1/README.md +0 -67
- jambu_gpu-0.4.1/docs/ACCEPTANCE.md +0 -85
- jambu_gpu-0.4.1/docs/ADDING_A_PROVIDER.md +0 -132
- jambu_gpu-0.4.1/docs/AGENTS.md +0 -56
- jambu_gpu-0.4.1/docs/CHOOSING_A_MODEL.md +0 -198
- jambu_gpu-0.4.1/docs/CONFIG.md +0 -242
- jambu_gpu-0.4.1/docs/INTEGRATION.md +0 -233
- jambu_gpu-0.4.1/docs/LIFECYCLE.md +0 -128
- jambu_gpu-0.4.1/docs/USAGE.md +0 -156
- jambu_gpu-0.4.1/docs/assets/jambu-logo.jpg +0 -0
- jambu_gpu-0.4.1/examples/experiment.py +0 -48
- jambu_gpu-0.4.1/examples/jambu.yaml +0 -36
- jambu_gpu-0.4.1/examples/mastra-agent/README.md +0 -70
- jambu_gpu-0.4.1/examples/mastra-agent/package-lock.json +0 -2235
- jambu_gpu-0.4.1/examples/mastra-agent/package.json +0 -19
- jambu_gpu-0.4.1/examples/mastra-agent/src/agent.ts +0 -42
- jambu_gpu-0.4.1/examples/mastra-agent/src/index.ts +0 -11
- jambu_gpu-0.4.1/examples/mastra-agent/src/tools/weather-tool.ts +0 -36
- jambu_gpu-0.4.1/examples/mastra-agent/tsconfig.json +0 -15
- jambu_gpu-0.4.1/examples/typescript-client/README.md +0 -44
- jambu_gpu-0.4.1/examples/typescript-client/package-lock.json +0 -71
- jambu_gpu-0.4.1/examples/typescript-client/package.json +0 -18
- jambu_gpu-0.4.1/examples/typescript-client/src/index.ts +0 -31
- jambu_gpu-0.4.1/examples/typescript-client/src/jambuGpu.ts +0 -178
- jambu_gpu-0.4.1/examples/typescript-client/tsconfig.json +0 -16
- jambu_gpu-0.4.1/jambu.example.yaml +0 -89
- jambu_gpu-0.4.1/jambu.models.yaml +0 -102
- jambu_gpu-0.4.1/jambu.yaml +0 -42
- jambu_gpu-0.4.1/jambu_gpu/__init__.py +0 -5
- jambu_gpu-0.4.1/jambu_gpu/__main__.py +0 -4
- jambu_gpu-0.4.1/jambu_gpu/agent/__init__.py +0 -1
- jambu_gpu-0.4.1/jambu_gpu/agent/install.py +0 -173
- jambu_gpu-0.4.1/jambu_gpu/agent/jobs.py +0 -193
- jambu_gpu-0.4.1/jambu_gpu/agent/mcp.py +0 -646
- jambu_gpu-0.4.1/jambu_gpu/agent/skill/SKILL.md +0 -68
- jambu_gpu-0.4.1/jambu_gpu/cli/__init__.py +0 -0
- jambu_gpu-0.4.1/jambu_gpu/cli/_common.py +0 -131
- jambu_gpu-0.4.1/jambu_gpu/cli/account.py +0 -330
- jambu_gpu-0.4.1/jambu_gpu/cli/agent.py +0 -74
- jambu_gpu-0.4.1/jambu_gpu/cli/inspect.py +0 -207
- jambu_gpu-0.4.1/jambu_gpu/cli/lifecycle.py +0 -127
- jambu_gpu-0.4.1/jambu_gpu/cli/main.py +0 -151
- jambu_gpu-0.4.1/jambu_gpu/cli/providers.py +0 -96
- jambu_gpu-0.4.1/jambu_gpu/cli/run.py +0 -38
- jambu_gpu-0.4.1/jambu_gpu/cli/setup.py +0 -164
- jambu_gpu-0.4.1/jambu_gpu/cli/status.py +0 -224
- jambu_gpu-0.4.1/jambu_gpu/cli/timer.py +0 -143
- jambu_gpu-0.4.1/jambu_gpu/cli/validate.py +0 -57
- jambu_gpu-0.4.1/jambu_gpu/core/__init__.py +0 -0
- jambu_gpu-0.4.1/jambu_gpu/core/beacon.py +0 -133
- jambu_gpu-0.4.1/jambu_gpu/core/config.py +0 -647
- jambu_gpu-0.4.1/jambu_gpu/core/credentials.py +0 -95
- jambu_gpu-0.4.1/jambu_gpu/core/durations.py +0 -47
- jambu_gpu-0.4.1/jambu_gpu/core/engine.py +0 -1335
- jambu_gpu-0.4.1/jambu_gpu/core/errors.py +0 -94
- jambu_gpu-0.4.1/jambu_gpu/core/events.py +0 -117
- jambu_gpu-0.4.1/jambu_gpu/core/execution.py +0 -169
- jambu_gpu-0.4.1/jambu_gpu/core/health.py +0 -62
- jambu_gpu-0.4.1/jambu_gpu/core/hosts.py +0 -58
- jambu_gpu-0.4.1/jambu_gpu/core/model_inspect.py +0 -368
- jambu_gpu-0.4.1/jambu_gpu/core/models.py +0 -313
- jambu_gpu-0.4.1/jambu_gpu/core/paths.py +0 -42
- jambu_gpu-0.4.1/jambu_gpu/core/policy.py +0 -264
- jambu_gpu-0.4.1/jambu_gpu/core/state.py +0 -406
- jambu_gpu-0.4.1/jambu_gpu/core/sweep.py +0 -478
- jambu_gpu-0.4.1/jambu_gpu/core/tool_parsers.py +0 -63
- jambu_gpu-0.4.1/jambu_gpu/providers/__init__.py +0 -4
- jambu_gpu-0.4.1/jambu_gpu/providers/base.py +0 -137
- jambu_gpu-0.4.1/jambu_gpu/providers/registry.py +0 -63
- jambu_gpu-0.4.1/jambu_gpu/providers/vast/__init__.py +0 -3
- jambu_gpu-0.4.1/jambu_gpu/providers/vast/client.py +0 -255
- jambu_gpu-0.4.1/jambu_gpu/providers/vast/config.py +0 -56
- jambu_gpu-0.4.1/jambu_gpu/providers/vast/mapper.py +0 -292
- jambu_gpu-0.4.1/jambu_gpu/providers/vast/provider.py +0 -438
- jambu_gpu-0.4.1/jambu_gpu/runtime/__init__.py +0 -6
- jambu_gpu-0.4.1/jambu_gpu/runtime/base.py +0 -184
- jambu_gpu-0.4.1/jambu_gpu/runtime/vllm.py +0 -295
- jambu_gpu-0.4.1/jambu_gpu/watchdog/__init__.py +0 -3
- jambu_gpu-0.4.1/jambu_gpu/watchdog/agent_body.py +0 -959
- jambu_gpu-0.4.1/jambu_gpu/watchdog/builder.py +0 -248
- jambu_gpu-0.4.1/pyproject.toml +0 -47
- jambu_gpu-0.4.1/tests/conftest.py +0 -81
- jambu_gpu-0.4.1/tests/fakes.py +0 -210
- jambu_gpu-0.4.1/tests/test_agent.py +0 -348
- jambu_gpu-0.4.1/tests/test_cli.py +0 -304
- jambu_gpu-0.4.1/tests/test_config.py +0 -398
- jambu_gpu-0.4.1/tests/test_engine.py +0 -920
- jambu_gpu-0.4.1/tests/test_engine_sessions.py +0 -445
- jambu_gpu-0.4.1/tests/test_integration.py +0 -289
- jambu_gpu-0.4.1/tests/test_model_inspect.py +0 -219
- jambu_gpu-0.4.1/tests/test_paths.py +0 -50
- jambu_gpu-0.4.1/tests/test_policy.py +0 -232
- jambu_gpu-0.4.1/tests/test_runtime_vllm.py +0 -263
- jambu_gpu-0.4.1/tests/test_state.py +0 -187
- jambu_gpu-0.4.1/tests/test_sweep.py +0 -680
- jambu_gpu-0.4.1/tests/test_vast_mapper.py +0 -276
- jambu_gpu-0.4.1/tests/test_vast_provider.py +0 -438
- jambu_gpu-0.4.1/tests/test_watchdog.py +0 -1130
- jambu_gpu-0.4.1/uv.lock +0 -870
- {jambu_gpu-0.4.1 → jambu_gpu-0.5.0}/LICENSE +0 -0
jambu_gpu-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jambu-gpu
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Renamed to gpu4agents (GPU for Agents). This release installs gpu4agents.
|
|
5
|
+
Project-URL: Homepage, https://gpu4agents.jambu.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/jambuai/gpu4agents
|
|
7
|
+
Author: Jambu.ai
|
|
8
|
+
Author-email: João Gabriel Lima <joao.gabriel@jambu.ai>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 7 - Inactive
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: gpu4agents>=0.5.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# jambu-gpu is now gpu4agents
|
|
17
|
+
|
|
18
|
+
This project was renamed to **GPU for Agents**: [`gpu4agents`](https://pypi.org/project/gpu4agents/).
|
|
19
|
+
|
|
20
|
+
This release has no code; it installs `gpu4agents`, which provides the same `gpu` command, reads the same `jambu.yaml` and finds the same machines.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pipx uninstall jambu-gpu && pipx install gpu4agents
|
|
24
|
+
# or: pip install -U gpu4agents
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Website: https://gpu4agents.jambu.ai · Code: https://github.com/jambuai/gpu4agents
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# jambu-gpu is now gpu4agents
|
|
2
|
+
|
|
3
|
+
This project was renamed to **GPU for Agents**: [`gpu4agents`](https://pypi.org/project/gpu4agents/).
|
|
4
|
+
|
|
5
|
+
This release has no code; it installs `gpu4agents`, which provides the same `gpu` command, reads the same `jambu.yaml` and finds the same machines.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pipx uninstall jambu-gpu && pipx install gpu4agents
|
|
9
|
+
# or: pip install -U gpu4agents
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Website: https://gpu4agents.jambu.ai · Code: https://github.com/jambuai/gpu4agents
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jambu-gpu"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "Renamed to gpu4agents (GPU for Agents). This release installs gpu4agents."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jambu.ai" },
|
|
14
|
+
{ name = "João Gabriel Lima", email = "joao.gabriel@jambu.ai" },
|
|
15
|
+
]
|
|
16
|
+
dependencies = ["gpu4agents>=0.5.0"]
|
|
17
|
+
classifiers = ["Development Status :: 7 - Inactive"]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://gpu4agents.jambu.ai"
|
|
21
|
+
Repository = "https://github.com/jambuai/gpu4agents"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
bypass-selection = true
|
jambu_gpu-0.4.1/.env.example
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Credentials are resolved from the environment, this file, or
|
|
2
|
-
# ~/.jambu/credentials - never from jambu.yaml.
|
|
3
|
-
|
|
4
|
-
# Required by the vast provider.
|
|
5
|
-
VAST_API_KEY=
|
|
6
|
-
|
|
7
|
-
# Optional: needed for gated HuggingFace models.
|
|
8
|
-
HF_TOKEN=
|
|
9
|
-
|
|
10
|
-
# Optional: per-profile override, used when provider.profile: staging
|
|
11
|
-
# VAST_API_KEY__staging=
|
jambu_gpu-0.4.1/.gitignore
DELETED
jambu_gpu-0.4.1/CONTRIBUTING.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Contributing
|
|
2
|
-
|
|
3
|
-
`jambu-gpu` provisions a temporary GPU runtime and guarantees it stops billing
|
|
4
|
-
when nothing is using it. Contributions are welcome. By submitting a change you
|
|
5
|
-
agree it is licensed under the [MIT License](LICENSE).
|
|
6
|
-
|
|
7
|
-
## Command name
|
|
8
|
-
|
|
9
|
-
The published command is `gpu`. The package name stays `jambu-gpu`.
|
|
10
|
-
|
|
11
|
-
Do not add a second console script. One command, in `pyproject.toml`:
|
|
12
|
-
|
|
13
|
-
```toml
|
|
14
|
-
[project.scripts]
|
|
15
|
-
gpu = "jambu_gpu.cli.main:main"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Setup
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
python3 -m venv .venv
|
|
22
|
-
.venv/bin/pip install -e ".[dev]"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Python 3.10 or newer. Tests do not need `VAST_API_KEY`, a GPU, or network access.
|
|
26
|
-
|
|
27
|
-
## Tests
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
.venv/bin/pytest
|
|
31
|
-
.venv/bin/pytest tests/test_policy.py # shutdown rule
|
|
32
|
-
.venv/bin/pytest tests/test_watchdog.py # generated guard, as a real process
|
|
33
|
-
.venv/bin/pytest tests/test_integration.py # setup -> boot -> run -> auto-stop
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
A change to lifecycle behaviour needs a test that fails before the change and
|
|
37
|
-
passes after it. The policy in `jambu_gpu/core/policy.py` and the watchdog that
|
|
38
|
-
runs on the instance must keep agreeing: the watchdog inlines that module at
|
|
39
|
-
provision time.
|
|
40
|
-
|
|
41
|
-
`tests/test_integration.py` does not mock the boot. It runs the onstart script
|
|
42
|
-
the engine generated, starts the real watchdog, waits on a real health endpoint,
|
|
43
|
-
runs a workload with real heartbeats, and asserts the guard stops the instance
|
|
44
|
-
with the right reason.
|
|
45
|
-
|
|
46
|
-
## What you may change
|
|
47
|
-
|
|
48
|
-
| Change | Where it goes |
|
|
49
|
-
| --- | --- |
|
|
50
|
-
| New provider | `jambu_gpu/providers/<name>/` plus one registry line. See [docs/ADDING_A_PROVIDER.md](docs/ADDING_A_PROVIDER.md). |
|
|
51
|
-
| Shutdown rule | `jambu_gpu/core/policy.py` and `tests/test_policy.py`. |
|
|
52
|
-
| How the model server starts | `jambu_gpu/runtime/`. |
|
|
53
|
-
| CLI surface | `jambu_gpu/cli/`. Keep `--json` and `--config` on every command. |
|
|
54
|
-
|
|
55
|
-
The core must not branch on a provider name. Branch on `provider.capabilities`.
|
|
56
|
-
If a provider feature requires an `if provider == "..."` in `core/`, the contract
|
|
57
|
-
is wrong — extend the contract instead.
|
|
58
|
-
|
|
59
|
-
Out of scope: multi-instance scheduling, multi-GPU
|
|
60
|
-
orchestration, Kubernetes, distributed inference, automatic provider selection,
|
|
61
|
-
job queues, a web dashboard. See [docs/ACCEPTANCE.md](docs/ACCEPTANCE.md).
|
|
62
|
-
|
|
63
|
-
## Secrets
|
|
64
|
-
|
|
65
|
-
Never commit credentials, `.env`, `.gpu/state.json`, or provider instance
|
|
66
|
-
ids from a real run. Keys are read from the environment, a gitignored `.env`,
|
|
67
|
-
or `~/.gpu/credentials`. `.env.example` ships with empty values only.
|
|
68
|
-
|
|
69
|
-
## Pull requests
|
|
70
|
-
|
|
71
|
-
- Explain why the behaviour should change, not only what moved.
|
|
72
|
-
- Run `.venv/bin/pytest` and report the result.
|
|
73
|
-
- Keep commits focused. A provider adapter and a policy change are two PRs.
|
|
74
|
-
- Match the surrounding style: English identifiers and docs, type hints on new
|
|
75
|
-
public functions, no new dependency unless the standard library cannot do the job.
|
jambu_gpu-0.4.1/PKG-INFO
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: jambu-gpu
|
|
3
|
-
Version: 0.4.1
|
|
4
|
-
Summary: Provider-agnostic CLI for provisioning and managing temporary GPU runtimes
|
|
5
|
-
Project-URL: Homepage, https://github.com/jambuai/jambu-gpu
|
|
6
|
-
Project-URL: Repository, https://github.com/jambuai/jambu-gpu
|
|
7
|
-
Project-URL: Issues, https://github.com/jambuai/jambu-gpu/issues
|
|
8
|
-
Author: Jambu.ai
|
|
9
|
-
Author-email: João Gabriel Lima <joao.gabriel@jambu.ai>
|
|
10
|
-
License: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: httpx>=0.27.0
|
|
14
|
-
Requires-Dist: pydantic>=2.6.0
|
|
15
|
-
Requires-Dist: pyyaml>=6.0.1
|
|
16
|
-
Requires-Dist: rich>=13.7.0
|
|
17
|
-
Requires-Dist: typer>=0.12.0
|
|
18
|
-
Provides-Extra: dev
|
|
19
|
-
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
20
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
21
|
-
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
|
|
24
|
-
# gpu
|
|
25
|
-
|
|
26
|
-
Temporary GPU runtimes that stop billing when the work stops.
|
|
27
|
-
|
|
28
|
-
You declare the model and how long it may live. `gpu` provisions a machine, starts the model server, and runs your job against it. Shutdown happens on the instance, so closing the laptop does not leave the GPU billing.
|
|
29
|
-
|
|
30
|
-
Vast.ai is the first provider. Another provider is a new adapter. The lifecycle rule stays the same.
|
|
31
|
-
|
|
32
|
-
## Run
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
pip install jambu-gpu
|
|
36
|
-
|
|
37
|
-
cd your-experiment
|
|
38
|
-
gpu init --model empero-ai/Qwythos-9B-v2
|
|
39
|
-
export VAST_API_KEY=...
|
|
40
|
-
|
|
41
|
-
gpu validate
|
|
42
|
-
gpu setup
|
|
43
|
-
gpu run python experiment.py
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
When you are finished, `gpu destroy`. If you walk away, the idle timeout does it.
|
|
47
|
-
|
|
48
|
-
From an application in any language: `gpu --json setup` returns the OpenAI-compatible `base_url`, `api_key` and `model`, and the app uses any OpenAI SDK. See [Integrating gpu into an application](docs/INTEGRATION.md), with a ready TypeScript client.
|
|
49
|
-
|
|
50
|
-
From a coding agent: `gpu agent install` gives Claude Code (or `-t cursor`, `-t desktop`) a skill and local MCP tools to rent a GPU, run the experiment and shut it down. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
|
|
51
|
-
|
|
52
|
-
As a last line of defense, `gpu ls` shows every jambu instance in the account and `gpu sweep --install-timer` sets up a systemd user timer that guards every project on the machine by its own rules and cleans up the orphans no project tracks. See [Keeping the account clean](docs/USAGE.md#keeping-the-account-clean).
|
|
53
|
-
|
|
54
|
-
The API key is read from the environment, a project `.env`, or `~/.gpu/credentials`. It never goes in the manifest.
|
|
55
|
-
|
|
56
|
-
## Declare
|
|
57
|
-
|
|
58
|
-
```yaml
|
|
59
|
-
gpu_runtime:
|
|
60
|
-
provider: {name: vast}
|
|
61
|
-
model_profile: qwythos-9b
|
|
62
|
-
lifecycle:
|
|
63
|
-
idle_timeout: 15m
|
|
64
|
-
max_lifetime: 6h
|
|
65
|
-
budget:
|
|
66
|
-
max_hourly_cost_usd: 1.50
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
The file is `jambu.yaml`. This tool reads only the `gpu_runtime:` key, so other Jambu tools can share the same file. A full example is [`jambu.example.yaml`](jambu.example.yaml).
|
|
70
|
-
|
|
71
|
-
## Docs
|
|
72
|
-
|
|
73
|
-
- [Integrating into an application (any language)](docs/INTEGRATION.md)
|
|
74
|
-
- [Letting a coding agent rent GPUs (Claude Code, Cursor, Claude Desktop)](docs/AGENTS.md)
|
|
75
|
-
- [Commands, workloads, and agents](docs/USAGE.md)
|
|
76
|
-
- [Every config field](docs/CONFIG.md)
|
|
77
|
-
- [Choosing a model](docs/CHOOSING_A_MODEL.md)
|
|
78
|
-
- [How shutdown works](docs/LIFECYCLE.md)
|
|
79
|
-
- [Adding a provider](docs/ADDING_A_PROVIDER.md)
|
|
80
|
-
- [Contributing](CONTRIBUTING.md)
|
|
81
|
-
|
|
82
|
-
## License
|
|
83
|
-
|
|
84
|
-
[MIT](LICENSE).
|
|
85
|
-
|
|
86
|
-
<p align="center">
|
|
87
|
-
<a href="https://jambu.ai">
|
|
88
|
-
<img src="docs/assets/jambu-logo.jpg" alt="Jambu.ai — Applied Intelligence Lab" width="280">
|
|
89
|
-
</a>
|
|
90
|
-
</p>
|
jambu_gpu-0.4.1/README.md
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# gpu
|
|
2
|
-
|
|
3
|
-
Temporary GPU runtimes that stop billing when the work stops.
|
|
4
|
-
|
|
5
|
-
You declare the model and how long it may live. `gpu` provisions a machine, starts the model server, and runs your job against it. Shutdown happens on the instance, so closing the laptop does not leave the GPU billing.
|
|
6
|
-
|
|
7
|
-
Vast.ai is the first provider. Another provider is a new adapter. The lifecycle rule stays the same.
|
|
8
|
-
|
|
9
|
-
## Run
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pip install jambu-gpu
|
|
13
|
-
|
|
14
|
-
cd your-experiment
|
|
15
|
-
gpu init --model empero-ai/Qwythos-9B-v2
|
|
16
|
-
export VAST_API_KEY=...
|
|
17
|
-
|
|
18
|
-
gpu validate
|
|
19
|
-
gpu setup
|
|
20
|
-
gpu run python experiment.py
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
When you are finished, `gpu destroy`. If you walk away, the idle timeout does it.
|
|
24
|
-
|
|
25
|
-
From an application in any language: `gpu --json setup` returns the OpenAI-compatible `base_url`, `api_key` and `model`, and the app uses any OpenAI SDK. See [Integrating gpu into an application](docs/INTEGRATION.md), with a ready TypeScript client.
|
|
26
|
-
|
|
27
|
-
From a coding agent: `gpu agent install` gives Claude Code (or `-t cursor`, `-t desktop`) a skill and local MCP tools to rent a GPU, run the experiment and shut it down. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
|
|
28
|
-
|
|
29
|
-
As a last line of defense, `gpu ls` shows every jambu instance in the account and `gpu sweep --install-timer` sets up a systemd user timer that guards every project on the machine by its own rules and cleans up the orphans no project tracks. See [Keeping the account clean](docs/USAGE.md#keeping-the-account-clean).
|
|
30
|
-
|
|
31
|
-
The API key is read from the environment, a project `.env`, or `~/.gpu/credentials`. It never goes in the manifest.
|
|
32
|
-
|
|
33
|
-
## Declare
|
|
34
|
-
|
|
35
|
-
```yaml
|
|
36
|
-
gpu_runtime:
|
|
37
|
-
provider: {name: vast}
|
|
38
|
-
model_profile: qwythos-9b
|
|
39
|
-
lifecycle:
|
|
40
|
-
idle_timeout: 15m
|
|
41
|
-
max_lifetime: 6h
|
|
42
|
-
budget:
|
|
43
|
-
max_hourly_cost_usd: 1.50
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
The file is `jambu.yaml`. This tool reads only the `gpu_runtime:` key, so other Jambu tools can share the same file. A full example is [`jambu.example.yaml`](jambu.example.yaml).
|
|
47
|
-
|
|
48
|
-
## Docs
|
|
49
|
-
|
|
50
|
-
- [Integrating into an application (any language)](docs/INTEGRATION.md)
|
|
51
|
-
- [Letting a coding agent rent GPUs (Claude Code, Cursor, Claude Desktop)](docs/AGENTS.md)
|
|
52
|
-
- [Commands, workloads, and agents](docs/USAGE.md)
|
|
53
|
-
- [Every config field](docs/CONFIG.md)
|
|
54
|
-
- [Choosing a model](docs/CHOOSING_A_MODEL.md)
|
|
55
|
-
- [How shutdown works](docs/LIFECYCLE.md)
|
|
56
|
-
- [Adding a provider](docs/ADDING_A_PROVIDER.md)
|
|
57
|
-
- [Contributing](CONTRIBUTING.md)
|
|
58
|
-
|
|
59
|
-
## License
|
|
60
|
-
|
|
61
|
-
[MIT](LICENSE).
|
|
62
|
-
|
|
63
|
-
<p align="center">
|
|
64
|
-
<a href="https://jambu.ai">
|
|
65
|
-
<img src="docs/assets/jambu-logo.jpg" alt="Jambu.ai — Applied Intelligence Lab" width="280">
|
|
66
|
-
</a>
|
|
67
|
-
</p>
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Acceptance criteria — where each one is implemented and tested
|
|
2
|
-
|
|
3
|
-
Eighteen guarantees, mapped to the code and the test that holds each one.
|
|
4
|
-
|
|
5
|
-
| # | Guarantee | Implementation | Test |
|
|
6
|
-
| --- | --- | --- | --- |
|
|
7
|
-
| 1 | Repeated `setup` does not duplicate infrastructure | `core/engine.py::Engine.setup` reconciles first, compares `config.fingerprint()`, and holds an exclusive `.gpu/runtime.lock` | `test_engine.py::test_setup_twice_does_not_create_two_instances`, `::test_setup_refuses_to_reuse_an_incompatible_instance` |
|
|
8
|
-
| 2 | Provider APIs do not leak into core orchestration | `providers/base.py` contract; all Vast wire format confined to `providers/vast/{client,mapper}.py` | `test_engine.py::test_the_core_never_branches_on_a_provider_name` (greps `core/`) |
|
|
9
|
-
| 3 | Missing credentials fail before provisioning | `VastProvider.validate` returns before any API call; `CredentialResolver` never reads jambu.yaml | `test_vast_provider.py::test_validate_fails_before_provisioning_without_a_key` (asserts zero HTTP calls) |
|
|
10
|
-
| 4 | Failed provisioning cleans up | `Engine.setup` persists state *before* the long wait, then destroys on failure when `cleanup_on_setup_failure` | `test_engine.py::test_failed_setup_cleans_up_partially_created_resources` |
|
|
11
|
-
| 5 | Active workloads are never stopped by the idle guard | `core/policy.py::evaluate` returns `workload_active` before any idle check | `test_policy.py::test_active_workload_is_never_stopped_by_the_idle_guard`, `test_watchdog.py::test_a_heartbeating_workload_is_never_stopped` |
|
|
12
|
-
| 6 | An idle unlocked instance stops within `idle_timeout + watchdog_interval` | remote watchdog loop, `watchdog/agent_body.py::guard_loop` | `test_watchdog.py::test_idle_instance_is_stopped_with_a_recorded_reason` (real subprocess) |
|
|
13
|
-
| 7 | `max_lifetime` bounds forgotten instances | `policy.evaluate` checks age before activity | `test_policy.py::test_max_lifetime_stops_even_when_busy`, `test_watchdog.py::test_max_lifetime_stops_a_busy_instance` |
|
|
14
|
-
| 8 | Locks prevent shutdown until expiry | `policy.Snapshot.lock_active` / `lock_expired`; TTL required unless `allow_indefinite_lock` | `test_policy.py::test_expired_lock_stops_protecting`, `test_watchdog.py::test_a_lock_prevents_shutdown_until_it_expires` |
|
|
15
|
-
| 9 | A crashed CLI does not disable protection | the guard runs *on the instance*; heartbeats go stale after `heartbeat_grace` | `test_watchdog.py::test_a_vanished_heartbeat_falls_back_to_idle`, `::test_startup_grace_expires_when_the_cli_never_confirms` |
|
|
16
|
-
| 10 | Remote state is reconciled | `Engine._reconcile` merges provider truth + watchdog truth and repairs local state | `test_engine.py::test_status_repairs_state_when_the_instance_vanished` |
|
|
17
|
-
| 11 | A new provider needs only the contract | `providers/registry.py`; the engine is driven end to end by `tests/fakes.py::FakeProvider` | `test_engine.py::test_a_new_provider_needs_only_the_contract` |
|
|
18
|
-
| 12 | Every automatic shutdown records its reason | `lifecycle_guard_triggered` + `instance_stopped` events carry `reason`, locally and on the instance | `test_engine.py::test_guard_stops_an_idle_unlocked_instance_and_records_the_reason`, `test_watchdog.py::test_idle_instance_is_stopped_with_a_recorded_reason` |
|
|
19
|
-
| 13 | A restarted instance stays guarded | `Engine._reconcile` takes running/stopped from the provider; the watchdog's `terminated` flag only counts when the provider cannot tell. `RuntimeState.session_started_at` restarts on every start, and `max_lifetime` and the session budget are measured from it | `test_engine_sessions.py::test_reconcile_trusts_the_provider_over_a_stale_terminated_flag`, `::test_a_stale_terminated_flag_does_not_disable_the_guard`, `::test_restarting_a_stopped_instance_starts_a_new_session`, `::test_guard_budget_resets_with_a_new_session`; watchdog-side session tests in `test_policy.py` / `test_watchdog.py` |
|
|
20
|
-
| 14 | Traffic on the model endpoint is activity | `VllmRuntime.activity_probe` hands the watchdog vLLM's `/metrics` gauges and counters; `lifecycle.traffic_counts_as_activity` turns it off | `test_engine_sessions.py::test_the_watchdog_is_told_how_to_read_vllm_traffic`, `::test_traffic_can_be_excluded_from_activity`, `test_runtime_vllm.py::test_activity_probe_reads_vllm_metrics_on_loopback`; the watchdog's metrics-reading tests in `test_watchdog.py` |
|
|
21
|
-
| 15 | A stopped instance does not bill storage forever | `lifecycle.stopped_ttl` -> `PolicyConfig.stopped_ttl_s`; `RuntimeState.stopped_since`; `Engine.guard_tick` destroys with reason `stopped_ttl`; `validate` warns when nothing would remove it | `test_engine_sessions.py::test_guard_destroys_an_instance_stopped_longer_than_stopped_ttl`, `::test_validate_warns_when_nothing_removes_a_billing_stopped_instance`; the stopped-TTL policy tests in `test_policy.py` |
|
|
22
|
-
| 16 | Orphans are found account-wide, each project keeps its own rules, and nothing else is touched | `GPUProvider.list_instances` (capability `list_instances`) returns only `jambu-` labels; `gpu setup` registers the project in `~/.gpu/projects.json`; `core/sweep.py::guard_projects` runs each registered project's own `Engine.guard_tick`; only untracked instances are orphans: running past `--orphan-max-age` is stopped (never destroyed), stopped past `--stopped-ttl` (from the sweep's own first sighting in `~/.gpu/sweep.json`) is destroyed; `gpu sweep --install-timer` schedules one working-directory-independent unit and refuses a key that is only in the shell | `test_sweep.py::test_vast_lists_only_jambu_labelled_instances`, `::test_non_jambu_instances_are_never_touched`, `::test_another_projects_restarted_and_locked_instance_is_not_destroyed`, `::test_each_project_is_judged_by_its_own_max_lifetime`, `::test_a_running_orphan_past_orphan_max_age_is_stopped_never_destroyed`, `::test_a_stopped_orphan_is_destroyed_once_seen_stopped_past_stopped_ttl`, `::test_install_timer_writes_one_unit_independent_of_the_project`, `::test_install_timer_refuses_a_key_only_in_the_shell_environment` |
|
|
23
|
-
| 17 | The CLI guard never stops a serving endpoint it cannot see | `Engine._idle_blind`: with the watchdog enabled and installed but unreachable, `guard_tick` evaluates without `idle_timeout` and records `idle_skipped`; `max_lifetime`, budget and `stopped_ttl` still apply | `test_engine_sessions.py::test_guard_leaves_idle_to_an_unreachable_watchdog`, `::test_max_lifetime_still_applies_when_the_watchdog_is_unreachable` |
|
|
24
|
-
| 18 | A fresh stop or a short API outage never costs the disk | `RuntimeState.mark_stopped` restarts `stopped_since` on evidence the instance ran since; the watchdog escalates a failing stop to destroy only after `escalate_after_s` (30m) of continuous failure and resets on renewed use | `test_engine_sessions.py::test_a_stale_stopped_since_does_not_destroy_a_fresh_stop`, `test_watchdog.py::test_an_api_blip_does_not_destroy_the_disk`, `::test_failures_reset_when_the_instance_is_back_in_use` |
|
|
25
|
-
|
|
26
|
-
## Validated on real hardware
|
|
27
|
-
|
|
28
|
-
On 2026-10-24 the lifecycle ran end to end on Vast.ai (RTX 3090, Qwen2.5-0.5B, `idle_timeout: 4m`, `stopped_ttl: 5m`), for $0.35 of Vast credit in total across every attempt:
|
|
29
|
-
|
|
30
|
-
| What was checked | What happened |
|
|
31
|
-
| --- | --- |
|
|
32
|
-
| Traffic is activity | 10 requests sent straight to the endpoint over 7.5 minutes, none through `gpu run`. The machine stayed up past the 4-minute idle timeout. |
|
|
33
|
-
| Idle stops | 269 s after the last request the watchdog stopped its own machine (`idle_timeout`). Vast showed it `exited`. |
|
|
34
|
-
| A restart is guarded | `gpu run` restarted it. The watchdog logged `session_started` with `terminated: false` and measured `max_lifetime` from the restart. After the workload it stopped the machine again on its own. |
|
|
35
|
-
| Stopped storage is reclaimed | 334 s after the stop, `gpu guard` destroyed it (`stopped_ttl`). |
|
|
36
|
-
| Failed setups leave nothing | An image built for a newer CUDA, a model server that died at import, a host whose network dropped the weights download, a 30-minute image pull, and a Ctrl-C each ended with the instance destroyed. |
|
|
37
|
-
| Bad hosts are skipped | Hosts that could not pull the image were destroyed, excluded and replaced, with never two instances alive at once. |
|
|
38
|
-
|
|
39
|
-
The run also found what unit tests could not, and each of these is fixed and covered by a test:
|
|
40
|
-
- the boot script was over Vast's 16 KB onstart limit;
|
|
41
|
-
- `latest` had moved to CUDA 13;
|
|
42
|
-
- the `-cu129` image variant could not load torchcodec;
|
|
43
|
-
- a 15-minute boot timeout was too short for a 12 GB pull;
|
|
44
|
-
- Ctrl-C did not clean up;
|
|
45
|
-
- a fresh contract was reported `READY`.
|
|
46
|
-
|
|
47
|
-
### Application integration (0.2.0)
|
|
48
|
-
|
|
49
|
-
Driven end to end by [`examples/typescript-client`](../examples/typescript-client/) through the OpenAI SDK, on an RTX 3090 with Qwen2.5-0.5B:
|
|
50
|
-
|
|
51
|
-
| Check | Result |
|
|
52
|
-
| --- | --- |
|
|
53
|
-
| `ensureGpu()` on an empty account | Two bad hosts (weights download cut by the host's network; image pull stalled 30 minutes) were destroyed and replaced; the third served. 37 minutes in all, one instance alive at a time. |
|
|
54
|
-
| The endpoint requires its key | No key: `401`. Wrong key: `401`. The key from `gpu --json setup`: `200`. `/health` without a key: `200`. |
|
|
55
|
-
| Chat through the SDK | Answered. |
|
|
56
|
-
| Healing after a stop | `stopGpu()`, then a call on the stale connection: it failed to connect, `gpu setup` restarted the same instance, and the retry answered 41 s later with the same key. |
|
|
57
|
-
|
|
58
|
-
It found two more bugs, both fixed and tested: Vast reports a stopping instance as `running` for a few seconds, which made a setup right after a stop skip the restart; and a Vast rate limit (`429`) failed setup instead of being waited out.
|
|
59
|
-
|
|
60
|
-
## A coding agent driving it (0.4.0)
|
|
61
|
-
|
|
62
|
-
On 2026-10-25 a headless Claude Code with no prior context (`claude -p`) was given a fresh directory holding only `gpu agent install`'s skill and `.mcp.json`, plus `VAST_API_KEY` in `.env`. It was asked in one sentence to test Qwen2.5-0.5B on a rented GPU, ask it the capital of Brazil, report time and cost, and destroy the machine. It loaded the `gpu` skill by itself and called `gpu_init` (with the cap and VRAM from the request), `gpu_validate`, `gpu_offers`, then `gpu_setup` until ready, `curl` for the question, `gpu_status`, `gpu_destroy` and `gpu_ls`. The model answered "Brasília", the account was empty at the end, and the agent reported setup time and cost correctly, noting that `session_cost_usd` leaves out the replaced host.
|
|
63
|
-
|
|
64
|
-
| What was checked | What happened |
|
|
65
|
-
| --- | --- |
|
|
66
|
-
| Setup longer than any tool timeout | `gpu_setup` returned `running` every 300 s for 42 minutes and re-attached to the same background job each time: one machine at a time, never a second setup. |
|
|
67
|
-
| Bad hosts | The first host could not pull the image for 30 minutes; setup destroyed it and rented another, which served. |
|
|
68
|
-
| Cancel | Over MCP (official Python SDK client), `gpu_destroy` during a setup interrupted it like Ctrl-C: the booting machine was destroyed and `gpu_ls` came back empty, 2.3 s after the call. |
|
|
69
|
-
| Protocol | The official MCP Python SDK (2.3.0) client initializes, lists the 13 tools and calls them. |
|
|
70
|
-
|
|
71
|
-
The first attempt found what the unit tests had not, each fixed and tested:
|
|
72
|
-
- during a setup, `gpu status`, `gpu logs`, `gpu stop` and `gpu destroy` failed on the project lock (reported as "no instance"). Now `status` and `logs` report the last saved state with `busy: true`, the MCP `gpu_stop` / `gpu_destroy` cancel the setup first, and a held lock is its own exit code, 12;
|
|
73
|
-
- the host that failed last when setup gave up was not remembered as bad, so the next setup rented it again;
|
|
74
|
-
- on Vast, three hosts in a row stalled 30 minutes each pulling the 12 GB Docker Hub image, and setup gave up after 90 minutes with nothing left billing. Vast now defaults to `vastai/vllm:v0.32.0-cuda-13.0`, the same vLLM that Vast's hosts cache: in the cancel test the container was running within a minute.
|
|
75
|
-
|
|
76
|
-
## The MVP command surface
|
|
77
|
-
|
|
78
|
-
`jambu.yaml` schema, CLI, provider contract, provider registry, Vast.ai adapter,
|
|
79
|
-
vLLM runtime, `setup`, `run`, `status`, `stop`, `destroy`, `lock`/`unlock`, idle
|
|
80
|
-
timeout, max lifetime, remote watchdog, structured logs — all present. On top of
|
|
81
|
-
it, `ls` and `sweep` for account-wide orphan control.
|
|
82
|
-
|
|
83
|
-
Deliberately not implemented: multi-instance scheduling, multi-GPU
|
|
84
|
-
orchestration, Kubernetes, distributed inference, provider optimization, automatic
|
|
85
|
-
provider selection, job queues, web dashboard.
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Adding a provider
|
|
2
|
-
|
|
3
|
-
Adding a provider means adding `jambu_gpu/providers/<name>/` and one line in the
|
|
4
|
-
registry. **No lifecycle, execution or CLI code changes.** If you find yourself
|
|
5
|
-
editing `core/`, the contract is leaking.
|
|
6
|
-
|
|
7
|
-
## 1. Implement the contract
|
|
8
|
-
|
|
9
|
-
```python
|
|
10
|
-
# jambu_gpu/providers/runpod/provider.py
|
|
11
|
-
from ...core.models import ProviderCapabilities
|
|
12
|
-
from ..base import GPUProvider, WatchdogSpec
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class RunpodProvider(GPUProvider):
|
|
16
|
-
name = "runpod"
|
|
17
|
-
display_name = "RunPod"
|
|
18
|
-
required_credentials = ["RUNPOD_API_KEY"]
|
|
19
|
-
capabilities = ProviderCapabilities(
|
|
20
|
-
stop=True,
|
|
21
|
-
destroy=True,
|
|
22
|
-
ssh=True,
|
|
23
|
-
public_ports=True,
|
|
24
|
-
gpu_selection=True,
|
|
25
|
-
pricing_query=True,
|
|
26
|
-
remote_exec=True,
|
|
27
|
-
logs=True,
|
|
28
|
-
stop_eliminates_billing=True,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
def validate(self, config): ...
|
|
32
|
-
def setup(self, spec): ...
|
|
33
|
-
def start(self, instance): ...
|
|
34
|
-
def run(self, instance, workload): ...
|
|
35
|
-
def status(self, instance): ...
|
|
36
|
-
def stop(self, instance): ...
|
|
37
|
-
def destroy(self, instance): ...
|
|
38
|
-
def logs(self, instance): ...
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Suggested layout, mirroring the Vast adapter:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
providers/runpod/
|
|
45
|
-
├── provider.py the contract implementation
|
|
46
|
-
├── client.py raw HTTP; the ONLY place that knows the wire format
|
|
47
|
-
├── mapper.py payloads <-> normalized models, state mapping
|
|
48
|
-
└── config.py provider.options schema and constants
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 2. Declare capabilities honestly
|
|
52
|
-
|
|
53
|
-
The core never branches on `provider.name` — only on `provider.capabilities`. Set
|
|
54
|
-
`stop_eliminates_billing=False` if a stopped instance still costs money; the policy
|
|
55
|
-
then knows it may need to escalate to destroy, and `lifecycle.stopped_ttl` applies.
|
|
56
|
-
|
|
57
|
-
Set `list_instances=True` and implement `list_instances()` if the API can enumerate
|
|
58
|
-
the account. Return only instances whose label starts with `JAMBU_LABEL_PREFIX`
|
|
59
|
-
(`core/models.py`); `gpu ls` and `gpu sweep` are built on it and stay unavailable
|
|
60
|
-
otherwise.
|
|
61
|
-
|
|
62
|
-
## 3. Normalize state and errors
|
|
63
|
-
|
|
64
|
-
Map every provider status onto `InstanceState`
|
|
65
|
-
(`provisioning / starting / ready / busy / idle / stopped / failed / destroyed`),
|
|
66
|
-
and raise only the normalized errors from `core/errors.py`:
|
|
67
|
-
`AuthenticationError`, `CapacityUnavailableError`, `ProvisioningError`,
|
|
68
|
-
`ProviderTimeoutError`, `UnsupportedCapabilityError`. CLI behaviour must be
|
|
69
|
-
identical across providers.
|
|
70
|
-
|
|
71
|
-
Report a machine that will not boot as `failed` rather than leaving it in
|
|
72
|
-
`starting`: `setup` then destroys it at once and rents another machine
|
|
73
|
-
(`compute.host_attempts`). Set `Offer.host_id` and `Instance.host_id` to the
|
|
74
|
-
physical machine when the API exposes it, and leave out offers whose `host_id` is
|
|
75
|
-
in `ComputeSpec.exclude_hosts`, or `setup` may rent the same bad machine again.
|
|
76
|
-
|
|
77
|
-
## 4. Supply the watchdog actions
|
|
78
|
-
|
|
79
|
-
The lifecycle *policy* is the core's; the adapter contributes only the two calls
|
|
80
|
-
that shut the machine down from inside itself:
|
|
81
|
-
|
|
82
|
-
```python
|
|
83
|
-
WATCHDOG_ACTIONS = '''
|
|
84
|
-
def provider_stop(ctx):
|
|
85
|
-
... # stdlib only: urllib, json, os
|
|
86
|
-
|
|
87
|
-
def provider_destroy(ctx):
|
|
88
|
-
...
|
|
89
|
-
'''
|
|
90
|
-
|
|
91
|
-
def watchdog_spec(self, spec_or_instance=None):
|
|
92
|
-
return WatchdogSpec(
|
|
93
|
-
actions_source=WATCHDOG_ACTIONS,
|
|
94
|
-
env={"RUNPOD_API_KEY": self.credentials.get("RUNPOD_API_KEY") or ""},
|
|
95
|
-
context={"provider": self.name},
|
|
96
|
-
)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The instance id may not exist when the boot script is generated. Either resolve it
|
|
100
|
-
from the container's own environment (as the Vast adapter does with
|
|
101
|
-
`VAST_CONTAINERLABEL`), or rely on the CLI's `/identify` call — support both if you
|
|
102
|
-
can, so the guard works even if the CLI never reconnects.
|
|
103
|
-
|
|
104
|
-
Return `None` only if the provider genuinely cannot self-terminate; users then have
|
|
105
|
-
to run `gpu guard` externally, and `validate` will warn.
|
|
106
|
-
|
|
107
|
-
## 5. Publish the ports
|
|
108
|
-
|
|
109
|
-
`ComputeSpec.ports` lists the container ports the core needs published. Map them to
|
|
110
|
-
`Instance.endpoints` under the names `model` and `watchdog`. Without a published
|
|
111
|
-
watchdog port the remote guard is unreachable.
|
|
112
|
-
|
|
113
|
-
## 6. Register it
|
|
114
|
-
|
|
115
|
-
```python
|
|
116
|
-
# providers/registry.py
|
|
117
|
-
def load_builtin_providers():
|
|
118
|
-
from .vast.provider import VastProvider
|
|
119
|
-
from .runpod.provider import RunpodProvider
|
|
120
|
-
|
|
121
|
-
registry.register(VastProvider)
|
|
122
|
-
registry.register(RunpodProvider)
|
|
123
|
-
return registry
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## 7. Test it
|
|
127
|
-
|
|
128
|
-
Copy `tests/test_vast_provider.py` and swap the mocked payloads. Then check the
|
|
129
|
-
adapter really is swappable — `tests/test_engine.py` drives the whole engine
|
|
130
|
-
through `tests/fakes.py::FakeProvider` with zero core changes, and
|
|
131
|
-
`test_the_core_never_branches_on_a_provider_name` fails the build if `core/` learns
|
|
132
|
-
a provider's name.
|
jambu_gpu-0.4.1/docs/AGENTS.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Letting a coding agent rent GPUs
|
|
2
|
-
|
|
3
|
-
`gpu` ships a skill and a local MCP server, so Claude Code, Cursor or Claude Desktop can rent a GPU, serve a model, run an experiment against it and shut it down, from a request like "compare Qwen 7B and Llama 8B on these 50 prompts".
|
|
4
|
-
|
|
5
|
-
The watchdog on the machine is what makes this safe to hand to an agent: a machine the agent forgets, or a conversation that ends mid-experiment, stops by itself after `lifecycle.idle_timeout`.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pipx install jambu-gpu # or: uv tool install jambu-gpu
|
|
11
|
-
cd your-project
|
|
12
|
-
echo "VAST_API_KEY=..." >> .env # or ~/.gpu/credentials; never in jambu.yaml
|
|
13
|
-
gpu agent install # Claude Code
|
|
14
|
-
gpu agent install -t cursor # Cursor
|
|
15
|
-
gpu agent install -t desktop # Claude Desktop
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
| Target | Skill | MCP server |
|
|
19
|
-
| --- | --- | --- |
|
|
20
|
-
| `claude` (default) | `.claude/skills/gpu/SKILL.md` | `.mcp.json` (Claude Code asks once to approve it) |
|
|
21
|
-
| `cursor` | the same `.claude/skills/gpu/` (Cursor reads Claude's skill directories) | `.cursor/mcp.json`, with `--project ${workspaceFolder}` |
|
|
22
|
-
| `desktop` | `~/.gpu/gpu-skill.zip`, to upload under Settings > Capabilities > Skills | `claude_desktop_config.json`, pinned to this project |
|
|
23
|
-
|
|
24
|
-
`--global` installs for every project of the user instead: the skill in `~/.claude/skills/gpu/`, the MCP server through `claude mcp add --scope user` and in `~/.cursor/mcp.json`. Existing config files are merged; only the `gpu` entry is written. Re-running is safe.
|
|
25
|
-
|
|
26
|
-
Claude Desktop and Cursor do not get your shell's environment. Keep `VAST_API_KEY` in the project `.env` or `~/.gpu/credentials`, not only in an `export`.
|
|
27
|
-
|
|
28
|
-
Another agent that reads `SKILL.md` files: `gpu agent skill > path/to/skills/gpu/SKILL.md`. Any MCP client: run `gpu mcp --project /path/to/project` over stdio.
|
|
29
|
-
|
|
30
|
-
## The MCP tools
|
|
31
|
-
|
|
32
|
-
| Tool | Does | Costs money |
|
|
33
|
-
| --- | --- | --- |
|
|
34
|
-
| `gpu_init` | write `jambu.yaml` for a model, with an optional price cap and VRAM | no |
|
|
35
|
-
| `gpu_validate` | check the config and credentials | no |
|
|
36
|
-
| `gpu_offers` | cheapest matching machines and their price | no |
|
|
37
|
-
| `gpu_setup` | rent / restart and wait for the model; returns `base_url`, `api_key`, `model` | yes |
|
|
38
|
-
| `gpu_status` | state, cost so far, idle time, the watchdog's next move, setup progress | no |
|
|
39
|
-
| `gpu_connection` | `base_url`, `api_key`, `model` without starting anything | no |
|
|
40
|
-
| `gpu_run` | run a local command with `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `JAMBU_MODEL_ID` set | yes, if it has to start the machine |
|
|
41
|
-
| `gpu_logs` | runtime, onstart, watchdog or provider log | no |
|
|
42
|
-
| `gpu_lock` / `gpu_unlock` | keep the machine up through a quiet period | while locked |
|
|
43
|
-
| `gpu_stop` | stop now; disk kept, restart takes about a minute | ends billing for compute |
|
|
44
|
-
| `gpu_destroy` | delete; needs `confirm=true` | ends all billing |
|
|
45
|
-
| `gpu_ls` | every machine in the account, from any project | no |
|
|
46
|
-
|
|
47
|
-
Each tool runs the `gpu` CLI with `--json` in the project directory, so errors carry the same `exit_code` as the [application contract](INTEGRATION.md#exit-codes), plus `meaning` and `next` for the agent. A tool takes an optional `project_dir`; by default it is the server's `--project`, `$GPU_PROJECT_DIR`, or the directory the client started it in.
|
|
48
|
-
|
|
49
|
-
`gpu_setup` would outlast any client's tool timeout on a first rent (5–15 minutes). It starts setup as a background job, waits up to `wait_seconds` (45 by default), and reports `status: "running"` until the model answers. Calling it again re-attaches to the same job; it never starts a second machine. The job outlives the MCP server, so a client restarted mid-setup finds it again through `gpu_setup` or `gpu_status`, which answers during setup with `"busy": true` and the job's progress. `gpu_stop` and `gpu_destroy` cancel a setup in progress the way Ctrl-C does: the machine still booting is destroyed.
|
|
50
|
-
|
|
51
|
-
## What keeps the agent in line
|
|
52
|
-
|
|
53
|
-
- **The skill** tells the agent to show the price and ask before renting, wait out a slow setup instead of starting over, never touch `VAST_API_KEY`, and offer stop or destroy (and check `gpu_ls`) when done.
|
|
54
|
-
- **The server's tool annotations** mark `gpu_destroy` destructive and the read-only tools read-only, so a client can let `gpu_status` run freely and ask for every `gpu_setup` and `gpu_destroy`. In Claude Code that is the `permissions` list in `.claude/settings.json`, e.g. `"allow": ["mcp__gpu__gpu_status", "mcp__gpu__gpu_offers", "mcp__gpu__gpu_ls"]`.
|
|
55
|
-
- **`jambu.yaml`** holds the hard limits the agent cannot exceed without editing the file: `budget.max_hourly_cost_usd`, `budget.max_session_cost_usd`, `lifecycle.max_lifetime`.
|
|
56
|
-
- **The watchdog** stops the machine after `idle_timeout` whatever the agent does, and `gpu sweep --install-timer` catches anything left behind.
|