Status: alpha (v0.1.0-alpha.1). The API, CLI, schemas, and wire protocols are still subject to change without notice. See
docs/for the design refs.
You train a robot with a multi-agent brain by describing a mission in YAML — an
embodiment to operate, agents with models for execution, tasks with a dataset to
train on, an evaluation benchmark to score against — and odyssey run walks it
through the full lifecycle: load → validate → execute training tasks → execute
the evaluation task → persist results. Local-mode by default; the hosted Lovell
AI services (leaderboard, learning graph, hosted runners) are optional layers
that land in later releases.
[!TIP] Linux only — install build dependencies before proceeding (needed by
.[all]):sudo apt update && sudo apt install build-essential python3-dev -y
git clone https://github.com/lovellai-dev/odyssey.git
cd odyssey
python3 -m venv .venv
source .venv/bin/activate
pip install -e . # CLI, validate, mock runs (lightweight)
pip install -e ".[all]" # real training + evaluation (torch, robosuite…)
pip install -e ".[all,dev]" # + pytest, ruff, mypy
The base install pulls in pydantic, click, pyyaml, and aiosqlite — enough to
run validate, list, status, and run --use-mock-runner against any
mission spec without a GPU. .[all] adds everything needed for real training
and evaluation runs.
# Validate the mission spec
$ odyssey validate examples/quickstart-openvla/mission.yaml
OK examples/quickstart-openvla/mission.yaml
spec version : 0.1
tasks : 1 training, 1 evaluation
# Run the full mission with a CPU mock (no GPU needed)
$ odyssey run examples/quickstart-openvla/mission.yaml --use-mock-runner
...
{"ts": "...", "event": "mission.completed", "overall_grade": 1.0}
COMPLETED c1756bad855e45cc9a95b5b0566c948b
overall_grade : 1.000
--use-mock-runner swaps in the CPU mock for every task, so this works on a
laptop without a GPU. Inspect runs afterward with odyssey list and
odyssey status <id>. State is persisted to ~/.odyssey/missions.db;
artifacts under ~/.odyssey/runs/<mission-id>/<task-id>/.
Two training paths ship today: GR00T (NVIDIA Isaac GR00T) and OpenVLA.
Both run through odyssey run <mission.yaml> — pick the quickstart that matches
the agent's model.
Fine-tunes nvidia/GR00T-N1.7-3B on the LeRobot-format demo set that ships
inside the Isaac-GR00T repo (no separate download), evaluated in the Isaac Lab
cube-lift environment.
Prerequisites:
Run
bash examples/quickstart-gr00t/setup.sh— it builds the three eval venvs (Odyssey core, GR00T server, Isaac Lab client transport) with uv. Seeexamples/quickstart-gr00t/README.mdfor the full walkthrough and why they're separate venvs by design.
Isaac Sim + Isaac Lab are a separate, heavy precondition — a large NVIDIA
Omniverse binary installed by NVIDIA's own tooling, not by setup.sh (the
script wires the eval client into it, and skips with a note if it's absent).
Provision them once following the
quickstart-gr00t README, then point
Odyssey at your checkouts:
export ISAAC_GR00T_REPO_PATH=/srv/Isaac-GR00T # GR00T checkout + demo dataset
export ISAACLAB_PATH=$HOME/IsaacLab # provides isaaclab.sh
Run:
odyssey run examples/quickstart-gr00t/mission.yaml
The mission routes its training task to the GR00T runner with
config: { runner: gr00t } — OpenVLA and GR00T both serve wildcard training
tasks, so the family is selected explicitly.
Prerequisites:
pip install -e ".[huggingface,openvla,robosuite]"
draccus and the fine-tuning script):
git clone https://github.com/openvla/openvla.git /srv/openvla
pip install -e /srv/openvla
export OPENVLA_REPO_PATH=/srv/openvla
wget -r -nH --cut-dirs=4 --reject="index.html*" \
https://rail.eecs.berkeley.edu/datasets/bridge_release/data/tfds/bridge_dataset/
mv bridge_dataset bridge_orig
Set --data_root_dir to the parent directory containing bridge_orig/.Run:
odyssey run examples/quickstart-openvla/mission.yaml
Hardware: 24 GB GPU (RTX 4090-class or better) for the OpenVLA LoRA fine-tune.
[!NOTE] GCP users: single-GPU VMs require
export NCCL_NET=Socketbefore running, to bypass Google's NCCL plugin. See issue #5 for details.
[!NOTE] Evaluation: the Robosuite runner auto-wires an OpenVLA→robosuite-action adapter (
make_openvla_policyinrunners/models/openvla.py) when no custompolicy_factoryis injected — it loads either a LoRA adapter or a full merged checkpoint, so eval works without extra glue. Full episode-completion validation on a real GPU is still in progress.
The fine-tune runs through the cloned OpenVLA repo, which carries its own
dependency set — most onboarding friction comes from there, not from Odyssey.
Mixing versions surfaces as protobuf / TensorFlow / tensorflow-metadata
conflicts or draccus import errors. Known-good versions (from OpenVLA's own
requirements — treat its repo as the source of truth):
Python 3.10
torch 2.2.0
torchvision 0.17.0
transformers 4.40.1
tokenizers 0.19.1
timm 0.9.10
flash-attn 2.5.5
To avoid re-downloading the 7B base model each run, point its path env var at a
local copy (HF id upper-cased, / and - → _, suffixed _PATH):
export OPENVLA_OPENVLA_7B_PATH=/path/to/openvla-7b # for base: openvla/openvla-7b
source: oxe / ref: bridge_orig resolvesOdyssey does not download the dataset — oxe is a pass-through. The runner
forwards two values to OpenVLA's finetune.py, which loads via TFDS/RLDS:
| mission.yaml | becomes the flag | meaning |
|---|---|---|
dataset.ref: bridge_orig | --dataset_name bridge_orig | the OXE registry key OpenVLA looks up |
config.data_root_dir: <path> | --data_root_dir <path> | the parent dir containing the RLDS dataset folder |
⚠️ Naming gotcha: the registry key and the on-disk folder name can differ.
In validation, ref: bridge_orig resolved to data under ~/bridge_dataset/1.0.0/,
so data_root_dir had to point at the parent of that folder — not the key
name. Check where your download actually landed and set data_root_dir to its
parent.
OpenVLA's finetune.py calls wandb.init() unconditionally, so a run stalls or
fails if W&B isn't reachable. Control it yourself:
# Disable for local / smoke runs:
export WANDB_MODE=disabled
# Or log to your account, then pass project/entity via mission config:
# config: { wandb_project: my-project, wandb_entity: my-entity }
Any config: key Odyssey doesn't consume is forwarded verbatim as
--<key> <value> to finetune.py.
Timing varies widely with hardware, disk, and network — treat these as orientation, not promises:
openvla-7b (~14 GB) on first run, unless
OPENVLA_OPENVLA_7B_PATH is set.it/s (~1.49 it/s on an NVIDIA L4 for
the quickstart config).If a stage seems stuck, it's almost always a download in progress or a dataset-path / W&B issue rather than a training bug — check those first.
The models pulled from the Hub are gated — you must accept each model's
license on its HuggingFace page, then authenticate on the machine before the
first run, or the download fails with 401/403:
openvla/openvla-7b — the PILOTgoogle/gemma-4-E2B-it — the
SPECIALIST in the multi-agent example (Apache-2.0, no gating)huggingface-cli login # paste a token from https://huggingface.co/settings/tokens
# or, non-interactive (CI / headless VM):
export HF_TOKEN=hf_xxx # a read token on an account that accepted the licenses
A mission with a SPECIALIST agent (a task planner) in addition to the PILOT runs a plan-then-execute loop during eval: the SPECIALIST decomposes the instruction into sub-steps once per episode, and the PILOT executes each. Only the PILOT produces actions and only the PILOT is trained — the SPECIALIST is inference-only (it runs its base checkpoint to plan and has no training task).
robot:
agents:
- id: pilot
role: PILOT
model: { source: huggingface, base: openvla/openvla-7b }
- id: task-planner
role: SPECIALIST
model:
source: huggingface
base: google/gemma-4-E2B-it
quantization: int4
modality: multimodal
The SPECIALIST is a vision-grounded multimodal Gemma 4 planner: it sees the
first camera frame of each episode and grounds its plan in the scene. Gemma 4
needs a modern transformers + torchvision, which conflicts with OpenVLA's
pinned transformers==4.40.1, so the SPECIALIST must run out of process in a
separate venv. The PILOT stays in the main venv; the two talk over a JSON-lines
subprocess protocol (the planner runs once per episode, off the per-step hot loop).
setup.sh)Multi-agent needs two venvs — env_pilot (the OpenVLA pilot + Robosuite
eval) and env_specialist (the Gemma 4 planner) — because their transformers
versions are mutually incompatible. The setup.sh script builds both
idempotently, each pinned to its known-good stack, clones the upstream OpenVLA repo,
and writes a sourceable .env:
# from the repo root — builds env_pilot + env_specialist, clones OpenVLA, writes .env
examples/multiagent-openvla-gemma/setup.sh
# load the active venv + env vars (incl. ODYSSEY_SPECIALIST_PYTHON) before each run
source examples/multiagent-openvla-gemma/.env
📖 Full GCP walkthrough (provisioning, dataset download, troubleshooting): the multi-agent GCP tutorial. For the simpler single-agent (OpenVLA-only) path, see the training tutorial.
The role-based names keep the two transformers stacks straight: env_pilot
holds OpenVLA (pinned transformers==4.40.1), env_specialist the Gemma planner
(modern transformers + torchvision).
# env_specialist — the Gemma planner (env_pilot is the OpenVLA install)
python -m venv env_specialist
env_specialist/bin/pip install -e ".[specialist]" \
-c constraints/specialist-known-good.txt
# point Odyssey at that venv's python (read per-process; export it in every shell
# that runs a mission, or persist it in your shell profile / VM startup script)
export ODYSSEY_SPECIALIST_PYTHON="$PWD/env_specialist/bin/python"
ODYSSEY_SPECIALIST_PYTHONis required for any mission with a SPECIALIST. The planner is launched in that venv (RemotePlanner→python -m odyssey.runners.agents.planner_server). If it is unset, multi-agent eval fails fast with a clearRuntimeError: the multimodal Gemma 4 planner cannot load in the main venv, which pinstransformers==4.40.1for OpenVLA.
Quick check without a simulator (launches the planner in the specialist venv and prints a decomposition — no OpenVLA or simulator needed):
python tests/manual/smoke_remote_planner.py
Why Gemma 4, not Gemma 3, for multimodal. Gemma 3 4B emits NaN logits under int4 bitsandbytes on this stack (verified across eager/sdpa attention, text-only and with-image), so it can't run quantized here. Gemma 4 (Apache-2.0, ungated) loads cleanly in int4 and grounds plans in the scene image.
VRAM note. Both models still share the GPU — the venv split solves the dependency conflict, not VRAM. The SPECIALIST is pinned to GPU 0 (
device_map={"": 0}) so bitsandbytes never silently offloads layers to CPU. Gemma 4 E4B-it int4 (~9.3 GB) alongside bf16 OpenVLA (~14 GB) peaks at ~23 GB — tight on a 24 GB L4; drop to E2B-it for headroom (this is what the multimodal example mission uses).
Two known-good stacks. The main venv pins OpenVLA's stack (
constraints/openvla-known-good.txt: torch 2.2.0, transformers 4.40.1); the specialist venv pins a modern one with torchvision (constraints/specialist-known-good.txt). They no longer need to be mutually compatible.
| Command | What it does |
|---|---|
odyssey init [DIR] | Scaffold a new mission directory. --template openvla|cpu_mock. |
odyssey validate <mission.yaml> | Parse + validate a spec. Exits 0 if clean. |
odyssey run <mission.yaml> | Execute end-to-end. --use-mock-runner for no-GPU smoke. |
odyssey list | Recent missions from the local SQLite DB. --status to filter. |
odyssey status <mission_id> | One mission's detail. Accepts an id prefix. |
All commands respect --db and --working-dir to override the
~/.odyssey/ defaults.
| Area | Done | Deferred |
|---|---|---|
| Spec + validate | ✓ | — |
| Engine + lifecycle | ✓ | watchdog timers, materialized profiles |
| In-memory + SQLite persistence | ✓ | — |
| Provider ABCs + Local + HF | ✓ | OXE, Lovell-mode |
| CPU mock runner | ✓ | — |
| OpenVLA training runner | ✓ (validated on L4) | — |
| GR00T training runner | ✓ (validated on H100); task-level runner: gr00t routing | — |
| Robosuite eval runner | ✓ (auto-wired OpenVLA adapter) | full GPU end-to-end validation |
| Isaac Lab eval runner | ✓ skeleton + tests, subprocess launch + ODYSSEY_* stdout protocol | blessed eval script (GR00T/VLA recipe), real-Isaac smoke |
| Multi-agent eval (PILOT + SPECIALIST) | ✓ (out-of-process Gemma 4 planner) | full GPU end-to-end validation |
odyssey init / run / list / status / validate | ✓ | logs, publish |
| Leaderboard publish, Learning Graph, Anonymizer, Auth | — | post-v0.1.0-alpha.1 |
Add capture_video: true to any Robosuite eval task's config to save an MP4 of
each rollout (the example missions already enable it):
- name: eval-on-robosuite-lift
kind: evaluation
evaluation_type: robosuite
benchmark_name: Lift
num_episodes: 10
config:
unnorm_key: bridge_orig
capture_video: true # one MP4 per episode
Clips land in the task's output dir, one per episode:
~/.odyssey/runs/<mission_id>/<task_id>/videos/episode_01_PASS.mp4
(~/.odyssey/runs is the default; override with odyssey run --working-dir.)
The paths are also surfaced in the eval's result_summary.artifacts.videos and
in a videos_saved progress event.
It's near-free: the camera-enabled eval env already renders the frame each step
for the policy, so capture only adds a list append per step plus one encode per
episode (offloaded to a thread). Optional knobs: video_fps (default 24),
video_format (default mp4).
By default the clip records the policy's own input frame (agentview at
256×256) — a view framed for the model, not for humans, so the robot may sit
partially out of frame. To record from a dedicated, better-framed camera
without changing what the policy sees, set video_camera:
config:
capture_video: true
video_camera: frontview # any robosuite camera, e.g. frontview, sideview
video_height: 512 # optional, default 512
video_width: 512 # optional, default 512
The env then renders both cameras each step — the policy keeps consuming its
own camera and the MP4 records the other — at the cost of one extra render
stream per step. When video_camera is unset, behavior is exactly the near-free
default above. Setting it to the policy's own camera raises an error: unset it
instead to record the policy view.
Dependency. MP4 encoding needs
imageio[ffmpeg], which ships with the[robosuite](and[all]) extra — no extra install step if you already run eval. It's best-effort: if the encoder is missing it logs a warning and the eval still completes, just without a video. On a headless VM the sameMUJOCO_GL=egl/PYOPENGL_PLATFORM=eglthe eval needs also drives the offscreen render used for capture.
Apache License 2.0. See LICENSE.
See CONTRIBUTING.md. DCO sign-off required on every commit. Open an issue before non-trivial PRs — the API surface is moving weekly until v0.1.0-alpha.1 freezes.
Python
100.0%
Status: alpha (v0.1.0-alpha.1). The API, CLI, schemas, and wire protocols are still subject to change without notice. See
docs/for the design refs.
You train a robot with a multi-agent brain by describing a mission in YAML — an
embodiment to operate, agents with models for execution, tasks with a dataset to
train on, an evaluation benchmark to score against — and odyssey run walks it
through the full lifecycle: load → validate → execute training tasks → execute
the evaluation task → persist results. Local-mode by default; the hosted Lovell
AI services (leaderboard, learning graph, hosted runners) are optional layers
that land in later releases.
[!TIP] Linux only — install build dependencies before proceeding (needed by
.[all]):sudo apt update && sudo apt install build-essential python3-dev -y
git clone https://github.com/lovellai-dev/odyssey.git
cd odyssey
python3 -m venv .venv
source .venv/bin/activate
pip install -e . # CLI, validate, mock runs (lightweight)
pip install -e ".[all]" # real training + evaluation (torch, robosuite…)
pip install -e ".[all,dev]" # + pytest, ruff, mypy
The base install pulls in pydantic, click, pyyaml, and aiosqlite — enough to
run validate, list, status, and run --use-mock-runner against any
mission spec without a GPU. .[all] adds everything needed for real training
and evaluation runs.
# Validate the mission spec
$ odyssey validate examples/quickstart-openvla/mission.yaml
OK examples/quickstart-openvla/mission.yaml
spec version : 0.1
tasks : 1 training, 1 evaluation
# Run the full mission with a CPU mock (no GPU needed)
$ odyssey run examples/quickstart-openvla/mission.yaml --use-mock-runner
...
{"ts": "...", "event": "mission.completed", "overall_grade": 1.0}
COMPLETED c1756bad855e45cc9a95b5b0566c948b
overall_grade : 1.000
--use-mock-runner swaps in the CPU mock for every task, so this works on a
laptop without a GPU. Inspect runs afterward with odyssey list and
odyssey status <id>. State is persisted to ~/.odyssey/missions.db;
artifacts under ~/.odyssey/runs/<mission-id>/<task-id>/.
Two training paths ship today: GR00T (NVIDIA Isaac GR00T) and OpenVLA.
Both run through odyssey run <mission.yaml> — pick the quickstart that matches
the agent's model.
Fine-tunes nvidia/GR00T-N1.7-3B on the LeRobot-format demo set that ships
inside the Isaac-GR00T repo (no separate download), evaluated in the Isaac Lab
cube-lift environment.
Prerequisites:
Run
bash examples/quickstart-gr00t/setup.sh— it builds the three eval venvs (Odyssey core, GR00T server, Isaac Lab client transport) with uv. Seeexamples/quickstart-gr00t/README.mdfor the full walkthrough and why they're separate venvs by design.
Isaac Sim + Isaac Lab are a separate, heavy precondition — a large NVIDIA
Omniverse binary installed by NVIDIA's own tooling, not by setup.sh (the
script wires the eval client into it, and skips with a note if it's absent).
Provision them once following the
quickstart-gr00t README, then point
Odyssey at your checkouts:
export ISAAC_GR00T_REPO_PATH=/srv/Isaac-GR00T # GR00T checkout + demo dataset
export ISAACLAB_PATH=$HOME/IsaacLab # provides isaaclab.sh
Run:
odyssey run examples/quickstart-gr00t/mission.yaml
The mission routes its training task to the GR00T runner with
config: { runner: gr00t } — OpenVLA and GR00T both serve wildcard training
tasks, so the family is selected explicitly.
Prerequisites:
pip install -e ".[huggingface,openvla,robosuite]"
draccus and the fine-tuning script):
git clone https://github.com/openvla/openvla.git /srv/openvla
pip install -e /srv/openvla
export OPENVLA_REPO_PATH=/srv/openvla
wget -r -nH --cut-dirs=4 --reject="index.html*" \
https://rail.eecs.berkeley.edu/datasets/bridge_release/data/tfds/bridge_dataset/
mv bridge_dataset bridge_orig
Set --data_root_dir to the parent directory containing bridge_orig/.Run:
odyssey run examples/quickstart-openvla/mission.yaml
Hardware: 24 GB GPU (RTX 4090-class or better) for the OpenVLA LoRA fine-tune.
[!NOTE] GCP users: single-GPU VMs require
export NCCL_NET=Socketbefore running, to bypass Google's NCCL plugin. See issue #5 for details.
[!NOTE] Evaluation: the Robosuite runner auto-wires an OpenVLA→robosuite-action adapter (
make_openvla_policyinrunners/models/openvla.py) when no custompolicy_factoryis injected — it loads either a LoRA adapter or a full merged checkpoint, so eval works without extra glue. Full episode-completion validation on a real GPU is still in progress.
The fine-tune runs through the cloned OpenVLA repo, which carries its own
dependency set — most onboarding friction comes from there, not from Odyssey.
Mixing versions surfaces as protobuf / TensorFlow / tensorflow-metadata
conflicts or draccus import errors. Known-good versions (from OpenVLA's own
requirements — treat its repo as the source of truth):
Python 3.10
torch 2.2.0
torchvision 0.17.0
transformers 4.40.1
tokenizers 0.19.1
timm 0.9.10
flash-attn 2.5.5
To avoid re-downloading the 7B base model each run, point its path env var at a
local copy (HF id upper-cased, / and - → _, suffixed _PATH):
export OPENVLA_OPENVLA_7B_PATH=/path/to/openvla-7b # for base: openvla/openvla-7b
source: oxe / ref: bridge_orig resolvesOdyssey does not download the dataset — oxe is a pass-through. The runner
forwards two values to OpenVLA's finetune.py, which loads via TFDS/RLDS:
| mission.yaml | becomes the flag | meaning |
|---|---|---|
dataset.ref: bridge_orig | --dataset_name bridge_orig | the OXE registry key OpenVLA looks up |
config.data_root_dir: <path> | --data_root_dir <path> | the parent dir containing the RLDS dataset folder |
⚠️ Naming gotcha: the registry key and the on-disk folder name can differ.
In validation, ref: bridge_orig resolved to data under ~/bridge_dataset/1.0.0/,
so data_root_dir had to point at the parent of that folder — not the key
name. Check where your download actually landed and set data_root_dir to its
parent.
OpenVLA's finetune.py calls wandb.init() unconditionally, so a run stalls or
fails if W&B isn't reachable. Control it yourself:
# Disable for local / smoke runs:
export WANDB_MODE=disabled
# Or log to your account, then pass project/entity via mission config:
# config: { wandb_project: my-project, wandb_entity: my-entity }
Any config: key Odyssey doesn't consume is forwarded verbatim as
--<key> <value> to finetune.py.
Timing varies widely with hardware, disk, and network — treat these as orientation, not promises:
openvla-7b (~14 GB) on first run, unless
OPENVLA_OPENVLA_7B_PATH is set.it/s (~1.49 it/s on an NVIDIA L4 for
the quickstart config).If a stage seems stuck, it's almost always a download in progress or a dataset-path / W&B issue rather than a training bug — check those first.
The models pulled from the Hub are gated — you must accept each model's
license on its HuggingFace page, then authenticate on the machine before the
first run, or the download fails with 401/403:
openvla/openvla-7b — the PILOTgoogle/gemma-4-E2B-it — the
SPECIALIST in the multi-agent example (Apache-2.0, no gating)huggingface-cli login # paste a token from https://huggingface.co/settings/tokens
# or, non-interactive (CI / headless VM):
export HF_TOKEN=hf_xxx # a read token on an account that accepted the licenses
A mission with a SPECIALIST agent (a task planner) in addition to the PILOT runs a plan-then-execute loop during eval: the SPECIALIST decomposes the instruction into sub-steps once per episode, and the PILOT executes each. Only the PILOT produces actions and only the PILOT is trained — the SPECIALIST is inference-only (it runs its base checkpoint to plan and has no training task).
robot:
agents:
- id: pilot
role: PILOT
model: { source: huggingface, base: openvla/openvla-7b }
- id: task-planner
role: SPECIALIST
model:
source: huggingface
base: google/gemma-4-E2B-it
quantization: int4
modality: multimodal
The SPECIALIST is a vision-grounded multimodal Gemma 4 planner: it sees the
first camera frame of each episode and grounds its plan in the scene. Gemma 4
needs a modern transformers + torchvision, which conflicts with OpenVLA's
pinned transformers==4.40.1, so the SPECIALIST must run out of process in a
separate venv. The PILOT stays in the main venv; the two talk over a JSON-lines
subprocess protocol (the planner runs once per episode, off the per-step hot loop).
setup.sh)Multi-agent needs two venvs — env_pilot (the OpenVLA pilot + Robosuite
eval) and env_specialist (the Gemma 4 planner) — because their transformers
versions are mutually incompatible. The setup.sh script builds both
idempotently, each pinned to its known-good stack, clones the upstream OpenVLA repo,
and writes a sourceable .env:
# from the repo root — builds env_pilot + env_specialist, clones OpenVLA, writes .env
examples/multiagent-openvla-gemma/setup.sh
# load the active venv + env vars (incl. ODYSSEY_SPECIALIST_PYTHON) before each run
source examples/multiagent-openvla-gemma/.env
📖 Full GCP walkthrough (provisioning, dataset download, troubleshooting): the multi-agent GCP tutorial. For the simpler single-agent (OpenVLA-only) path, see the training tutorial.
The role-based names keep the two transformers stacks straight: env_pilot
holds OpenVLA (pinned transformers==4.40.1), env_specialist the Gemma planner
(modern transformers + torchvision).
# env_specialist — the Gemma planner (env_pilot is the OpenVLA install)
python -m venv env_specialist
env_specialist/bin/pip install -e ".[specialist]" \
-c constraints/specialist-known-good.txt
# point Odyssey at that venv's python (read per-process; export it in every shell
# that runs a mission, or persist it in your shell profile / VM startup script)
export ODYSSEY_SPECIALIST_PYTHON="$PWD/env_specialist/bin/python"
ODYSSEY_SPECIALIST_PYTHONis required for any mission with a SPECIALIST. The planner is launched in that venv (RemotePlanner→python -m odyssey.runners.agents.planner_server). If it is unset, multi-agent eval fails fast with a clearRuntimeError: the multimodal Gemma 4 planner cannot load in the main venv, which pinstransformers==4.40.1for OpenVLA.
Quick check without a simulator (launches the planner in the specialist venv and prints a decomposition — no OpenVLA or simulator needed):
python tests/manual/smoke_remote_planner.py
Why Gemma 4, not Gemma 3, for multimodal. Gemma 3 4B emits NaN logits under int4 bitsandbytes on this stack (verified across eager/sdpa attention, text-only and with-image), so it can't run quantized here. Gemma 4 (Apache-2.0, ungated) loads cleanly in int4 and grounds plans in the scene image.
VRAM note. Both models still share the GPU — the venv split solves the dependency conflict, not VRAM. The SPECIALIST is pinned to GPU 0 (
device_map={"": 0}) so bitsandbytes never silently offloads layers to CPU. Gemma 4 E4B-it int4 (~9.3 GB) alongside bf16 OpenVLA (~14 GB) peaks at ~23 GB — tight on a 24 GB L4; drop to E2B-it for headroom (this is what the multimodal example mission uses).
Two known-good stacks. The main venv pins OpenVLA's stack (
constraints/openvla-known-good.txt: torch 2.2.0, transformers 4.40.1); the specialist venv pins a modern one with torchvision (constraints/specialist-known-good.txt). They no longer need to be mutually compatible.
| Command | What it does |
|---|---|
odyssey init [DIR] | Scaffold a new mission directory. --template openvla|cpu_mock. |
odyssey validate <mission.yaml> | Parse + validate a spec. Exits 0 if clean. |
odyssey run <mission.yaml> | Execute end-to-end. --use-mock-runner for no-GPU smoke. |
odyssey list | Recent missions from the local SQLite DB. --status to filter. |
odyssey status <mission_id> | One mission's detail. Accepts an id prefix. |
All commands respect --db and --working-dir to override the
~/.odyssey/ defaults.
| Area | Done | Deferred |
|---|---|---|
| Spec + validate | ✓ | — |
| Engine + lifecycle | ✓ | watchdog timers, materialized profiles |
| In-memory + SQLite persistence | ✓ | — |
| Provider ABCs + Local + HF | ✓ | OXE, Lovell-mode |
| CPU mock runner | ✓ | — |
| OpenVLA training runner | ✓ (validated on L4) | — |
| GR00T training runner | ✓ (validated on H100); task-level runner: gr00t routing | — |
| Robosuite eval runner | ✓ (auto-wired OpenVLA adapter) | full GPU end-to-end validation |
| Isaac Lab eval runner | ✓ skeleton + tests, subprocess launch + ODYSSEY_* stdout protocol | blessed eval script (GR00T/VLA recipe), real-Isaac smoke |
| Multi-agent eval (PILOT + SPECIALIST) | ✓ (out-of-process Gemma 4 planner) | full GPU end-to-end validation |
odyssey init / run / list / status / validate | ✓ | logs, publish |
| Leaderboard publish, Learning Graph, Anonymizer, Auth | — | post-v0.1.0-alpha.1 |
Add capture_video: true to any Robosuite eval task's config to save an MP4 of
each rollout (the example missions already enable it):
- name: eval-on-robosuite-lift
kind: evaluation
evaluation_type: robosuite
benchmark_name: Lift
num_episodes: 10
config:
unnorm_key: bridge_orig
capture_video: true # one MP4 per episode
Clips land in the task's output dir, one per episode:
~/.odyssey/runs/<mission_id>/<task_id>/videos/episode_01_PASS.mp4
(~/.odyssey/runs is the default; override with odyssey run --working-dir.)
The paths are also surfaced in the eval's result_summary.artifacts.videos and
in a videos_saved progress event.
It's near-free: the camera-enabled eval env already renders the frame each step
for the policy, so capture only adds a list append per step plus one encode per
episode (offloaded to a thread). Optional knobs: video_fps (default 24),
video_format (default mp4).
By default the clip records the policy's own input frame (agentview at
256×256) — a view framed for the model, not for humans, so the robot may sit
partially out of frame. To record from a dedicated, better-framed camera
without changing what the policy sees, set video_camera:
config:
capture_video: true
video_camera: frontview # any robosuite camera, e.g. frontview, sideview
video_height: 512 # optional, default 512
video_width: 512 # optional, default 512
The env then renders both cameras each step — the policy keeps consuming its
own camera and the MP4 records the other — at the cost of one extra render
stream per step. When video_camera is unset, behavior is exactly the near-free
default above. Setting it to the policy's own camera raises an error: unset it
instead to record the policy view.
Dependency. MP4 encoding needs
imageio[ffmpeg], which ships with the[robosuite](and[all]) extra — no extra install step if you already run eval. It's best-effort: if the encoder is missing it logs a warning and the eval still completes, just without a video. On a headless VM the sameMUJOCO_GL=egl/PYOPENGL_PLATFORM=eglthe eval needs also drives the offscreen render used for capture.
Apache License 2.0. See LICENSE.
See CONTRIBUTING.md. DCO sign-off required on every commit. Open an issue before non-trivial PRs — the API surface is moving weekly until v0.1.0-alpha.1 freezes.
Python
100.0%