KitchenBench
A bimanual kitchen-manipulation benchmark for VLA models.
Built on Inspect Robots · part of WorldEvals, the "Inspect Evals for robotics".
Note: This project is in early development. The API may change between releases, so pin a version before depending on it.
KitchenBench is 10 kitchen-manipulation tasks expressed as Inspect Robots Tasks.
They are embodiment-agnostic, so you run them against any compatible
policy/embodiment. The set emphasizes bimanual coordination: pouring, lid
removal, folding, part-mating, a pure two-arm handover, and tool-mediated
scooping, alongside classic pick-place / stacking / slotted insertion and a
multi-instance sort.
It ships a dependency-free mock kitchen so the whole suite runs in CI, and is designed to point straight at real hardware (e.g. YAM bimanual arms driven by MolmoAct2).
The tasks
Task (--task) |
Goal | Bimanual | Category |
|---|---|---|---|
kitchenbench/place_cutlery |
place the {cutlery} on the {dishware} | pick-place | |
kitchenbench/stack |
stack the cups / bowls / plates | stacking | |
kitchenbench/place_in_rack |
place the {dishware} into the dish rack | insertion | |
kitchenbench/pour_pasta |
pour the dry pasta into the {vessel} | ✅ | granular |
kitchenbench/open_container |
open the {container} | ✅ | articulated |
kitchenbench/fold_cloth |
fold the {cloth} | ✅ | deformable |
kitchenbench/seal_container |
seal the {container} with its lid | ✅ | mating |
kitchenbench/handoff |
hand off the {item} from one arm to the other | ✅ | coordination |
kitchenbench/sort_cutlery |
sort the cutlery into the correct tray compartments | classification | |
kitchenbench/scoop_pasta |
scoop about {fill_target_g} g of the {pasta} with the {tool} and transfer it to the container | ✅ | granular+tool |
Note: Every value each task varies over (the objects a
{placeholder}can name, counts, forces, placement jitter) is documented in the task reference on the KitchenBench docs site. The per-instance distributions live inspecs.py.
Quick start
Install from PyPI (uv shown, plain pip works too; inspect-robots comes along
as a dependency):
uv pip install kitchenbench
Installing registers the 10 tasks plus a dependency-free mock kitchen (the
kitchen embodiment and the kitchen_scripted / kitchen_random /
kitchen_noop policies) with Inspect Robots via entry points, so everything
below runs immediately: no hardware, no simulator, no configuration.
Run one task (5 instances × 5 realizations = 25 rollouts, a few seconds on the mock):
inspect-robots list tasks # the 10 kitchenbench/* tasks from the table above
inspect-robots run --task kitchenbench/pour_pasta --policy kitchen_scripted --embodiment kitchen
Run the whole benchmark from Python with eval_set:
from inspect_robots import eval_set
from kitchenbench import TASK_FACTORIES
ok, logs = eval_set(
[f"kitchenbench/{key}" for key in sorted(TASK_FACTORIES)],
"kitchen_scripted",
"kitchen",
)
for log in logs:
print(log.eval.task, log.results.metrics["task_success"])
The mock is abstract (it models progress toward the scene goal, like Inspect Robots's
CubePick). Its job is to exercise the pipeline and give you a template. In the
mock, success depends only on the seeded goal direction, so the sampled setup
distributions have no causal effect (P̂ is degenerately 1.0 for the scripted
oracle); the distribution content only bites on a real embodiment. The value is
the task definitions, which run unchanged on a real robot: to evaluate a real
VLA, swap in a real policy/embodiment pair (see "Run it on real hardware" and
"Run it in simulation" below).
Task instances & realizations
KitchenBench follows the physical-automation methodology. The key ideas, top-down:
- A task (e.g.
pour_pasta) is a set of task instances. - A task instance is one concrete scenario written as a distribution: a stochastic setup (named random variables, each with a distribution) plus a goal (a natural-language success criterion that may reference the sampled variables). It is not a single fixed scene. It is a recipe for generating many.
- A realization is one sample of that recipe: draw every random variable from its distribution to get one concrete environment (and a concrete goal sentence).
- Running a
(policy, embodiment)pair onK_realizationsrealizations and averaging the binary successes estimates the instance success probability P̂[Yᵢ = 1].
task pour_pasta
├─ instance 1 (a distribution) ──realize──▶ 5 concrete environments ──▶ P̂₁
├─ instance 2 (a distribution) ──realize──▶ 5 concrete environments ──▶ P̂₂
│ … 5 instances total …
└─ instance 5 (a distribution) ──realize──▶ 5 concrete environments ──▶ P̂₅
KitchenBench uses the methodology's recommended defaults: 5 instances per task
(K_INSTANCES) and 5 realizations per instance (K_REALIZATIONS).
A worked example
This is one of pour_pasta's five instances (from
specs.py, lightly reformatted):
TaskInstance(
instance_id="pour_pasta/measuring-cup-to-bowl",
goal="pour the dry pasta into the {vessel}", # {vessel} is sampled
setup={
"vessel": Categorical(("bowl", "cup", "pot")),
"fill_g": Uniform(80, 200), # grams of pasta
"pour_height_cm": Uniform(8, 15),
"vessel_x_cm": Normal(0.0, 3.0), # placement jitter (cm)
"vessel_y_cm": Normal(0.0, 3.0),
},
language_vars=("vessel",),
target_kind="pour_into",
static={"substance": "dry_pasta"},
)
Realizing it with different seeds samples those distributions into concrete environments an operator can physically arrange (and a goal to give the VLA) (numbers rounded here for readability):
realize(seed=0) realize(seed=2)
Goal: pour the dry pasta into the bowl Goal: pour the dry pasta into the cup
Setup: Setup:
vessel = bowl vessel = cup
fill_g = 156 fill_g = 111
pour_height_cm = 9.9 pour_height_cm = 10.1
vessel_x_cm = +0.3 vessel_x_cm = -7.3
vessel_y_cm = -1.6 vessel_y_cm = +5.4
Inspect and realize instances from Python:
from kitchenbench import SPEC_BY_KEY
inst = SPEC_BY_KEY["pour_pasta"].instances[0]
inst.setup_spec()
# {'fill_g': 'Uniform[80, 200]', 'pour_height_cm': 'Uniform[8, 15]',
# 'vessel': 'Categorical({bowl, cup, pot})', 'vessel_x_cm': 'N(0, 3²)', ...}
r = inst.realize(seed=0)
r.instruction # 'pour the dry pasta into the bowl'
r.values # {'vessel': 'bowl', 'fill_g': 156.43…, 'pour_height_cm': 9.88…, …} (JSON-native)
r.setup_lines # ('fill_g = 156.43…', 'pour_height_cm = 9.88…', 'vessel = bowl', …)
How it maps to a run
Each instance becomes one Inspect Robots Scene; the 5 realizations are the 5 epochs
(Epochs(count=5, reducer="mean")), each seeded independently. Because the reducer
is the mean, each scene's reduced task_success is the instance success
probability P̂[Yᵢ = 1], exactly the methodology's estimator:
from inspect_robots import eval
(log,) = eval("kitchenbench/pour_pasta", "kitchen_scripted", "kitchen")
for s in log.samples:
print(s.scene_id, s.reduced["task_success"]) # one P̂ per instance
On real hardware an embodiment (or operator tool) calls realize_scene(scene, seed) to get the concrete setup to arrange. Realization.setup_lines is the
"arrange this" checklist, and Realization.instruction is the goal fed to the VLA.
Distribution types (in distributions.py):
Uniform(a, b) continuous · Categorical((…), weights=None) over a finite set ·
Normal(μ, σ) Gaussian · Constant(v) fixed. Every sample is a builtin
float/int/str (JSON-native), and Categorical preserves value types (an int
category samples back as an int).
Validation status: read before trusting the numbers. The shipped instances are AI-authored drafts (
Validation(source="opus-draft"),validated=False). The methodology'sK_i = 5is the count after human validation: 3 experts rating each instance on representativeness and quality, accepted only if both are ≥ 4. Run that commissioning pipeline before relying on the instances; we do not fabricate ratings. Also noteeval()'s task-levelmetrics["task_success"]is the mean of P̂ over instances: a convenience aggregate, not a methodology output (the methodology sorts the per-instance P̂ into quantiles and fits the pTQ / automation-halvings curves).
Run it on real hardware (YAM arms + MolmoAct2)
KitchenBench tasks are embodiment-agnostic. To evaluate on real YAM bimanual
arms with MolmoAct2, provide two Inspect Robots components (e.g. in your own
adapter package such as robocurve/embodiments):
- a
Policywrapping MolmoAct2:act(observation) -> ActionChunk(the scene'sinstructionis fed to the VLA verbatim); - an
Embodimentfor the YAM arms:reset/step/close, declaring its action space (e.g. two 7-DoF arms + grippers) and cameras. Because there is no privileged success oracle, the embodiment should turn the operator's confirmation at episode end intoStepResult(terminated=True, termination_reason="success")(or setrecord.operator_judgement). KitchenBench'stask_successscorer reads either. Declare the"self_paced"capability and pace the control loop insidestep().
inspect-robots run --task kitchenbench/pour_pasta --policy molmoact2 --embodiment yam_arms
Inspect Robots checks (policy, embodiment) compatibility (action dims, semantics,
camera/state keys) before any motion and writes an immutable EvalLog.
Run it in simulation
Every task instance carries a machine-readable sim annotation (see
plans/0003-sim-support.md): kitchenbench.sim resolves it into a
SceneBlueprint (what to spawn, where, with per-instance success parameters)
and defines the benchmark's official quantitative success criteria per
target kind, so a physics simulator needs no human operator. A sim embodiment
(Isaac Lab, MuJoCo, …):
from kitchenbench import build_blueprint, make_success_checker
# in Embodiment.reset(scene, seed=...) — the same per-epoch seed eval() passes:
bp = build_blueprint(scene, seed)
# ...spawn bp.objects from your asset catalog (nominal dims in kitchenbench.ASSETS),
# apply bp.conditions, register the reserved names gripper_left/gripper_right...
# build the checker AFTER spawning, with the scene at rest: some criteria
# capture initial state at construction (fold's baseline footprint).
checker = make_success_checker(bp, world) # world implements WorldState (4 queries)
# per step(): once checker(world).success, terminate with
# termination_reason="success" — the existing task_success scorer fires unchanged.
WorldState is four queries any simulator can answer (aabb,
contained_fraction, contained_mass_g, opening_fraction); the criteria
(nesting-aware stacking, mass-targeted pouring/scooping, transfer-detecting
handoff, …) and their thresholds are versioned as SIM_CONTRACT_VERSION.
Log it with your runs. Declare supported_target_kinds on your embodiment so
Inspect Robots gates scene realizability before any rollout. The checkers work off
poses, not sim internals, so a pose-tracked real rig can reuse them too.
Development
Dependency changes: after editing dependencies in
pyproject.toml, runuv lockand commit the updated lockfile. CI installs withuv sync --lockedand fails with "the lockfile needs to be updated" if you forget. Day-to-day conventions (PR-onlymain, the requiredci-okcheck, one-click releases) are documented inCLAUDE.md.
uv venv && uv pip install -e ".[dev]" # inspect-robots resolved from PyPI (>= 0.6)
uv run pre-commit install
uv run pytest --cov # 100% coverage required
uv run ruff check . && uv run mypy
Every public module, class, and function needs a docstring, enforced by Ruff D1; state the contract instead of restating the symbol name.
Citation
If you use KitchenBench in your research, please cite it:
@software{kitchenbench,
author = {Robocurve},
title = {KitchenBench: A bimanual kitchen-manipulation benchmark for VLA models},
year = {2026},
url = {https://github.com/robocurve/kitchenbench},
version = {0.3.0},
license = {MIT}
}
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kitchenbench-0.5.0.tar.gz.
File metadata
- Download URL: kitchenbench-0.5.0.tar.gz
- Upload date:
- Size: 172.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c30cee481cc69b4df1272958db2a4950c915cec615b61eebca34fe97c4740652
|
|
| MD5 |
71f889980c86d767d16a46a0a6d7ec30
|
|
| BLAKE2b-256 |
37656d5b15e22edf7caf63f3366b3a68566da279d6bddf13c67f6f8fc86bacb2
|
Provenance
The following attestation bundles were made for kitchenbench-0.5.0.tar.gz:
Publisher:
release.yml on robocurve/kitchenbench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kitchenbench-0.5.0.tar.gz -
Subject digest:
c30cee481cc69b4df1272958db2a4950c915cec615b61eebca34fe97c4740652 - Sigstore transparency entry: 2169298495
- Sigstore integration time:
-
Permalink:
robocurve/kitchenbench@6144319cd66914fbc7d34e12d32b2cf9d4852c2d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/robocurve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6144319cd66914fbc7d34e12d32b2cf9d4852c2d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file kitchenbench-0.5.0-py3-none-any.whl.
File metadata
- Download URL: kitchenbench-0.5.0-py3-none-any.whl
- Upload date:
- Size: 42.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a04c3b8c80211af4775308a77b98ddee811f20cb9a7bc1cf9291db8170a908d
|
|
| MD5 |
491668025fbd1c0d8a142a126a8b4f7e
|
|
| BLAKE2b-256 |
e3389776a3aa461273a424b19b4b12baf3193b3fd69cca11ab08be2bef42af69
|
Provenance
The following attestation bundles were made for kitchenbench-0.5.0-py3-none-any.whl:
Publisher:
release.yml on robocurve/kitchenbench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kitchenbench-0.5.0-py3-none-any.whl -
Subject digest:
4a04c3b8c80211af4775308a77b98ddee811f20cb9a7bc1cf9291db8170a908d - Sigstore transparency entry: 2169298510
- Sigstore integration time:
-
Permalink:
robocurve/kitchenbench@6144319cd66914fbc7d34e12d32b2cf9d4852c2d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/robocurve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6144319cd66914fbc7d34e12d32b2cf9d4852c2d -
Trigger Event:
workflow_dispatch
-
Statement type: