simhub 0.2.0__tar.gz → 0.2.1__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.
Files changed (112) hide show
  1. simhub-0.2.1/PKG-INFO +1196 -0
  2. simhub-0.2.1/README.sdk.md +1146 -0
  3. {simhub-0.2.0 → simhub-0.2.1}/pyproject.toml +23 -2
  4. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/__init__.py +29 -12
  5. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/base.py +57 -11
  6. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/blender_replay.py +3 -0
  7. simhub-0.2.1/src/simhub/checkpoint.py +36 -0
  8. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/client.py +99 -7
  9. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/contracts.py +41 -2
  10. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/definition_runtime.py +17 -3
  11. simhub-0.2.1/src/simhub/deformables.py +169 -0
  12. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/env.py +26 -0
  13. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/errors.py +4 -0
  14. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/execution_plan.py +7 -2
  15. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/hosted_run.py +7 -4
  16. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/interfaces.py +3 -1
  17. simhub-0.2.1/src/simhub/logs.py +79 -0
  18. simhub-0.2.1/src/simhub/logs_cli.py +54 -0
  19. simhub-0.2.1/src/simhub/model_cli.py +126 -0
  20. simhub-0.2.1/src/simhub/model_sources.py +148 -0
  21. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/models.py +3 -0
  22. simhub-0.2.1/src/simhub/openpi_policy.py +212 -0
  23. simhub-0.2.1/src/simhub/openpi_transport.py +167 -0
  24. simhub-0.2.1/src/simhub/physics.py +92 -0
  25. simhub-0.2.1/src/simhub/physics_routing.py +28 -0
  26. simhub-0.2.1/src/simhub/preview_animation.py +174 -0
  27. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/project.py +19 -4
  28. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/recording.py +32 -4
  29. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/render_context.py +28 -3
  30. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/render_diagnostics.py +25 -2
  31. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/render_scene.py +8 -1
  32. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/render_worker.py +40 -22
  33. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/rollout.py +90 -11
  34. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/run_cli.py +43 -3
  35. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/runner.py +126 -49
  36. simhub-0.2.1/src/simhub/runner_environment.py +41 -0
  37. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/session.py +81 -5
  38. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/single.py +4 -2
  39. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/spec.py +3 -3
  40. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/viewer.py +24 -21
  41. simhub-0.2.1/src/simhub/warp_checks.py +27 -0
  42. simhub-0.2.1/src/simhub/warp_physics.py +211 -0
  43. simhub-0.2.0/PKG-INFO +0 -646
  44. simhub-0.2.0/README.sdk.md +0 -606
  45. simhub-0.2.0/src/simhub/model_cli.py +0 -83
  46. {simhub-0.2.0 → simhub-0.2.1}/skills/README.md +0 -0
  47. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-assets/SKILL.md +0 -0
  48. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-blender-scenes/SKILL.md +0 -0
  49. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-datasets/SKILL.md +0 -0
  50. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-evaluations/SKILL.md +0 -0
  51. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-hosted-batches/SKILL.md +0 -0
  52. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-local-debugging/SKILL.md +0 -0
  53. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-mujoco-scenes/SKILL.md +0 -0
  54. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-policies/SKILL.md +0 -0
  55. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-projects/SKILL.md +0 -0
  56. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-projects/assets/counter.py +0 -0
  57. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-recording/SKILL.md +0 -0
  58. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-setup/SKILL.md +0 -0
  59. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-tasks/SKILL.md +0 -0
  60. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-typed-runs/SKILL.md +0 -0
  61. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-typed-scenes/SKILL.md +0 -0
  62. {simhub-0.2.0 → simhub-0.2.1}/skills/simhub-variations/SKILL.md +0 -0
  63. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/agent_workspace.py +0 -0
  64. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/asset_cli.py +0 -0
  65. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/asset_spec.py +0 -0
  66. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/asset_urdf.py +0 -0
  67. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/batch.py +0 -0
  68. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/blender.py +0 -0
  69. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/builders.py +0 -0
  70. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/checkout.py +0 -0
  71. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/checks.py +0 -0
  72. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/cleaning.py +0 -0
  73. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/codegen.py +0 -0
  74. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/datasets.py +0 -0
  75. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/emit.py +0 -0
  76. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/evaluation.py +0 -0
  77. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/experiments.py +0 -0
  78. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/frame_set.py +0 -0
  79. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/gymnasium_adapter.py +0 -0
  80. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/hf.py +0 -0
  81. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/ids.py +0 -0
  82. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/kinematics.py +0 -0
  83. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/mjcf.py +0 -0
  84. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/named_assets.py +0 -0
  85. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/native_physics.py +0 -0
  86. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/native_scene.py +0 -0
  87. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/native_visuals.py +0 -0
  88. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/notebook.py +0 -0
  89. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/objects.py +0 -0
  90. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/openfoam.py +0 -0
  91. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/particulate.py +0 -0
  92. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/policies.py +0 -0
  93. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/presentation.py +0 -0
  94. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/project_cli.py +0 -0
  95. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/py.typed +0 -0
  96. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/registry.py +0 -0
  97. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/rendering.py +0 -0
  98. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/replay.py +0 -0
  99. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/report.py +0 -0
  100. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/robots.py +0 -0
  101. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/run_images.py +0 -0
  102. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/runs_cli.py +0 -0
  103. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/scene_spec.py +0 -0
  104. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/schema.py +0 -0
  105. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/scorer.py +0 -0
  106. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/studio.py +0 -0
  107. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/suite.py +0 -0
  108. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/task.py +0 -0
  109. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/types.py +0 -0
  110. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/variation_effects.py +0 -0
  111. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/variation_specs.py +0 -0
  112. {simhub-0.2.0 → simhub-0.2.1}/src/simhub/variations.py +0 -0
simhub-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,1196 @@
1
+ Metadata-Version: 2.5
2
+ Name: simhub
3
+ Version: 0.2.1
4
+ Summary: Define an environment, a policy and a task in Python; run them on hosted GPUs
5
+ Project-URL: Homepage, https://github.com/General-Deployment/sim
6
+ License: Proprietary
7
+ Keywords: evaluation,mujoco,reinforcement-learning,robotics,simulation
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: numpy>=1.24
11
+ Requires-Dist: pydantic>=2.7
12
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
13
+ Requires-Dist: typing-extensions>=4.12
14
+ Provides-Extra: all
15
+ Requires-Dist: huggingface-hub>=0.26; extra == 'all'
16
+ Requires-Dist: imageio-ffmpeg>=0.5; extra == 'all'
17
+ Requires-Dist: ipython>=8.0; extra == 'all'
18
+ Requires-Dist: matplotlib>=3.7; extra == 'all'
19
+ Requires-Dist: msgpack>=1.0; extra == 'all'
20
+ Requires-Dist: mujoco==3.11.*; (python_version >= '3.10') and extra == 'all'
21
+ Requires-Dist: pandas>=2.0; extra == 'all'
22
+ Requires-Dist: pillow>=10.0; extra == 'all'
23
+ Requires-Dist: pyarrow>=15.0; extra == 'all'
24
+ Requires-Dist: websockets<16,>=15; extra == 'all'
25
+ Provides-Extra: dataset
26
+ Requires-Dist: pyarrow>=15.0; extra == 'dataset'
27
+ Provides-Extra: mujoco
28
+ Requires-Dist: mujoco==3.11.*; (python_version >= '3.10') and extra == 'mujoco'
29
+ Provides-Extra: notebook
30
+ Requires-Dist: ipython>=8.0; extra == 'notebook'
31
+ Requires-Dist: matplotlib>=3.7; extra == 'notebook'
32
+ Requires-Dist: pandas>=2.0; extra == 'notebook'
33
+ Requires-Dist: pillow>=10.0; extra == 'notebook'
34
+ Provides-Extra: openpi
35
+ Requires-Dist: msgpack>=1.0; extra == 'openpi'
36
+ Requires-Dist: pillow>=10.0; extra == 'openpi'
37
+ Requires-Dist: websockets<16,>=15; extra == 'openpi'
38
+ Provides-Extra: typing
39
+ Requires-Dist: datamodel-code-generator==0.35.0; extra == 'typing'
40
+ Provides-Extra: video
41
+ Requires-Dist: imageio-ffmpeg>=0.5; extra == 'video'
42
+ Requires-Dist: pillow>=10.0; extra == 'video'
43
+ Provides-Extra: warp
44
+ Requires-Dist: mujoco-warp==3.14.0; (python_version >= '3.10') and extra == 'warp'
45
+ Requires-Dist: mujoco==3.14.0; (python_version >= '3.10') and extra == 'warp'
46
+ Requires-Dist: warp-lang==1.15.0; (python_version >= '3.10') and extra == 'warp'
47
+ Provides-Extra: weights
48
+ Requires-Dist: huggingface-hub>=0.26; extra == 'weights'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # SimHub SDK
52
+
53
+ SimHub is a Python SDK for building simulation environments, running robot
54
+ policies, and evaluating their results. Write your environment and controller
55
+ in Python, submit their source to hosted SimHub, and inspect runs, metrics,
56
+ recordings, and task outcomes in the
57
+ [SimHub console](https://simhub-console.pages.dev/).
58
+
59
+ This guide covers SDK **0.2.0**. Start with installation and the hosted example;
60
+ use the authoring reference when building your own project. The examples submit
61
+ to hosted SimHub by default, including the complete single-file starter. Local
62
+ execution is an explicit debugging option.
63
+
64
+ - [Install and connect](#install-and-connect)
65
+ - [Run your first hosted simulation](#run-your-first-hosted-simulation)
66
+ - [Author a project](#author-a-project)
67
+ - [Submit a complete single-file project](#submit-a-complete-single-file-project)
68
+ - [Compare policies and vary environments](#compare-policies-and-vary-environments)
69
+ - [Debug and recover runs](#debug-and-recover-runs)
70
+ - [Guidance for coding agents](#guidance-for-coding-agents)
71
+
72
+ ## How SimHub fits together
73
+
74
+ An **environment** is the simulated world: it accepts actions, produces
75
+ observations, and reports measurements. A **policy** is the controller: it reads
76
+ observations and chooses the next action. A **task** defines success using the
77
+ environment's measurements, such as placing an object without a collision.
78
+
79
+ A **project** is the Python source and assets needed to build that world and run
80
+ its policies. An **episode** is one execution of an environment and policy with
81
+ a particular seed. Hosted episodes produce **runs**; the service groups work
82
+ into batches and schedules jobs for compilation, simulation, and requested
83
+ postprocessing. An **experiment** freezes a collection of episodes and places
84
+ limits on their total work.
85
+
86
+ For new hosted integrations, use `EnvironmentDefinition`, `PolicyDefinition`,
87
+ `TaskDefinition`, and `RunRequest`. These are validated descriptions of your
88
+ code and parameters. The backend constructs the actual runtime objects.
89
+ Decorator-based projects using `@simhub.environment` and `@simhub.policy` are
90
+ also supported and submit to the same hosted service.
91
+
92
+ ## Physics backend
93
+
94
+ Native MuJoCo remains the default. Manifests and single-file declarations
95
+ without an image use `python-cpu`; typed packages without a manifest retain
96
+ `python-gpu` for policy/rendering resources with native physics. Existing
97
+ explicit runtime selections are preserved.
98
+
99
+ MuJoCo Warp is a supported opt-in backend. Select `image = "python-mujoco-warp"`
100
+ in `simhub.toml` and, for typed declarations, set
101
+ `physics=sh.MuJoCoPhysics(backend="mujoco-warp")`. It requires an allocated NVIDIA
102
+ GPU and a deployed runtime advertising `physics:mujoco-warp-v1`, with no silent
103
+ CPU fallback. `sh.MuJoCoPhysics()` and `sh.create_physics(model)` use native physics.
104
+
105
+ Construct `sh.create_physics(model, config=context.physics)` in the runtime. Use
106
+ its `step`, `reset`, `upload_state` and `bind_recorder` methods instead of directly
107
+ calling `mujoco.mj_step`. `MujocoEnvironment` subclasses honor the explicitly
108
+ selected hosted backend automatically.
109
+
110
+ See [the Warp runtime guide](../docs/SIMHUB_MUJOCO_WARP.md) for state synchronization,
111
+ cloth recording, migration and the hosted acceptance example. GPU acceleration
112
+ does not itself repair scene collisions or guarantee a speedup for one world.
113
+
114
+ ## Install and connect
115
+
116
+ The core SDK supports Python 3.9 or newer. Use Python 3.10 or newer for local
117
+ MuJoCo and the repository examples in this guide. In your Python environment:
118
+
119
+ ```bash
120
+ python -m pip install simhub
121
+ python -c "import simhub; print(simhub.__version__)"
122
+ simhub --help
123
+ ```
124
+
125
+ The base installation contains everything needed to submit the examples in this
126
+ guide. Hosted workers provide the simulation engines; MuJoCo, Blender, and model
127
+ weights do not need to be installed on the submitting machine. Optional extras
128
+ support client-side analysis and explicitly requested local development:
129
+
130
+ ```bash
131
+ python -m pip install 'simhub[mujoco]' # Local MuJoCo, pinned to the supported series
132
+ python -m pip install 'simhub[video]' # Local video encoding and image support
133
+ python -m pip install 'simhub[notebook]' # Tables, plots, and notebook display
134
+ python -m pip install 'simhub[openpi]' # OpenPI WebSocket client
135
+ python -m pip install 'simhub[dataset]' # LeRobot state/action export
136
+ python -m pip install 'simhub[weights]' # Hugging Face checkpoint access
137
+ python -m pip install 'simhub[typing]' # Generate parameter classes from schemas
138
+ python -m pip install 'simhub[all]' # Runtime extras above, excluding typing
139
+ ```
140
+
141
+ The standard hosted Python runtimes include MuJoCo, NumPy, Pillow, video support,
142
+ and the OpenPI client transport. Projects do not need to repeat these packages
143
+ in their manifests. Declare only additional project-specific libraries. Blender
144
+ and its `bpy` module need separate installation only for explicit local use.
145
+
146
+ For hosted work, sign in to the
147
+ [SimHub console](https://simhub-console.pages.dev/), open **API keys**, and create
148
+ a key. Make it available to your process:
149
+
150
+ ```bash
151
+ export SIMHUB_API_KEY="your-api-key"
152
+ ```
153
+
154
+ ```python
155
+ import simhub as sh
156
+
157
+ with sh.connect() as sim:
158
+ print(sim)
159
+ ```
160
+
161
+ The default API is `https://api.g-deployment.com`. To use another deployment,
162
+ set `SIMHUB_API_URL` or pass `sh.connect(url="https://your-api.example.com")`.
163
+ Explicit `url=` and `key=` arguments take precedence over environment variables.
164
+ Keep keys out of project source and uploaded assets.
165
+
166
+ `connect()` is hosted-only by default and prevents `project.run_local(...)`
167
+ through that session. No additional flag is needed for hosted execution.
168
+ Use `sim.run(...)` for typed requests or `sim.project(...).launch(...)` for
169
+ factory projects. Both upload source and submit hosted jobs whose results appear
170
+ in the console. The CLI's `simhub launch PROJECT` and `simhub run PROJECT`
171
+ commands also default to hosted submission.
172
+
173
+ For repository contributors: the published SDK and the server distribution
174
+ share the `simhub` import name. Use separate environments for them. Installing
175
+ `./simhub` directly selects the server's build configuration; see the
176
+ [SDK build and release instructions](https://github.com/General-Deployment/sim/blob/main/simhub/PUBLISHING.md)
177
+ when you need an SDK built from source.
178
+
179
+ ## Run your first hosted simulation
180
+
181
+ The repository includes a complete warehouse project: a scripted robot scans
182
+ three shelf labels and returns to its starting position. Its classes are
183
+ project code, so get the example before importing them:
184
+
185
+ ```bash
186
+ git clone https://github.com/General-Deployment/sim.git
187
+ cd sim/examples
188
+ ```
189
+
190
+ With the SDK installed and your API key configured, save the following as
191
+ `run_warehouse.py` in this `examples` directory:
192
+
193
+ ```python
194
+ from pathlib import Path
195
+ import simhub as sh
196
+ from warehouse_barcode_scan.definitions import Warehouse, BarcodeController, ScanLabels
197
+
198
+ request = sh.RunRequest(
199
+ environment=Warehouse(),
200
+ policy=BarcodeController(),
201
+ task=ScanLabels(),
202
+ seed=0,
203
+ limits=sh.RunLimits(max_steps=960, timeout_seconds=1800, gpus=1),
204
+ outputs=sh.Outputs(cameras=()),
205
+ )
206
+
207
+ with sh.connect() as sim:
208
+ plan = sim.plan(request, project="warehouse_barcode_scan")
209
+ print(plan.describe())
210
+
211
+ run = sim.run(
212
+ request,
213
+ project="warehouse_barcode_scan",
214
+ request_id="warehouse-first-run-1",
215
+ )
216
+ run.save(Path("warehouse-run.json"))
217
+ run.wait(outputs=True)
218
+
219
+ print("Run:", run.id)
220
+ print("Evaluation:", run.evaluation)
221
+ for artifact in run.artifacts:
222
+ print(artifact.id, artifact.path)
223
+ ```
224
+
225
+ ```bash
226
+ python run_warehouse.py
227
+ ```
228
+
229
+ This submits real hosted work. The SDK validates the request, packages the
230
+ source, and selects compatible hosted runtimes. The backend builds the scene,
231
+ executes the policy, and records the result. Your machine does not need MuJoCo,
232
+ Blender, a GPU, or an operator account to submit it. The example requires a
233
+ deployment that advertises the corresponding typed scene and runtime variation
234
+ capabilities; the client reports missing capabilities before submission.
235
+
236
+ `sim.plan(...)` reads source files and the hosted catalog without uploading or
237
+ starting jobs. It describes stages and limits; it is not a price quote or a
238
+ capacity reservation. `sim.run(...)` submits the work. The saved receipt lets
239
+ you resume monitoring after closing the terminal.
240
+
241
+ Open the [SimHub console](https://simhub-console.pages.dev/), select the same
242
+ organization as your API key, and open **Runs** to follow the submitted run.
243
+ Its run page shows execution status, task evaluation, metrics, and available
244
+ recordings. SDK submissions appear there automatically; no separate result
245
+ upload is needed. The **Scenes** view contains stored scene previews and
246
+ snapshots when the project produces them.
247
+
248
+ The first run above requests no extra recording cameras or cinematic film.
249
+ To request a film, replace its `outputs` with:
250
+
251
+ ```python
252
+ outputs = sh.Outputs(video=sh.VideoOutput())
253
+ ```
254
+
255
+ Use this value in a new `RunRequest` and a new request ID. The default cinematic
256
+ is 1920×1080, 60 fps, 20 seconds, and 24 samples, with overlays enabled. Film
257
+ delivery also requires a compatible hosted renderer and takes additional work.
258
+ After `run.wait(outputs=True)`, `run.video`, `run.poster`, and `run.provenance`
259
+ identify the delivered artifacts. Ordinary artifact URLs require authentication;
260
+ use the session's client to download their bytes:
261
+
262
+ ```python
263
+ # Inside the open session, after waiting for a run that requested a film:
264
+ video = run.video
265
+ if video is not None:
266
+ Path("warehouse.mp4").write_bytes(sim.client.download(video.id))
267
+ ```
268
+
269
+ Execution and task success are separate. `run.wait()` confirms execution and
270
+ requested delivery; inspect `run.evaluation.outcome` and
271
+ `run.evaluation.checked` to determine whether the stored task passed. A policy
272
+ can finish normally and fail to scan the labels.
273
+
274
+ See the [warehouse source and assumptions](https://github.com/General-Deployment/sim/tree/main/examples/warehouse_barcode_scan)
275
+ for the scene, controller, evaluation, and cinematic hooks.
276
+
277
+ ## Author a project
278
+
279
+ ### Project layout and dependencies
280
+
281
+ A typed project is a Python package directory. For example:
282
+
283
+ ```text
284
+ my_project/
285
+ __init__.py
286
+ simhub.toml
287
+ parameters.py # Validated environment and policy inputs
288
+ definitions.py # Public environment, policy, and task contracts
289
+ runtime.py # Executable environment and policy implementations
290
+ recipe.py # Optional scene construction
291
+ parts.py # Optional reusable scene components
292
+ assets/ # Meshes, textures, and other input files
293
+ run_project.py # Submission script, beside the package
294
+ ```
295
+
296
+ Run `python run_project.py` from the directory containing `my_project`, import
297
+ from `my_project.definitions`, and pass `project="my_project"` to `sim.run`.
298
+ The source root must have a valid Python package name, such as `my_project`.
299
+ The manifest's display name may contain hyphens.
300
+
301
+ Put `simhub.toml` **inside** the package directory:
302
+
303
+ ```toml
304
+ name = "my-project"
305
+ image = "python-gpu"
306
+ include = ["*.py", "assets/**"]
307
+ ```
308
+
309
+ - `image` selects a runtime offered by the deployment, commonly `python-cpu`
310
+ or `python-gpu`.
311
+ - `include` controls which source and asset files enter the bundle. Add patterns
312
+ for nonstandard inputs such as `*.blend`. Files outside the bundle are not
313
+ available to hosted code unless resolved through a supported asset mechanism.
314
+
315
+ Bundles are deterministic and content-addressed. Keep heavy imports and model
316
+ loading inside runtime construction or factories so that importing definitions
317
+ remains lightweight. Use paths relative to `__file__` or the supplied runtime
318
+ context instead of absolute paths from your laptop.
319
+
320
+ For projects that need libraries beyond the standard hosted runtime,
321
+ `requirements` is an optional list of additional simulation dependencies.
322
+ `scene_requirements` optionally supplies the complete list of additional
323
+ packages for scene compilation and previews; omitting it uses `requirements`,
324
+ while `[]` adds no scene packages. Keep policy-only libraries out of the scene
325
+ list. Configure these once in the project rather than reconstructing dependency
326
+ lists for each run. The hosted runtime owns the supported MuJoCo version.
327
+
328
+ ### Typed definitions
329
+
330
+ Subclass `sh.Parameters` to declare inputs with types and Pydantic constraints.
331
+ For example, in `my_project/parameters.py`:
332
+
333
+ ```python
334
+ from pydantic import Field
335
+ import simhub as sh
336
+
337
+ class SceneParameters(sh.Parameters):
338
+ target_count: int = Field(default=3, ge=1, le=10)
339
+ spacing_m: float = Field(default=0.5, gt=0)
340
+
341
+ class ControllerParameters(sh.Parameters):
342
+ speed: float = Field(default=0.2, gt=0, le=1)
343
+ ```
344
+
345
+ Definitions describe the runtime's interface. The following is a contract
346
+ example for `my_project/definitions.py`; it requires the matching runtime
347
+ implementations described below before it can be submitted:
348
+
349
+ ```python
350
+ import simhub as sh
351
+ from .parameters import SceneParameters, ControllerParameters
352
+
353
+ STATE = sh.ArrayDefinition(name="state", shape=(6,), dtype="float32", unit="rad")
354
+ ACTION = sh.ArrayDefinition(name="action", shape=(6,), dtype="float32", unit="rad")
355
+
356
+ environment = sh.EnvironmentDefinition[SceneParameters](
357
+ name="my-environment",
358
+ parameters=SceneParameters(),
359
+ runtime=sh.SourceRef(entrypoint="my_project.runtime:MyEnvironment"),
360
+ observations=(STATE,),
361
+ action=ACTION,
362
+ metrics=(sh.MetricDefinition(name="targets_reached"),),
363
+ control_hz=30,
364
+ )
365
+ policy = sh.PolicyDefinition[ControllerParameters](
366
+ name="my-controller",
367
+ parameters=ControllerParameters(),
368
+ runtime=sh.SourceRef(entrypoint="my_project.runtime:MyController"),
369
+ observations=(STATE,),
370
+ action=ACTION,
371
+ control_hz=30,
372
+ )
373
+ task = sh.TaskDefinition(
374
+ name="reach-all-targets",
375
+ spec=sh.ConditionsSpec(conditions=(
376
+ sh.MetricPredicate(metric="targets_reached", op="gte", value=3),
377
+ )),
378
+ )
379
+ request = sh.RunRequest(environment=environment, policy=policy, task=task)
380
+ ```
381
+
382
+ `RunRequest` validates that the action schemas and control rates match, the
383
+ policy's observations exist with the declared shapes, dtypes, and units, and
384
+ the task references declared metrics. Supported predicate operators are `lt`,
385
+ `lte`, `gt`, `gte`, `eq`, and `ne`. Use `StagesSpec` with `StagePredicate` for
386
+ ordered progress or `ScoreSpec` for a threshold on one metric.
387
+
388
+ Contracts reject unknown fields and nonfinite numbers and are immutable.
389
+ Construct a new value to change inputs. `model_dump_json()` serializes a
390
+ contract; `sh.export_schema(type(value))` exposes its JSON Schema. Preserve
391
+ concrete parameter types when loading serialized definitions or experiments.
392
+
393
+ `SourceRef` is a `package.module:ClassOrFactory` reference to code in the bundle.
394
+ `SourceRef.of(MyClass)` also works for importable classes; classes defined in
395
+ `__main__` or nested inside a function cannot be used that way. A typed
396
+ `EnvironmentDefinition` describes an environment; it is not the environment
397
+ instance that steps physics.
398
+
399
+ ### Runtime contracts
400
+
401
+ For typed projects, the environment constructor or factory accepts
402
+ `parameters: SceneParameters` and `context: sh.RuntimeContext` as keyword
403
+ arguments. The policy accepts `parameters: ControllerParameters`,
404
+ `context: sh.RuntimeContext`, and `environment`. Annotate `parameters` with its
405
+ concrete `Parameters` subclass: the hosted runtime uses that annotation to
406
+ validate the input. Model-backed typed policies also accept `model` when a
407
+ model binding is declared.
408
+
409
+ An environment implements:
410
+
411
+ - `spec: sh.EnvSpec`: its action width, observation schemas, control rate,
412
+ episode limit, cameras, and metrics. The identifier field is `id`.
413
+ - `reset(seed) -> dict`: reset episode state and return initial observations.
414
+ - `step(action) -> (dict, sh.StepInfo)`: advance one control tick. This is a
415
+ two-item tuple; it is not the Gymnasium five-item return value.
416
+ - `observe(keys) -> dict`: provide the requested observations and render only
417
+ requested image keys.
418
+ - `score() -> dict`: return the declared metrics. Missing or nonfinite required
419
+ metrics prevent a clean result.
420
+ - `done`: a boolean property indicating that the episode has ended.
421
+
422
+ `sh.Environment` supplies `spec` from your `describe()` method, a step counter,
423
+ and a default `done` based on `spec.max_steps`. Your implementation resets
424
+ `self._steps` in `reset()` and increments it in `step()`. Initialize fields used
425
+ by `describe()` or `done` before calling `super().__init__()`.
426
+
427
+ A policy implements `spec: sh.PolicySpec`, `reset()`, and `act(obs) -> action`.
428
+ Use `sh.PolicyBase` for a stateless default `reset`, and override it to clear
429
+ stateful controllers between episodes. `PolicySpec.expects` names the
430
+ observations the loop requests. Implement `setup(context)` and `close()` when
431
+ resources need initialization or cleanup. Both base classes provide these hooks.
432
+
433
+ `StepInfo(progress=..., metrics=..., state=..., done=...)` describes an
434
+ individual step. Progress and `done` do not themselves prove task success;
435
+ the task evaluates the recorded measurements. Keep runtime specs consistent
436
+ with their typed definitions: the worker checks them during construction.
437
+
438
+ `RuntimeContext` supplies `source_root`, `output_root`, an optional compiled
439
+ `scene_path`, and deterministic seed streams. Use `context.seeds` or
440
+ `context.rng("sensor_noise", namespace="wrist")` for independent random streams.
441
+ Seed third-party simulators and frameworks explicitly.
442
+
443
+ For existing engines, use `sh.MujocoEnvironment` or adapt a Gymnasium environment
444
+ with `sh.GymnasiumAdapter`. The
445
+ [Gymnasium adapter guide](https://github.com/General-Deployment/sim/blob/main/docs/SDK_TYPED_PARAMETERS.md#adapting-an-existing-gymnasium-environment)
446
+ and [warehouse runtime](https://github.com/General-Deployment/sim/blob/main/examples/warehouse_barcode_scan/runtime.py)
447
+ show working integrations.
448
+
449
+ ### Scene recipes, native engines, and assets
450
+
451
+ Set an environment's `recipe=sh.SourceRef(...)` when it needs a separate scene
452
+ compilation stage. An `EnvironmentRecipe` composes `Component` objects through
453
+ a `SceneBuilder`. MuJoCo and Blender components can supply native programs or
454
+ files while keeping their public inputs typed. The runtime consumes the compiled
455
+ scene through `context.scene_path`.
456
+
457
+ Start with the [native component guide](https://github.com/General-Deployment/sim/blob/main/docs/SDK_NATIVE_COMPONENTS.md)
458
+ and [warehouse recipe](https://github.com/General-Deployment/sim/blob/main/examples/warehouse_barcode_scan/recipe.py).
459
+ `ObjectSlot`, `ComponentInterface`, `SlotSelection`, and `InstanceOptions` let
460
+ components expose compatible joints, frames, materials, and instance settings.
461
+
462
+ For reusable robot or mesh files, upload a named asset:
463
+
464
+ ```bash
465
+ simhub asset upload robot.urdf --env robots --name arm
466
+ ```
467
+
468
+ Resolve declared assets in project code with `sh.asset("arm").path`. URDF
469
+ references need their meshes and other dependencies packaged too. Follow
470
+ [named assets](https://github.com/General-Deployment/sim/blob/main/simhub/docs/named-assets.md)
471
+ for aliases and version pinning, and
472
+ [asset metadata](https://github.com/General-Deployment/sim/blob/main/docs/SDK_ASSET_METADATA.md)
473
+ for links, joints, materials, and semantic roles.
474
+
475
+ Blender-authored factory projects can provide `write_mjcf(out, seed=0, **kwargs)`.
476
+ The `simhub.blender` helpers mark static objects, dynamic bodies, collision
477
+ proxies, and excluded visual geometry before export. Include `.blend` files in
478
+ the manifest if the scene is authored in Blender's UI. See
479
+ [the procedural Blender example](https://github.com/General-Deployment/sim/tree/main/examples/blender_depot)
480
+ and [the saved Blender scene example](https://github.com/General-Deployment/sim/tree/main/examples/blend_file_dock).
481
+ MuJoCo collision geometry is convex; use separate parts or proxies where a
482
+ single hull would fill an opening.
483
+
484
+ ## Submit a complete single-file project
485
+
486
+ This small environment moves a number toward a seeded target. It demonstrates
487
+ the complete environment, policy, task, and hosted submission using only the
488
+ base SDK and your API key. Save it as `counter.py`:
489
+
490
+ ```python
491
+ import numpy as np
492
+ import simhub as sh
493
+
494
+ sh.manifest("counter-demo", image="python-cpu")
495
+
496
+ class Counter(sh.Environment):
497
+ def __init__(self):
498
+ self.position = 0.0
499
+ self.target = 1.0
500
+ super().__init__()
501
+
502
+ def describe(self):
503
+ return sh.EnvSpec(
504
+ id="counter", action_dim=1, control_hz=10, max_steps=20,
505
+ observation={"state": sh.ArraySpec(shape=(2,))},
506
+ randomised=True,
507
+ metrics={"reached": sh.MetricSpec(required=True)},
508
+ )
509
+
510
+ def reset(self, seed=0):
511
+ self._steps = 0
512
+ self.position = 0.0
513
+ self.target = float(np.random.default_rng(seed).integers(3, 8))
514
+ return self.observe(("state",))
515
+
516
+ def observe(self, keys):
517
+ if "state" not in keys:
518
+ return {}
519
+ return {"state": np.array([self.position, self.target], dtype="float32")}
520
+
521
+ def step(self, action):
522
+ self.position += float(np.clip(action[0], 0, 1))
523
+ self._steps += 1
524
+ return self.observe(("state",)), sh.StepInfo(done=self.done)
525
+
526
+ @property
527
+ def done(self):
528
+ return self.position >= self.target or super().done
529
+
530
+ def score(self):
531
+ return {"reached": float(self.position >= self.target)}
532
+
533
+ class Advance(sh.PolicyBase):
534
+ def __init__(self, env):
535
+ super().__init__(sh.PolicySpec(
536
+ action_dim=1, control_hz=env.spec.control_hz, expects=("state",),
537
+ ))
538
+
539
+ def act(self, obs):
540
+ position, target = obs["state"]
541
+ return np.array([min(1.0, max(0.0, target - position))], dtype="float32")
542
+
543
+ @sh.environment("counter")
544
+ def counter():
545
+ return Counter()
546
+
547
+ @sh.policy("advance")
548
+ def advance(env):
549
+ return Advance(env)
550
+ ```
551
+
552
+ Save the submission script beside it as `submit_counter.py`. The simulation
553
+ source stays in `counter.py`; this script loads and submits it:
554
+
555
+ ```python
556
+ import simhub as sh
557
+
558
+ with sh.connect() as sim:
559
+ project = sim.project("counter.py")
560
+ batch = project.launch(
561
+ env="counter", policies=["advance"], seeds=(3,),
562
+ task=sh.Task(reached="eq:1"), record=sh.Record(), gpu=False,
563
+ request_id="counter-first-run-1",
564
+ )
565
+ print("Batch:", batch.id)
566
+ print("Open https://simhub-console.pages.dev/ and select Runs.")
567
+ batch.wait()
568
+ for run in batch.runs:
569
+ print(run.id, run.state, run.outcome, run.metrics)
570
+ ```
571
+
572
+ ```bash
573
+ python submit_counter.py
574
+ ```
575
+
576
+ This uploads the single-file project and runs one episode on a hosted CPU.
577
+ Find it in the console under the `counter-demo` environment. It should report
578
+ task success with `reached=1.0`; its metrics, evaluation, and result are stored
579
+ on SimHub. It has no camera or physics body, so it produces no video. Reuse the
580
+ request ID only when recovering the same submission; choose a new ID when you
581
+ intend to run changed code or another episode.
582
+
583
+ A larger factory project uses the same decorators in its package's
584
+ `__init__.py` and the same `simhub.toml` layout described above.
585
+ `sh.Project.load(path)` imports a factory project to discover its registrations;
586
+ typed submission collects source using declarations without importing its
587
+ runtime code.
588
+
589
+ ## Compare policies and vary environments
590
+
591
+ ### Hosted batches for factory projects
592
+
593
+ To compare the counter's policy against a baseline, submit a hosted batch from
594
+ the directory containing `counter.py`, with an API key configured:
595
+
596
+ ```python
597
+ import simhub as sh
598
+
599
+ with sh.connect() as sim:
600
+ project = sim.project("counter.py")
601
+ batch = project.launch(
602
+ env="counter",
603
+ policies=["advance", "simhub:hold"],
604
+ seeds=range(8),
605
+ task=sh.Task(reached="eq:1"),
606
+ record=sh.Record(),
607
+ gpu=False,
608
+ )
609
+ print("Batch:", batch.id)
610
+ batch.wait()
611
+ print(batch.report().table())
612
+ ```
613
+
614
+ This creates 16 episodes: two policies × eight seeds. `simhub:hold` is a built-in
615
+ baseline. Install the `notebook` extra for DataFrame tables and plots. Inside an
616
+ open session, `batch.dataframe()`, `batch.videos()`, and `batch.plot.success()`
617
+ help inspect recorded results; `batch.cancel()` requests cancellation. Resume
618
+ an existing batch with `sim.batch("batch_...")`.
619
+
620
+ For a fixed benchmark, define a suite and evaluation, then use the same project:
621
+
622
+ ```python
623
+ suite = sh.Suite.seeds("counter/v1", range(8))
624
+ evaluation = sh.Eval(
625
+ suite=suite, task=sh.Task(reached="eq:1"),
626
+ primary="success", truncated_is="failure",
627
+ )
628
+ # Inside the session above:
629
+ batch = project.evaluate(
630
+ evaluation, env="counter", policies=["advance", "simhub:hold"],
631
+ record=sh.Record(), gpu=False,
632
+ )
633
+ report = batch.wait().report(evaluation)
634
+ print(report.table())
635
+ print(report.compare("advance", "simhub:hold"))
636
+ ```
637
+
638
+ A suite freezes seeds and parameter overrides, and its content digest identifies
639
+ its version. Reports distinguish coverage, execution failures, checked task
640
+ verdicts, and runner claims. Comparisons are paired across the same suite and
641
+ include uncertainty. Decide explicitly how truncated episodes count.
642
+
643
+ ### Typed experiments
644
+
645
+ For typed projects, use `Variations` and `ExperimentPlan` to validate and freeze
646
+ the entire comparison before submitting. From the `sim/examples` directory
647
+ used in the hosted quickstart:
648
+
649
+ ```python
650
+ from pathlib import Path
651
+ import simhub as sh
652
+ from warehouse_barcode_scan.definitions import Warehouse, BarcodeController, ScanLabels
653
+ from warehouse_barcode_scan.parameters import WarehouseParameters
654
+
655
+ variations = sh.Variations.grid(
656
+ WarehouseParameters,
657
+ base=WarehouseParameters(width=320, height=240, verify_physics=False),
658
+ axes={"carton_color": ("natural", "blue")},
659
+ )
660
+ plan = sh.ExperimentPlan.vary(
661
+ Warehouse(), variations,
662
+ policies=(BarcodeController(),), task=ScanLabels(), seeds=(0, 1),
663
+ limits=sh.RunLimits(max_steps=960, timeout_seconds=300, gpus=1),
664
+ outputs=sh.Outputs(cameras=()),
665
+ budget=sh.ExperimentBudget(
666
+ max_runs=4, max_concurrent_jobs=2, max_gpus=1,
667
+ timeout_seconds=3600, max_gpu_seconds=3600,
668
+ ),
669
+ )
670
+ print(plan.describe()) # Two colors × two replicates × one policy = four episodes
671
+
672
+ with sh.connect() as sim:
673
+ experiment = sim.experiment(
674
+ plan, project="warehouse_barcode_scan", request_id="color-comparison-1",
675
+ )
676
+ experiment.save(Path("experiment.json"))
677
+ experiment.wait()
678
+ print(experiment.report(group_by=("policy", "carton_color")))
679
+ ```
680
+
681
+ `Variations.grid` takes a cross product; `zip` pairs axis entries; `cases`
682
+ accepts explicit parameter values; `sample` draws bounded random cases.
683
+ Portable recipes add serializable distributions, correlated choices, and
684
+ constraints. Rejected cases are recorded, and an unsuccessful bounded sample
685
+ does not silently submit a smaller experiment.
686
+
687
+ `RunLimits` bounds an episode's steps and stage resources. `ExperimentBudget`
688
+ separately bounds the number of episodes, concurrent jobs, GPUs, elapsed time
689
+ including queueing, and reserved GPU seconds. Compilation, simulation,
690
+ rendering, and retries consume the applicable budget. Tenant and fleet limits
691
+ still apply. A plan requires a backend that can enforce its limits.
692
+
693
+ Parameters are treated as compilation inputs by default. Reusing a compiled
694
+ scene across reset-only changes requires explicit `ParameterEffect`
695
+ declarations, runtime ABI 2, and a `VariationRuntime.apply_variation` adapter.
696
+ See [typed experiments](https://github.com/General-Deployment/sim/blob/main/docs/SDK_TYPED_EXPERIMENTS.md)
697
+ and [portable variation recipes](https://github.com/General-Deployment/sim/blob/main/docs/SDK_VARIATION_RECIPES.md)
698
+ for budget accounting, deterministic seed derivation, and reuse rules.
699
+
700
+ ## Recordings, rendering, and datasets
701
+
702
+ Recording is explicit. Factory runs use `sh.Record(cameras=(...), video="hero")`;
703
+ typed requests use `sh.Outputs(cameras=(...), video=sh.VideoOutput(...))`.
704
+ Camera names must exist in the environment. `PolicySpec.expects` controls what
705
+ the policy observes, while recording options can request additional cameras.
706
+ Use `Outputs(cameras=())` to omit additional recording cameras; the default is
707
+ `("hero",)`. A policy's required image observations still have to be rendered.
708
+
709
+ `sh.Record(video="hero")` defaults to **60 fps** for environments with
710
+ `step_with_substeps(action, on_substep)`. `MujocoEnvironment` provides this hook
711
+ when using its built-in `step`; subclasses that override `step` must provide
712
+ their own hook. It runs the normal control step and calls `on_substep(fraction)`
713
+ after each physics substep, ending at `1`. The recorder renders only when an
714
+ output frame is due, using `render(camera)` at the current physics state.
715
+ Custom renderers must not reuse a frame cached for the whole control step.
716
+ Use `sh.Record(video="hero", fps=30)` to choose another presentation rate
717
+ between 1 and 60 fps. Frames are labeled with the completed control step's
718
+ progress and state, including its terminal status.
719
+ The smooth presentation is saved as `film/video.mp4` and selected as the run's
720
+ primary video. Policy-camera recordings retain the control rate and the exact
721
+ images the policy saw; trajectory sampling and policy calls are unchanged.
722
+ Older environments without the hook keep their original control-rate video
723
+ and log that limitation. Typed cinematic requests also default to 60 fps;
724
+ use `sh.VideoOutput(fps=30)` to choose another rate.
725
+
726
+ Depending on the environment and requested outputs, artifacts include:
727
+
728
+ ```text
729
+ metrics.json Final measurements
730
+ result.json Local episode result
731
+ evaluation.json Metric metadata and local task evaluation
732
+ trajectory/meta.json Recording schema, body names, and units
733
+ trajectory/000000.npz Chunks of recorded state, actions, and poses
734
+ video/hero.mp4 Direct camera recording, when requested
735
+ renders/<camera>/*.png Camera frames, when a frame sink is selected
736
+ checkpoints/*.npz Checkpoints, when supported and enabled
737
+ film/video.mp4 Smooth presentation or hosted cinematic output
738
+ film/poster.png Hosted cinematic poster
739
+ film/render-manifest.json Cinematic provenance
740
+ ```
741
+
742
+ Not every environment produces every file. Body trajectories require recorder
743
+ bindings; checkpoints require environment support. `Recorder.channel(name,
744
+ sampler)` adds fixed-shape finite numeric fields. Pose quaternions use **wxyz**,
745
+ and the trajectory metadata records the convention. Direct camera recordings
746
+ preserve what the policy saw; cinematic rendering replays the saved motion.
747
+ The hosted run's stored outcome and checked flag remain authoritative over a
748
+ local evaluation sidecar.
749
+
750
+ After simulation, render a different film without running the policy again:
751
+
752
+ ```python
753
+ from pathlib import Path
754
+ import simhub as sh
755
+
756
+ with sh.connect() as sim:
757
+ run = sh.RunHandle.resume(sim, Path("warehouse-run.json"))
758
+ render = run.render(
759
+ sh.VideoOutput(width=1280, height=720, fps=24, seconds=12),
760
+ request_id="warehouse-film-2",
761
+ )
762
+ render.save(Path("warehouse-render.json"))
763
+ render.wait()
764
+ print(run.video.url)
765
+ ```
766
+
767
+ The recording must contain the scene and trajectory inputs required by the
768
+ renderer. A render cannot add observations that were never recorded.
769
+ Presentation projects can customize cameras, materials, overlays, and
770
+ composition; see [hosted rendering and recovery](https://github.com/General-Deployment/sim/blob/main/docs/SDK_HOSTED_RECOVERY.md).
771
+
772
+ For dataset export, install `simhub[dataset]` and use
773
+ `simhub.datasets.from_trajectories(...)` for local `trajectory/` directories or
774
+ `simhub.datasets.lerobot(batch, ...)` for a completed batch. The current exporter
775
+ writes LeRobot v2.1 state/action data and requires recorded `qpos` and `action`
776
+ channels. Batch export includes complete runs regardless of task success and
777
+ does not copy camera video files. Select appropriate episodes, match the
778
+ recorded control rate, and validate against your training loader. See the
779
+ [dataset export guide](https://github.com/General-Deployment/sim/blob/main/simhub/skills/simhub-datasets/SKILL.md)
780
+ for the complete example and current limits.
781
+
782
+ ## Learned policies and model credentials
783
+
784
+ `sh.OpenPiPolicy` manages a connection to an OpenPI server and executes returned
785
+ action chunks. Subclass it, declare `expects`, and implement
786
+ `encode_observation(obs)`. Override `decode_action(action, obs)` or
787
+ `decode_chunk(actions, inference_obs)` when the checkpoint's output needs
788
+ conversion. Match camera keys, joint ordering, gripper units, action convention,
789
+ and control rate to the checkpoint.
790
+
791
+ The standard hosted Python runtimes include OpenPI's client dependencies.
792
+ For hosted factory runs, pass the endpoint through `policy_kwargs` to your
793
+ factory; it must be reachable from the worker. Install `simhub[openpi]` only
794
+ when using the client locally. See the
795
+ [OpenPI policy guide](https://github.com/General-Deployment/sim/blob/main/simhub/docs/openpi-policies.md)
796
+ for a full adapter, authentication, timeouts, and relative actions.
797
+
798
+ For private Hugging Face checkpoints:
799
+
800
+ ```bash
801
+ simhub model credentials set my-hf-token
802
+ simhub model register --help
803
+ ```
804
+
805
+ The first command reads the token through a hidden prompt. Register a pinned
806
+ policy revision bound to that credential, then select it with
807
+ `project.launch(policy_version_id=...)`. Typed policies use `HuggingFaceModel`
808
+ with a full 40-character revision and a credential ID; token values are never
809
+ part of the definition. See
810
+ [hosted model credentials](https://github.com/General-Deployment/sim/blob/main/simhub/MODEL_CREDENTIALS.md).
811
+
812
+ ### Google Cloud Storage models
813
+
814
+ GCS is a checkpoint storage source; policies still execute on SimHub's
815
+ Lambda/Fleet compute backend. Use SDK, API, worker, and runner images from the
816
+ release containing `GCSModel` and `simhub model resolve`. Older 0.2.0 builds may
817
+ lack these APIs; check `simhub model resolve --help`. The submission SDK needs
818
+ only its normal dependencies. Google libraries run in the trusted API/worker,
819
+ and neither `gcloud` nor `gsutil` is required to resolve or download models.
820
+ See the [server setup](README.md#gcs-model-source-deployment) for deployment
821
+ prerequisites. Legacy Modal and other backends without trusted staging reject
822
+ GCS submissions before execution.
823
+
824
+ Resolve a public checkpoint into a frozen manifest:
825
+
826
+ ```bash
827
+ simhub model resolve \
828
+ gs://gdm-robotics-open-x-embodiment/open_x_embodiment_and_rt_x_oss/rt_1_tf_trained_for_000400120/ \
829
+ --recursive --layout files --out models/rt1-converged.json
830
+ ```
831
+
832
+ `--recursive` requires a nonempty prefix ending in `/`. Without it, the URI
833
+ names exactly one object. Resolution reads metadata, not weights. The resulting
834
+ JSON records the bucket, object names, exact generation strings, byte counts,
835
+ CRC32C checksums, relative destination paths, layout, and model root. Commit
836
+ this manifest with your experiment. Running it always requests those generations;
837
+ if a version disappears, the run fails instead of switching to newer weights.
838
+ Use completed release prefixes: listing is not an atomic snapshot of a directory
839
+ that a publisher is still uploading. Long-term reproducibility also requires
840
+ retaining the pinned objects or mirroring them to a controlled bucket.
841
+
842
+ For private buckets, obtain a service-account JSON key with permission to read
843
+ the intended objects. Exact-object resolution and downloads need
844
+ `storage.objects.get`; prefix expansion also needs `storage.objects.list`.
845
+ A bucket-scoped Storage Object Viewer role supplies both. Import the key once
846
+ through a file or stdin; never put key contents in your source or a command-line
847
+ argument:
848
+
849
+ ```bash
850
+ simhub model credentials set research-gcs \
851
+ --provider gcs --service-account-file /secure/research-reader.json
852
+ simhub model credentials list
853
+ # Replace gcsc_returnedid with the opaque ID returned above.
854
+ simhub model resolve gs://my-policy-bucket/releases/policy.zip \
855
+ --layout zip --root policy --credential gcsc_returnedid \
856
+ --out models/private-policy.json
857
+ ```
858
+
859
+ The imported key is encrypted and scoped to your organization. The manifest
860
+ contains only the credential revision ID. The trusted worker uses it to stage
861
+ files; policy code receives no Google key or access token. Omitting
862
+ `--credential` means anonymous access, even when your laptop has a Google login.
863
+ The initial private-access format is a `service_account` JSON key with Google's
864
+ standard OAuth token endpoint. Federation, signed URLs, Requester Pays billing,
865
+ and custom storage endpoints are not supported.
866
+
867
+ To rotate access, import a new key revision, resolve a manifest using its ID,
868
+ and register a new policy revision. Revoke old access with
869
+ `simhub model credentials revoke gcsc_oldid`. Revocation prevents new staging
870
+ and is checked again after downloads; it cannot retract bytes from an already
871
+ running policy. Do not delete an old object generation if experiments still
872
+ need it.
873
+
874
+ For buckets where listing is disallowed, pass a JSON array of exact names in
875
+ `--objects-file`, with `gs://bucket` as the URI. Names are retained as relative
876
+ destinations, so `--root` can select their shared model directory:
877
+
878
+ ```bash
879
+ simhub model resolve gs://my-policy-bucket \
880
+ --objects-file checkpoint-objects.json --root releases/policy \
881
+ --credential gcsc_returnedid --out models/private-policy.json
882
+ ```
883
+
884
+ Declare the resolved manifest in an existing typed project. Here `environment`, `task`,
885
+ `ControllerParameters`, and `Controller` are your project definitions:
886
+
887
+ ```python
888
+ from pathlib import Path
889
+ import simhub as sh
890
+
891
+ model = sh.GCSModel.model_validate_json(
892
+ Path("models/private-policy.json").read_text()
893
+ )
894
+ policy = sh.PolicyDefinition(
895
+ name="gcs-policy",
896
+ parameters=ControllerParameters(),
897
+ runtime=sh.SourceRef.of(Controller),
898
+ observations=environment.observations,
899
+ action=environment.action,
900
+ control_hz=environment.control_hz,
901
+ model=model,
902
+ )
903
+ # Use this policy in your RunRequest; session.run registers the binding.
904
+ request = sh.RunRequest(environment=environment, policy=policy, task=task)
905
+ with sh.connect() as session:
906
+ run = session.run(request, project=Path("my_project"), request_id="gcs-trial-001")
907
+ ```
908
+
909
+ Include a manifest loaded by project code in the source bundle's `include`
910
+ patterns. Resolve paths relative to the module when reading inside your project,
911
+ for example `Path(__file__).parent / "models/private-policy.json"`. Project
912
+ inventory embeds the resolved source in `PolicyDefinition.model`; no separate
913
+ TOML model registry is needed. Current hosted model policies must declare
914
+ `observation/state` in addition to any image observations.
915
+
916
+ Your typed policy constructor receives `model: sh.GCSModel` and a
917
+ `RuntimeContext` whose `model_path` is the verified local model directory. Load
918
+ TensorFlow, PyTorch, JAX, or other formats from that path using your policy's
919
+ own loader. Decorator-based factories receive `model` as a contract dictionary
920
+ and `model_path` as a local path string. Both inputs are bound to the registered
921
+ revision and cannot be overridden through `policy_kwargs`.
922
+
923
+ For an existing decorator-based policy, register and launch explicitly:
924
+
925
+ ```bash
926
+ simhub model register gcs-policy --contract models/private-policy.json \
927
+ --project-policy gcs-policy --observation-dim 8 --action-dim 7 --control-hz 3
928
+ ```
929
+
930
+ ```python
931
+ # project is your existing HostedProject; use the version ID returned above.
932
+ batch = project.launch(policy_version_id="pvr_returnedid")
933
+ ```
934
+
935
+ Python clients can use `session.client.resolve_model_source(...)`,
936
+ `set_gcs_credential(name, service_account_dict)`, and
937
+ `register_model_policy(...)` for the same workflow. Existing
938
+ `register_hf_policy(...)`, `HuggingFaceModel`, HF dependencies, and HF credential
939
+ commands retain their behavior and policy digests.
940
+
941
+ `layout="files"` preserves the manifest's relative paths. `layout="zip"`
942
+ requires one pinned ZIP, verifies its bytes before extraction, and uses `root`
943
+ to select the directory inside it. The default root is `.`. Inputs are mounted
944
+ read-only and staged separately for each run; there is no shared download cache.
945
+ Limits are 1 MiB of contract JSON, 1,024 selected objects, 20 GiB downloaded,
946
+ 20 GiB extracted, 10,000 ZIP entries, and 16 MiB of ZIP directory metadata.
947
+ ZIP links, traversal, encryption,
948
+ special files, and colliding destinations are rejected. Disk headroom and a
949
+ bounded staging deadline also apply.
950
+
951
+ Common setup failures:
952
+
953
+ - **Access denied:** check the credential revision, bucket permissions, and
954
+ whether a prefix requires list permission. Ambient Google credentials are
955
+ intentionally ignored.
956
+ - **Generation unavailable:** restore the pinned version or explicitly resolve
957
+ and register a new release. A retry never chooses the latest bytes.
958
+ - **Verification/layout failure:** check object checksums and sizes, safe file
959
+ paths, ZIP contents, limits, and the selected root directory.
960
+ - **Capability unavailable:** deploy matching Fleet API/worker/runner and node
961
+ configuration. Google libraries belong in the trusted services, while model
962
+ inference dependencies must be baked into the offline policy image.
963
+
964
+ [SIMPLER example manifests](examples/gcs_models/README.md) pin the three RT-1
965
+ training-stage checkpoints and RT-1-X ZIP. Keep Octo on a pinned HF revision and
966
+ record all model digests with ranking results. These declarations download
967
+ checkpoints; RT-1/Octo adapters, language encoders/tokenizers, the Google Robot
968
+ environment, and observation/action parity are separate requirements before
969
+ comparing policy ordering. Auxiliary assets must be in the declared bundle or
970
+ pinned inference image; GCS models currently contain one file bundle or archive.
971
+
972
+ ## Debug and recover runs
973
+
974
+ Persist a request ID **before** submitting if your application must recover
975
+ from a lost response. Reuse the same ID and identical inputs after
976
+ `SubmissionUncertain`; changing the ID requests new work. Use a new ID for an
977
+ intentional change to source, parameters, or outputs. Save the returned receipt
978
+ immediately. Receipts contain IDs and the API address, not an API key.
979
+
980
+ ```python
981
+ from pathlib import Path
982
+ import simhub as sh
983
+
984
+ with sh.connect() as sim:
985
+ run = sh.RunHandle.resume(sim, Path("warehouse-run.json"))
986
+ for stage in run.stages:
987
+ print(stage.kind, stage.state, stage.attempts, stage.job_id)
988
+ try:
989
+ run.wait(outputs=True, timeout=3600)
990
+ except sh.HostedExecutionError as error:
991
+ print(error.failure.model_dump_json(indent=2))
992
+ print("Inspect job:", error.job_id)
993
+ ```
994
+
995
+ `run.wait(outputs=False)` waits for simulation and required evaluation while
996
+ allowing a requested film to remain unfinished. `outputs=True` also requires
997
+ requested delivery. A client wait timeout does not cancel remote work. Resume
998
+ with the same API origin and credentials; keep the session open while using
999
+ its handles. `run.events()` yields typed execution events.
1000
+
1001
+ Use `run.cancel()` or `experiment.cancel()` to request cancellation and inspect
1002
+ the resulting stage states. Cancellation acknowledgement alone does not prove
1003
+ that all remote containers have stopped. A render receipt can be resumed with
1004
+ `sh.RenderHandle.resume`; an experiment receipt with
1005
+ `sh.ExperimentHandle.resume`.
1006
+
1007
+ Fetch saved logs by job ID or by environment:
1008
+
1009
+ ```bash
1010
+ simhub logs job_... --tail 100
1011
+ simhub logs --env warehouse-barcode-scan --tail 100
1012
+ simhub logs job_... --json > job-logs.json
1013
+ ```
1014
+
1015
+ ```python
1016
+ with sh.connect() as sim:
1017
+ logs = sim.job_logs("job_...", tail=100)
1018
+ for log in logs.logs:
1019
+ print(log.path, log.text)
1020
+ ```
1021
+
1022
+ `sim.environment_logs(name_or_id, tail=100)` provides the environment form in
1023
+ Python. It selects the newest submitted job across all versions and job kinds,
1024
+ including queued and running jobs. It does not fall back to an older job if the
1025
+ newest has no saved logs. These are snapshots of retained logs and structured
1026
+ errors, not a live stream. Logs commonly become available after an attempt ends.
1027
+ `tail` trims each log after download.
1028
+
1029
+ Common starting points for a failure:
1030
+
1031
+ - **Import or missing-file errors:** check the package name, entry points,
1032
+ manifest `include` patterns, and runtime dependency lists.
1033
+ - **Schema errors:** compare the environment and policy action, observation,
1034
+ and control-rate declarations; check task metric names.
1035
+ - **Missing camera or output:** declare the camera and recorder bindings, or
1036
+ remove an output you do not need. Inspect the render stage separately from
1037
+ the simulation result.
1038
+ - **Unsupported capability or budget refusal:** inspect the plan and deployment
1039
+ catalog. Installing a local extra does not add a hosted capability.
1040
+ - **Authentication errors:** confirm the API address and that the key belongs
1041
+ to the intended deployment and organization.
1042
+
1043
+ ### Job retries
1044
+
1045
+ New simulations default to three attempts. `max_attempts` accepts 1–10 when
1046
+ submitting a job. Failures marked retryable use exponential backoff with jitter
1047
+ (2.5–5 seconds initially, capped at five minutes). Invalid inputs and ambiguous
1048
+ container launch failures stop immediately. Deployment admission pauses wait
1049
+ without consuming an attempt, and expired worker leases cannot execute beyond
1050
+ the attempt budget.
1051
+
1052
+ The job's `summary.retry` records `next_attempt_at` and the most recent 50 failed
1053
+ attempts, including their timestamps and errors. The console shows this history
1054
+ and retry timing on run pages. The jobs menu also lists recent failed jobs,
1055
+ including compiles and failures before run creation, with the same recovery
1056
+ controls. A pending retry clears the earlier verdict and metrics from the live
1057
+ run; those remain available in that attempt's saved artifacts. Logs and artifacts from earlier attempts remain
1058
+ available. A simulation waiting to retry is pending, and cancellation during
1059
+ that wait stops delivery.
1060
+
1061
+ After inspecting a retryable failure, grant that job one additional attempt:
1062
+
1063
+ ```python
1064
+ import simhub as sh
1065
+
1066
+ with sh.connect() as sim:
1067
+ logs = sim.job_logs("job_...")
1068
+ job = sim.retry_job(logs.job.id, expected_attempt=logs.job.attempts)
1069
+ ```
1070
+
1071
+ The equivalent request is `POST /api/jobs/{job_id}/retry` with
1072
+ `{"expected_attempt": 3}` for a job whose last observed attempt was 3. Repeating
1073
+ the same request does not launch duplicate work or replenish the budget again,
1074
+ even if its response was lost. Stale attempts and cancelled, successful, live,
1075
+ or non-retryable jobs are rejected. An exhausted worker lease requires execution
1076
+ reconciliation first. This retries one job; cancelled downstream jobs and
1077
+ cancelled batches are not restarted.
1078
+
1079
+
1080
+ ## Pull, edit, preview, and share
1081
+
1082
+ To edit an environment already stored on the service:
1083
+
1084
+ ```bash
1085
+ simhub pull warehouse-pick ./warehouse_pick
1086
+ # Edit the source in ./warehouse_pick.
1087
+ simhub push ./warehouse_pick --message "Update the picking scene"
1088
+ ```
1089
+
1090
+ `push` publishes a source version to hosted SimHub, where it is available in the
1091
+ console. To execute it as well, submit a typed request or use
1092
+ `sim.project(...).launch(...)`; publishing source alone does not start a run.
1093
+
1094
+ `pull` accepts an environment name or ID. It downloads the latest version by
1095
+ default; use `--version current`, a version number, or a version ID to select
1096
+ another. The destination must be new or empty. Pull verifies checksums and
1097
+ restores editable source and retained named assets without importing source or
1098
+ installing dependencies. A compiled-only version cannot be pulled as editable
1099
+ Python; an older source version is not silently substituted.
1100
+
1101
+ Keep `.simhub-package` and `.simhub-checkout.json` with the checkout. They retain
1102
+ package identity, API origin, environment, and parent version. Push refuses a
1103
+ stale checkout; pull into another directory and reapply edits. A concurrent
1104
+ push may create a branch with `current: false`. Typed checkouts retain their
1105
+ published definitions and parameters; submit a new typed request to change
1106
+ those parameter values.
1107
+
1108
+ The Python equivalents are `sim.pull("warehouse-pick", into="warehouse_pick")`
1109
+ and `sim.project("warehouse_pick").push("Update the picking scene")`.
1110
+
1111
+ For a factory project with an environment named `warehouse-pick`, publish an
1112
+ initial scene preview without stepping its policy:
1113
+
1114
+ ```bash
1115
+ simhub launch ./warehouse_pick --preview --env warehouse-pick --seed 7
1116
+ ```
1117
+
1118
+ In Python, use `sim.project(path).preview(env="warehouse-pick", seed=7)`.
1119
+ Preview generation is hosted compilation and requires a compatible GLB compiler.
1120
+ It builds and resets the environment and can incur compute charges. The console
1121
+ shows stored previews and run snapshots without starting another compile merely
1122
+ because you open them.
1123
+
1124
+ Run pages are organization-scoped. When you want to give someone access without
1125
+ an account, explicitly create a revocable, expiring public share:
1126
+
1127
+ ```python
1128
+ # Inside the session, with a completed run handle:
1129
+ print(run.share(expires_in_days=7).url)
1130
+ ```
1131
+
1132
+ Treat that URL as access to the scoped results. Never put an organization API
1133
+ key in a shared URL.
1134
+
1135
+ ## Guidance for coding agents
1136
+
1137
+ Default to hosted submission and console-visible results. Use local simulation
1138
+ only when the user explicitly requests local debugging. Do not replace a hosted
1139
+ run with a local rollout when credentials or hosted capabilities are missing;
1140
+ report the missing prerequisite.
1141
+
1142
+ Use this sequence when implementing an integration:
1143
+
1144
+ 1. Check the installed SDK version and choose typed hosted definitions for a
1145
+ new hosted project, or preserve an existing factory project's API.
1146
+ 2. Define parameter models, observation/action shapes and units, control rate,
1147
+ required metrics, and task predicates before implementing the runtime.
1148
+ 3. Keep every referenced module and asset in the source bundle. Keep runtime
1149
+ construction out of definition imports. Use the hosted runtime's bundled
1150
+ dependencies and declare only additional project-specific libraries.
1151
+ 4. Validate definitions without starting a simulator, inspect `sim.plan(...)`
1152
+ for typed requests, and set explicit output and resource limits. Submit a
1153
+ small hosted run before expanding to a batch.
1154
+ 5. Persist a stable request ID, submit, and save the receipt. Resume existing
1155
+ work after a timeout or uncertain response instead of creating duplicates.
1156
+ 6. Direct the user to the run in the SimHub console. Report execution state,
1157
+ checked task outcome, metrics, and requested artifact delivery separately.
1158
+ Inspect failed stages and saved logs before retrying.
1159
+ 7. Freeze cases and seeds for comparisons. Use experiment budgets for typed
1160
+ sweeps, and record the source version, plan, and receipt with results.
1161
+
1162
+ The SDK includes **15 portable agent skills** covering setup, typed runs,
1163
+ scene composition, variations, assets, policies, tasks, debugging, evaluation,
1164
+ recording, Blender, and datasets. Installing the Python package does not
1165
+ activate skills in an agent. Copy complete skill directories from the wheel or
1166
+ repository into your agent's skill folder and reload the agent session. Follow
1167
+ [installation instructions](https://github.com/General-Deployment/sim/blob/main/simhub/skills/README.md#install-for-your-agent)
1168
+ for the copy commands. Start with `$simhub-typed-runs` for hosted work or
1169
+ `$simhub-projects` for factory authoring.
1170
+
1171
+ For exact signatures and additional examples:
1172
+
1173
+ - [Typed contracts](https://github.com/General-Deployment/sim/blob/main/simhub/src/simhub/contracts.py)
1174
+ and [runtime base classes](https://github.com/General-Deployment/sim/blob/main/simhub/src/simhub/base.py).
1175
+ - [Hosted plans, receipts, cancellation, and rerendering](https://github.com/General-Deployment/sim/blob/main/docs/SDK_HOSTED_RECOVERY.md).
1176
+ - [Parameter schema generation](https://github.com/General-Deployment/sim/blob/main/docs/SDK_TYPED_PARAMETERS.md)
1177
+ from an immutable published version.
1178
+ - [Agent skill index](https://github.com/General-Deployment/sim/blob/main/simhub/skills/README.md)
1179
+ with focused workflows and implementation limits.
1180
+ - [Repository examples](https://github.com/General-Deployment/sim/tree/main/examples),
1181
+ including robotics, Blender sets, and the specialized
1182
+ [single-file ceiling simulation](https://github.com/General-Deployment/sim/blob/main/examples/asbestos_ceiling.py).
1183
+
1184
+ ## Optional local debugging
1185
+
1186
+ Local execution is available when explicitly needed and requires the project's
1187
+ engine dependencies on your machine. For factory projects, `simhub launch
1188
+ PROJECT --check` runs scene checks and `simhub launch PROJECT --episode` runs a
1189
+ local episode. In Python, `sh.check(...)` checks a scene and the top-level
1190
+ `sh.run(...)` executes registered factories locally. These operations do not
1191
+ publish runs to the console; use the hosted submission APIs above for that.
1192
+ To enable `project.run_local(...)` through a session, explicitly opt in with
1193
+ `sh.connect(hosted_only=False)`. The default connection rejects local execution,
1194
+ including the deprecated `project.run(...)` alias.
1195
+ The [local debugging guide](https://github.com/General-Deployment/sim/blob/main/simhub/skills/simhub-local-debugging/SKILL.md)
1196
+ documents setup, registration, camera checks, and local output files.