Metadata-Version: 2.4
Name: openroboto
Version: 1.4.0
Summary: OpenRoboto subnet (Bittensor netuid 80) CLI — everything a miner or external validator types.
Keywords: bittensor,openroboto,subnet,miner,cli
Author: OpenRoboto
Author-email: OpenRoboto <cameron@moonshotcommons.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Dist: openroboto-protocol==0.11.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: bittensor>=10.5,<11
Requires-Dist: huggingface-hub>=0.24.0
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/openroboto-ai/openroboto-cli
Project-URL: Issues, https://github.com/openroboto-ai/openroboto-cli/issues
Description-Content-Type: text/markdown

# openroboto

The command-line tool for mining on **OpenRoboto**, a Bittensor mainnet subnet
(netuid 80) that rewards improvements to vision-language-action models.

You fine-tune the current season's base model — LingBot-VLA 2.0 today — publish the
checkpoint to Hugging Face, pay a small on-chain evaluation fee, and announce it. The subnet evaluates every submission in
simulation with a seed nobody can predict, ranks the results, and pays emissions by
rank.

Everything you type is this one package. **There is nothing to clone.**

```bash
pip install openroboto
```

[Docs index](docs/README.md) · [How the subnet works](docs/SUBNET_OVERVIEW.md) ·
[This season's base model](docs/MIGRATION.md) ·
[Evaluation toolkit](https://github.com/openroboto-ai/openroboto-evaluation)

Real-robot documentation: [parallel tracks](docs/REAL_TRACKS.md) ·
[miner preparation](docs/MINER_REAL.md) · [workstation and reference data](docs/WORKSTATION_DATA.md).
These pages describe π0.5 and LingBot as parallel physical tracks and publish the
confirmed joint-space interface. Publication does not announce a new competition
as open.

---

## Requirements

- Linux, an NVIDIA GPU (24 GB VRAM minimum) and a recent driver
- Python **3.11**
- Docker with the NVIDIA Container Toolkit — training runs in a container because
  openpi needs `numpy<2.0` and bittensor needs `numpy>=2.0`; one interpreter cannot
  hold both
- A registered Bittensor mainnet hotkey with enough TAO for the evaluation fee
- A Hugging Face account and a write token

## Your first submission

```bash
# 1. Install and scaffold
pip install openroboto
openroboto init my-miner          # a ready-to-use workspace: miner.yaml,
                                  # train_strategy.py, README.md, .gitignore
cd my-miner
$EDITOR miner.yaml                # hotkey_ss58, HF token + username

# 2. Check everything BEFORE anything costs money
openroboto doctor                 # config, season + fee, HF permissions, balance, Docker, GPU

# 3. Build the training image, then train
openroboto build
openroboto train

# 4. Verify the checkpoint format — still free
openroboto check

# 5. Upload, pay the fee, announce on chain
openroboto submit

# 6. See what the subnet made of it
openroboto status
```

Steps 2 and 4 exist for one reason: **entry fees are not refundable.** The most expensive
mistake on this subnet is discovering after paying that the upload was not something
the evaluator can load — a bare LoRA adapter, or a checkpoint buried in a
subdirectory. `doctor` and `check` are free and catch both.

Full walkthrough: [docs/MINER.md](docs/MINER.md).
Real-machine setup, systemd, custom strategies: [docs/MINER_DEPLOY.md](docs/MINER_DEPLOY.md).

## Commands

| Command | What it does |
|---|---|
| `openroboto init [DIR] [-s simple\|example] [--validator] [--refresh] [--backend-url URL] [--force]` | Create a working workspace: config, a training strategy to edit, a README with the exact next commands, and a `.gitignore` that keeps your wallet password out of git. ⚠️ **This is the one command that needs the network**: the open competitions come from `GET /api/v1/competitions`, and if that cannot be reached it writes **no file at all** rather than guessing a season. `--refresh` rewrites only the `competition:` section of an existing `miner.yaml`; `--backend-url` says which backend to ask (and the whole workspace is then written to match it); `--force` overwrites existing files |
| `openroboto doctor` | Environment check: Python, config, the season's own spec, Docker, GPU, image, HF token, wallet balance against **this season's** fee |
| `openroboto build` | Build the training image from the build context shipped inside the package (no clone, no network). Refuses for a competition this client has no image for, rather than filling that competition's image name with the π0.5 one |
| `openroboto train [-s script.py]` | Train once; your strategy script is mounted into the container |
| `openroboto check [PATH]` | Verify checkpoint layout with the rules the evaluator uses — **no GPU, no network, no second repository** |
| `openroboto submit [--config] [--output-dir] [--force]` | Upload → re-check the season against the backend → check the repository layout → pay the entry fee → announce on chain. Resumable from `state/competition_<id>.json`: an upload already done is not repeated and a fee already paid is not paid again |
| `openroboto status [--hotkey] [--competition]` | Submission history and scanner rejection reasons (no API key needed) |
| `openroboto validator run` | External validator: read published weights, set them on chain |
| `openroboto --version` | CLI version and protocol package version |

⚠️ **There is no `openroboto merge`, and none is planned.** Exporting a full
checkpoint is part of **training** — it needs the model libraries, which cannot live
in the same interpreter as `bittensor` — so it belongs in your training script,
inside the training container. The bundled strategies leave that step blank on
purpose and say so; `openroboto train` tells you what the run actually produced, and
`openroboto check` gives the verdict **before you pay**.

⚠️ **`openroboto submit` checks the layout too, and it is not optional.** Between
the upload and the payment it reads the file listing of your HuggingFace
repository — the same listing the subnet reads *after* the fee — and stops
without paying anything if that listing would not earn a score. There is no flag
to skip it, because past that point a rejection is final and the TAO is not
refunded. If the listing cannot be fetched at all, it also stops: an answer we
could not get is not one this will spend your money on. Run `openroboto check`
first anyway — it is free, it runs before the multi-gigabyte upload, and it
checks two more rules that need the weight index on your disk.

## Things that will cost you TAO if you skip them

The fee is published on the competition you are entering (`params.fee`), and the
simulation and real-hardware seasons do not charge the same thing, which is why no
figure belongs in your config — or in this file. Never hard-code it:
`openroboto submit` confirms it against the backend in the moment before paying,
and **refuses to pay at all** if it cannot, rather than reaching for an amount
that says how much but not which competition.

**`submit` is the only command that pays, and that is on purpose.** The backend only
accepts a submission whose payment is within **50 blocks (~10 minutes)** of the chain
commitment; this stops a single fee being reused across submissions. Past that window
the submission is rejected and the fee is gone. `openroboto submit` runs upload,
payment and announcement back-to-back so you stay inside it, and it refuses to
publish once the window has closed instead of charging you a second fee for a doomed
submission. `upload` / `burn` / `announce` were removed as separate commands in 1.0
for exactly this reason: running one alone is how a fee gets paid for a submission
that is never announced.

Details: [docs/PAYMENT.md](docs/PAYMENT.md).

## Submission format

The evaluator accepts **complete model checkpoints** — an openpi JAX `params/`
directory or a PyTorch `model.safetensors`, plus
`assets/physical-intelligence/libero/norm_stats.json`. A bare LoRA adapter is rejected
by a CPU pre-check before any GPU time is spent.

Put them at the **top** of the directory you upload. The evaluator descends two
levels looking for the weights and stops; the LingBot exporter's own layout,
`checkpoints/global_step_N/hf_ckpt/`, is one level past that, so an unedited upload
of a training output is admitted and then never loaded.

Exact requirements: [docs/SUBNET_OVERVIEW.md](docs/SUBNET_OVERVIEW.md).

## Verify your evaluation seed

Your seed is derived from three public values, two of which did not exist when you
submitted — the block hash that carried your commitment, the **competition id** your
submission was admitted to, and a drand beacon value. Nobody, including the subnet
operator, can pick a seed for a specific miner. You can recompute it:

```bash
pip install openroboto-protocol
```

```python
from openroboto_protocol.seed import derive_seed

# The middle argument is the competition id, not the payload's `r` and not the
# season ordinal the API displays. It is passed by position.
seed = derive_seed(block_hash, competition_id, drand_randomness)
```

The backend and this CLI import that exact function — not a copy of it.
Formula, drand chain identifier and security assumptions:
[docs/SEED_GENERATION.md](docs/SEED_GENERATION.md).

## Docker: what runs where

Training **always** runs in a container — openpi needs `numpy<2.0` and bittensor
needs `numpy>=2.0`, so one interpreter cannot hold both. You do not have to
arrange that:

```bash
openroboto build     # builds the training image, once
openroboto train     # starts it for you, data and strategy mounted in
```

The training image definition ships **inside the package**. There is nothing to
clone, nothing to keep in sync, and no network needed to build it. You do need
Docker on the host.

The package ships **two** image definitions, one per base model: `runner/`
(openpi, π0.5) and `runner/lingbot/` (LingBot-VLA 2.0). Which one is used follows
`competition.base_model_family`, never the adapter name. A competition whose base
model has no image here is refused by `build` and `train` — they stop rather than
build another base model's image under that competition's name. Train such a season
your own way, then come back for `openroboto check` and `openroboto submit`, which
both work on a checkpoint this CLI did not produce.

### Running the CLI in a container too (optional, needs a clone)

Separately, the `Dockerfile` and `docker-compose.yml` **in this repository** put
the CLI itself in a container, for people who would rather not install it into
their host Python. That is a repository-level convenience: `openroboto init` does
**not** write these files into your workspace, because a compose file that builds
the CLI image would need this repository's build context anyway.

```bash
git clone https://github.com/openroboto-ai/openroboto-cli && cd openroboto-cli
docker compose up train
docker compose run --rm train submit --config miner.yaml
```

There is deliberately no `submit` service — a compose service can be restarted, and
restarting a command that spends a non-refundable entry fee is not something to leave
to a restart policy.

⚠️ This compose file mounts the Docker socket so the containerised CLI can start
the training container. That grants host root to the container; run it only on
your own machine. Installing with `pip` avoids that entirely, which is why it is
the documented path.

## Public trust boundary

Public: miner participation, local training, Hugging Face upload, fee payment and
chain announcement; chain commitment formats and weight-setting logic; evaluation rules,
baseline methodology, LIBERO tooling and seed derivation; and the read-only API
contract. (`control.json` is documented for external validators, who fetch
`public_key` from it — no miner command reads it.)

Not here: held-out task data, the scoring-service deployment, and subnet-owner
operational tooling.

Seed derivation is public precisely because publishing it gives nothing away — the
future block hash and drand value do not exist at submission time.

## Development

For contributors. Skip this if you installed from PyPI.

```bash
git clone https://github.com/openroboto-ai/openroboto-cli
cd openroboto-cli
uv sync --locked
```

One repository is enough: `openroboto-protocol` is installed from PyPI at the exact
version pinned in `pyproject.toml`. To work against unreleased protocol changes,
override it in your environment only —
`uv pip install -e ../openroboto-protocol` — and do not commit a
`[tool.uv.sources]` path entry. A path source **bypasses the version constraint**,
A path source that disagrees with the pin leaves every local and CI run green while
resolving a different version than the one published.

`--locked` is deliberate: it fails when `uv.lock` does not match `pyproject.toml`
instead of silently resolving a different dependency tree. The interpreter is pinned to
Python 3.11 by `.python-version` — the version miners run.

```bash
bash scripts/lint.sh                             # mypy + ruff check + ruff format
uv run pytest -q                                 # no GPU, no chain, no network
uv run coverage run --source=src -m pytest -q
uv run coverage report                           # fails below the threshold in pyproject.toml
uvx pre-commit install                           # optional: the same lint on every commit
```

`.github/workflows/ci.yml` runs these same commands — `scripts/lint.sh` is the single
definition of "lint", so local and CI cannot drift. CI also fails on any skipped test:
nothing here needs hardware or credentials, so a skip means a test was switched off.

Protocol constants (commitment encoding, seed derivation, shared vocabularies) come
from `openroboto-protocol` and are never copied in here.
`.github/workflows/protocol-guards.yml` enforces that and that the dependency is pinned
to an exact version — a floating range would let the miner side and the backend side
resolve different code, which is the one thing that package exists to prevent.

## Releasing

```bash
git tag v1.0.1   && git push origin v1.0.1      # stable
git tag v1.1.0a1 && git push origin v1.1.0a1    # pre-release, for testing
```

The tag triggers the same gates every pull request runs, and the `pypi`
environment only accepts `v*` tags, so a stray branch build cannot reach the
index. It then waits for a reviewer to approve that environment: uploads cannot
be undone and a version number can never be reused, so that approval is the last
thing standing between a typo and every miner.

`pip install openroboto` resolves to the newest **stable** release. A tester
takes a pre-release by pinning it exactly (`pip install openroboto==1.1.0a1`);
no miner lands on one by accident.

## Repository map

| Path | Purpose |
|---|---|
| `src/openroboto/` | The package: `commands/`, `chain/`, `huggingface/`, `payment/`, `config/`, `training/`, `templates/` |
| `src/openroboto/runner/`, `src/openroboto/runner/lingbot/` | One training-image build context per base model (Dockerfile + the in-container entry script), **both shipped in the wheel** so `openroboto build` works offline |
| `docs/` | Miner, validator and reproducibility documentation — index at [docs/README.md](docs/README.md) |
| `tests/` | Mirrors `src/`; needs no GPU, chain or network |
| `Dockerfile`, `docker-compose.yml` | Optional containerised way to run the CLI |

Local configuration, runtime state, logs, caches and model weights are excluded by
`.gitignore`.

## License

See [LICENSE](LICENSE).
