armnet-client API Reference¶
armnet_client
¶
armnet client SDK — submit jobs to the platform.
Top-level surface:
- :func:
execute— submit one job, block until done. - :func:
execute_many— submit many in parallel, stream results. - :func:
execute_local— run a@mainscript in the current Python process; bypasses Docker, NATS, and the orchestrator. - :class:
Image— build a Docker image with content-hash caching. - :class:
OrchestratorClient— lower-level HTTP wrapper.
Wire types are re-exported from :mod:armnet_core for ergonomics.
OrchestratorClient
¶
Synchronous client for the orchestrator HTTP API.
Defaults to Armnet's production API and the ARMNET_API_KEY environment
variable. Pass base_url or api_key explicitly for development and
custom integrations.
Source code in client/src/armnet_client/client.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
close
¶
close() -> None
Source code in client/src/armnet_client/client.py
71 72 | |
submit
¶
submit(spec: JobSpec, *, allow_queue: bool = True) -> Job
POST /jobs — create a new job.
The returned :class:Job carries dispatched: True if it was sent to
a cell immediately, False if it was accepted but queued.
allow_queue (default True): when no matching cell is free, queue the
job (True) or have the orchestrator refuse it with HTTP 409 (False, for
callers that only make sense running immediately).
Source code in client/src/armnet_client/client.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
get
¶
get(job_id: str) -> Job
GET /jobs/{id} — fetch the current state of a job.
Source code in client/src/armnet_client/client.py
98 99 100 101 102 | |
list_jobs
¶
list_jobs(*, limit: int = 10, queued_only: bool = False) -> list[Job]
GET /jobs — the current user's most-recent jobs (newest first).
Source code in client/src/armnet_client/client.py
104 105 106 107 108 109 110 111 112 | |
get_status_transitions
¶
get_status_transitions(job_id: str) -> list[JobStatusTransition]
GET /jobs/{id}/status-transitions — a job's status-change audit trail.
Source code in client/src/armnet_client/client.py
114 115 116 117 118 | |
stop_job
¶
stop_job(job_id: str, *, reason: str = 'client requested stop') -> Job
POST /jobs/{id}/stop — request an immediate stop of a running job.
Used when the operator intentionally aborts (e.g. Ctrl-C): this cancels the job now rather than waiting out the orchestrator's log-disconnect grace window.
Source code in client/src/armnet_client/client.py
120 121 122 123 124 125 126 127 128 129 | |
get_cell_status
¶
get_cell_status(*, embodiment: str = DEFAULT_EMBODIMENT, task: Optional[str] = None) -> CellStatusSummary
GET /cells/status — fetch current availability for a task.
Source code in client/src/armnet_client/client.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
get_registry_credentials
¶
get_registry_credentials() -> RegistryCredentials
POST /registry/credentials — fetch fresh image-registry credentials.
Used by :meth:armnet_client.Image.push to get a short-lived
OAuth2 token for pushing customer images to the platform's
Artifact Registry repo. The orchestrator returns 503 if it isn't
configured for credential issuance (typical for local dev).
Source code in client/src/armnet_client/client.py
146 147 148 149 150 151 152 153 154 155 156 | |
whoami
¶
whoami() -> WhoAmI
GET /whoami — fetch the username for the current API key.
Source code in client/src/armnet_client/client.py
158 159 160 161 162 163 | |
list_secrets
¶
list_secrets() -> SecretList
GET /secrets — list secret names for the current user.
Source code in client/src/armnet_client/client.py
165 166 167 168 169 170 | |
create_secret
¶
create_secret(name: str, value: str) -> SecretInfo
POST /secrets — create or replace one secret value.
Source code in client/src/armnet_client/client.py
172 173 174 175 176 177 178 179 180 | |
delete_secret
¶
delete_secret(name: str) -> None
DELETE /secrets/{name} — delete one user secret if it exists.
Source code in client/src/armnet_client/client.py
182 183 184 185 186 | |
get_volume_credentials
¶
get_volume_credentials() -> VolumeCredentials
POST /volume/credentials — fetch short-lived GCS volume credentials.
Source code in client/src/armnet_client/client.py
188 189 190 191 192 193 | |
wait
¶
wait(job_id: str, poll_interval_seconds: float = 1.0, deadline_seconds: Optional[float] = None) -> Job
Poll GET /jobs/{id} until the job reaches a terminal state.
Returns the final :class:Job. Raises :class:TimeoutError if
deadline_seconds elapses first.
Source code in client/src/armnet_client/client.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
OrchestratorError
¶
Bases: RuntimeError
Raised when the orchestrator returns a non-2xx response.
status_code is the HTTP status returned by the orchestrator;
None if the request never got a response (e.g. connection refused).
Callers can branch on it to distinguish e.g. "endpoint not implemented"
(404) or "configured-but-unavailable" (503) from real failures.
Source code in client/src/armnet_client/client.py
35 36 37 38 39 40 41 42 43 44 45 46 | |
Image
dataclass
¶
A reference to a Docker image, either pre-existing or built locally.
Most customer code constructs one with :meth:build. To wrap an
existing registry-pushed image, use :meth:from_ref or just pass the
image string directly to :func:execute.
Source code in client/src/armnet_client/image.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 | |
from_ref
classmethod
¶
from_ref(ref: str) -> 'Image'
Source code in client/src/armnet_client/image.py
152 153 154 | |
validate_for_current_user
¶
validate_for_current_user(*, if_possible: bool = False) -> 'Image'
Fast client-side ownership check; the orchestrator remains authoritative.
In pure-local development the registry endpoint may be unavailable.
if_possible=True preserves that workflow while production callers
still get immediate feedback for another user's or external image.
Source code in client/src/armnet_client/image.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
planned_ref
classmethod
¶
planned_ref(dockerfile: Union[str, Path], name: str, context_dir: Union[str, Path, None] = None, *, platform: Optional[str] = None, build_contexts: Optional[Mapping[str, Union[str, Path]]] = None) -> str
The registry ref :meth:build then :meth:push would produce.
Worked out from the source alone: the tag is a hash of the build context, so the answer needs no Docker daemon and no build. It does need the registry credentials, since only they say which namespace the image lands in.
This is what lets a caller ask "is this already pushed?" before spending twenty minutes producing a byte-identical image.
Source code in client/src/armnet_client/image.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
exists_in_registry
staticmethod
¶
exists_in_registry(ref: str) -> bool
Whether ref is already in the registry.
Asks the registry's manifest endpoint directly with the same short-lived token a push would use, so it costs one request and needs no Docker.
Source code in client/src/armnet_client/image.py
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | |
push
¶
push(*, if_possible: bool = False) -> 'Image'
Push this image to the platform's registry; return a new Image whose ref points at the registry.
Flow:
- Fetch short-lived OAuth2 credentials from the orchestrator's
POST /registry/credentialsendpoint (cached on disk; refreshed when within 5 minutes of expiry). docker login <registry> -u oauth2accesstoken --password-stdin.docker tag <local> <registry>/<namespace>/<local>.docker pushthe new tag.- Return
Image(ref=<full registry ref>).
Customers don't need any GCP credentials; the orchestrator does the IAM dance on their behalf via service-account impersonation.
For pure-local M0.5 dev where the orchestrator + cell share a
docker daemon and pushing isn't necessary, pass if_possible=True
— the method then logs a warning and returns self unchanged
if the orchestrator can't issue credentials, instead of raising.
Real docker push failures (network, auth, etc.) always raise.
Source code in client/src/armnet_client/image.py
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | |
build
classmethod
¶
build(dockerfile: Union[str, Path], name: str, context_dir: Union[str, Path, None] = None, force_rebuild: bool = False, platform: Optional[str] = None, build_contexts: Optional[Mapping[str, Union[str, Path]]] = None, cache_from: Optional[Iterable[str]] = None, cache_to: Optional[Iterable[str]] = None) -> 'Image'
Build a Docker image and return a content-addressed reference.
:param dockerfile: Path to the Dockerfile.
:param name: Image name (the part before the tag); used as a
human-readable prefix on the resulting tag.
:param context_dir: Build context directory. Defaults to the
Dockerfile's parent.
:param force_rebuild: If True, always invoke docker build even
when an image with the computed content-hash tag already
exists.
:param platform: Optional Docker platform, e.g. linux/arm64/v8 or
linux/amd64. Defaults to ARMNET_DOCKER_PLATFORM when
set. Otherwise an x86 host uses Docker's native platform, and
any other host (Apple Silicon included) builds linux/amd64
so the image can run on a cell.
:param build_contexts: Optional named build contexts forwarded to
docker build --build-context name=path. The Dockerfile can
then reference them via COPY --from=name .... Useful for
pulling in source trees that live outside the primary build
context (e.g. a sibling checkout on the build host). Contents
participate in the content hash so changes invalidate the cache.
:param cache_from: Optional BuildKit --cache-from specs (e.g.
["type=gha,scope=my-image"]). When any cache is requested the
build runs via docker buildx build --load so layers can be
reused across machines/CI runs. Falls back to a plain
docker build (no cache) if buildx or the cache backend is
unavailable, so enabling caching can never break a build.
:param cache_to: Optional BuildKit --cache-to specs (e.g.
["type=gha,scope=my-image,mode=max"]).
:returns: An :class:Image whose ref is
<name>:armnet-<short-hash>.
As a convenience for CI, setting ARMNET_DOCKER_BUILDX_CACHE=gha in
the environment enables GitHub-Actions layer caching (scoped per image
name) without passing cache_from / cache_to explicitly.
Source code in client/src/armnet_client/image.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 | |
ImageBuildError
¶
Bases: RuntimeError
Raised when docker build fails.
Source code in client/src/armnet_client/image.py
114 115 | |
ImageOwnershipError
¶
Bases: ValueError
A prebuilt image is outside the authenticated customer's namespace.
Source code in client/src/armnet_client/image.py
134 135 | |
ImagePushError
¶
Bases: RuntimeError
Raised when docker login / docker tag / docker push fails.
Source code in client/src/armnet_client/image.py
118 119 | |
RegistryCredentialsUnavailableError
¶
Bases: RuntimeError
Raised when the orchestrator can't issue image-registry credentials.
This is the recoverable shape of an :meth:Image.push failure — the
orchestrator either doesn't have the endpoint (404), isn't configured
for credential issuance (503), or isn't reachable at all (connection
refused, typical for pure-local M0.5 dev). Callers can pass
if_possible=True to :meth:Image.push to log + fall back to the
local image ref instead of propagating this.
Source code in client/src/armnet_client/image.py
122 123 124 125 126 127 128 129 130 131 | |
LocalContext
dataclass
¶
Developer-facing context for local Docker execution.
This is intentionally smaller than RobotCellConfig. It describes the
runtime values a containerized program needs, without exposing operator-only
cell settings such as NATS, cell IDs, or completion model configuration.
Source code in client/src/armnet_client/local_container.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
camera_configs
class-attribute
instance-attribute
¶
camera_configs: Mapping[str, Any] = field(default_factory=dict)
calibration_dir
class-attribute
instance-attribute
¶
calibration_dir: Optional[Union[str, Path]] = None
calibration_file_path
class-attribute
instance-attribute
¶
calibration_file_path: Optional[Union[str, Path]] = None
language_instruction
class-attribute
instance-attribute
¶
language_instruction: Optional[str] = None
TeleoperatorConfig
dataclass
¶
How to connect to the local leader arm being used to teleoperate.
Mirrors what LeRobot needs to construct a teleoperator, plus the sample
rate. id is the LeRobot teleoperator id used to find calibration.
Source code in client/src/armnet_client/teleop_stream.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
yam_left_gravity_profile
class-attribute
instance-attribute
¶
yam_left_gravity_profile: Optional[str] = None
yam_right_gravity_profile
class-attribute
instance-attribute
¶
yam_right_gravity_profile: Optional[str] = None
yam_gravity_assist_gain
class-attribute
instance-attribute
¶
yam_gravity_assist_gain: Optional[float] = None
yam_gravity_current_limit_ma
class-attribute
instance-attribute
¶
yam_gravity_current_limit_ma: Optional[int] = None
yam_gravity_joint_signs
class-attribute
instance-attribute
¶
yam_gravity_joint_signs: Optional[tuple[int, ...]] = None
yam_gravity_joint_offsets
class-attribute
instance-attribute
¶
yam_gravity_joint_offsets: Optional[tuple[float, ...]] = None
yam_gravity_link_masses
class-attribute
instance-attribute
¶
yam_gravity_link_masses: Optional[tuple[float, ...]] = None
yam_gravity_joints
class-attribute
instance-attribute
¶
yam_gravity_joints: Optional[tuple[str, ...]] = None
yam_gravity_joint_ranges
class-attribute
instance-attribute
¶
yam_gravity_joint_ranges: Optional[tuple[float, ...]] = None
yam_gripper_return_current_ma
class-attribute
instance-attribute
¶
yam_gripper_return_current_ma: Optional[int] = None
execute_many
¶
execute_many(specs: Sequence[Mapping[str, Any]], *, max_parallel: int = 4) -> Iterator[JobResult]
Submit many jobs in parallel; yield :class:JobResult as each completes.
specs is a sequence of kwarg-dicts, each accepted by
:func:execute (e.g. [{"image": ..., "embodiment": ..., "task": ...,
"args": {...}}, ...]). Results are yielded in completion order, not
submission order — match them up via your spec's own identifying
field if you need to.
max_parallel caps the number of concurrent submissions. Backed by
a thread pool; safe to use from synchronous code.
Source code in client/src/armnet_client/execute.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
execute_local
¶
execute_local(main_module: Union[str, Path], *, args: Optional[Mapping[str, Any]] = None, port: Optional[str] = None, robot_id: Optional[str] = None, calibration_dir: Optional[Union[str, Path]] = None, calibration_file_path: Optional[Union[str, Path]] = None, camera_configs: Optional[Mapping[str, Any]] = None, embodiment: str = DEFAULT_EMBODIMENT, task: str = DEFAULT_TASK, timeout_seconds: int = 120, job_id: Optional[str] = None) -> JobResult
Run a @main-decorated script in the current Python process.
:param main_module: Path to the Python file containing exactly one
@main-decorated function.
:param args: Mapping passed through as ctx.args.
:param port: Value placed at ctx.cell.robot_port — typically the path
to a real local serial device, e.g. "/dev/ttyUSB0".
:param robot_id: Value placed at ctx.cell.robot_id — typically the
name of the robot, e.g. "praveen_so101".
:param embodiment: Recorded on ctx.embodiment (defaults to the
only embodiment supported in M0.5).
:param task: Recorded on ctx.task (defaults to the only task
supported in M0.5).
:param timeout_seconds: Recorded on ctx.timeout_seconds. Not
enforced by execute_local (see module docstring).
:param job_id: Optional explicit job id for ctx.job_id. Defaults
to a stable synthetic id derived from the script path.
:returns: A :class:JobResult with return_value set to whatever
@main returned, or error populated with a traceback if it
raised.
Source code in client/src/armnet_client/local.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
execute_local_container
¶
execute_local_container(*, image: Union[str, Image], local_context: Optional[LocalContext] = None, args: Optional[Mapping[str, Any]] = None, embodiment: str = DEFAULT_EMBODIMENT, task: str = DEFAULT_TASK, timeout_seconds: int = 120, cell_socket: Optional[str] = None, cell_endpoint: Optional[str] = None, robot_id: Optional[str] = None, calibration_dir: Optional[Union[str, Path]] = None, calibration_file_path: Optional[Union[str, Path]] = None, cell_config: Optional[RobotCellConfig] = None, cell_config_path: Optional[Union[str, Path]] = None, mount_calibration: bool = True, username: str = 'remoterobo-test', job_id: str = 'job_local_container', stream_output: bool = True, docker_gpus: Optional[str] = None, docker_runtime: Optional[str] = None, gpus: Optional[str] = None) -> JobResult
Run a runtime image locally through Docker.
This is the M2 local-dev path for robot examples: it validates the same container + armnet-runtime + Unix-socket route as remote execution, without going through NATS or the cloud orchestrator.
Source code in client/src/armnet_client/local_container.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
init_rerun
¶
init_rerun(application_id: str = 'armnet', *, spawn: bool = True, serve_web: bool = False, web_port: Optional[int] = None, grpc_port: Optional[int] = None, open_browser: bool = True, flush_tick_seconds: Optional[float] = None) -> Any
Initialize Rerun and start a viewer.
Thin convenience wrapper so orchestrate scripts don't need to import the
Rerun SDK directly. Raises a clear error if rerun-sdk isn't installed
(pip install 'armnet-client[viz]').
Viewer selection:
serve_web=Truealways hosts the web viewer over HTTP (open the printed URL in a browser; forward the port first if you're on a remote host).spawn=True(default) opens a native desktop window — but only when a display is available. On a headless machine (noDISPLAY/ Wayland, e.g. SSH or a GUI-less workstation) it automatically falls back to the web viewer instead of failing to open a window.
flush_tick_seconds sets Rerun's micro-batching flush interval (the
RERUN_FLUSH_TICK_SECS knob). A very small value (e.g. 0.002 = 2ms)
minimizes how long logged data waits before being sent to the viewer —
noticeably snappier for teleoperation. Left unset, Rerun's default applies.
Source code in client/src/armnet_client/rerun_stream.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
start_recording_key_listener
¶
start_recording_key_listener(events: 'queue.Queue[str]')
Bind LeRobot's dataset-recording keyboard shortcuts to events.
Right Arrow → next_episode, Left Arrow → rerecord_episode, Esc →
stop_recording. Each press is enqueued for :func:stream_job_teleop
to forward to the cell. Esc does not tear anything down client-side: the
runtime ends the session on receipt and the job result unblocks the
orchestrate script, which then stops its streams.
Linux reads the kernel input device via evdev, because pynput only
hears keys through X11 and a Wayland session never delivers them. macOS
keeps pynput. Returns the started listener (call .stop() when
done), or None when no keyboard is available.
Source code in client/src/armnet_client/teleop_stream.py
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | |
stream_job_teleop
¶
stream_job_teleop(job_id: str, stop: Event, config: TeleoperatorConfig, events: 'queue.Queue[str] | None' = None) -> None
Sample the local leader and push actions to job_id until stop is set.
Intended to run on a background thread (alongside the rerun stream). Returns quietly if no API key is configured, LeRobot is missing, or the leader/ websocket can't be reached, so teleop never crashes the orchestrate script.
events, when given, is a queue of recording-control event strings (see
:func:start_recording_key_listener); each is sent on the same websocket
as a :class:TeleopEvent frame. Unlike action frames — which are
freshest-wins and freely dropped — a queued event survives websocket
reconnects: it is re-queued on a failed send and retried.
Source code in client/src/armnet_client/teleop_stream.py
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 | |
armnet_client.execute
¶
Top-level job-submission API: execute and execute_many.
Both target the platform's remote orchestrator + cell infrastructure. In M0.5 the orchestrator and cell happen to run on localhost, but they are still "remote" from the SDK's perspective — the SDK speaks to them only through the orchestrator HTTP API.
The orchestrator URL is hard-coded into the SDK build (see
:mod:armnet_client._config); customers do not pass it. Set
DEBUG_ARMNET_ORCHESTRATOR_URI to override during development.
LogStreamResult
dataclass
¶
How a log stream ended.
error is unauthorized, forbidden, not_found, or another
server reason. Those are permanent: the stream does not reconnect.
terminal_status is set when the job finished. saw_log is false when
the job was already over before this connection, which is when there is
nothing to show because logs are not stored.
Source code in client/src/armnet_client/execute.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
execute
¶
execute(*, image: Union[str, Image], embodiment: str, task: Optional[str] = None, args: Optional[Mapping[str, Any]] = None, secrets: Optional[Mapping[str, str]] = None, detach: bool = False, stream_logs: bool = True, use_rerun: bool = False, timeout_seconds: int = 120, allow_queue: bool = True) -> JobResult
Submit a single job to the platform and block until it terminates.
Returns the final :class:JobResult. Use result.return_value to
access whatever the customer's @main-decorated function returned;
use result.status/result.error for outcome.
The SDK polls the orchestrator until the job reaches a terminal state
or until timeout_seconds + 30 seconds have elapsed (the cell
enforces the actual timeout_seconds cap on the container; the
+30 here is just SDK-side polling slack).
Omit task to submit a task-less job: any cell of embodiment may run
it, regardless of the task it is configured for.
allow_queue (default True): the job is durably queued in JetStream. If
no cell claims it during the submission response window, this call returns
immediately with a non-terminal JobResult (status queued) rather
than blocking. Set allow_queue=False
for jobs that only make sense running right now (e.g. interactive
teleop/rerun): the orchestrator refuses to create the job (raising
:class:~armnet_client.client.OrchestratorError, HTTP 409) if it can't
be dispatched immediately.
Source code in client/src/armnet_client/execute.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
execute_many
¶
execute_many(specs: Sequence[Mapping[str, Any]], *, max_parallel: int = 4) -> Iterator[JobResult]
Submit many jobs in parallel; yield :class:JobResult as each completes.
specs is a sequence of kwarg-dicts, each accepted by
:func:execute (e.g. [{"image": ..., "embodiment": ..., "task": ...,
"args": {...}}, ...]). Results are yielded in completion order, not
submission order — match them up via your spec's own identifying
field if you need to.
max_parallel caps the number of concurrent submissions. Backed by
a thread pool; safe to use from synchronous code.
Source code in client/src/armnet_client/execute.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
armnet_client.image
¶
Programmatic Docker image building with content-hash caching.
The :class:Image class lets customer code declaratively build a Docker
image from a Dockerfile and pass the resulting reference to
:func:execute. Builds are content-addressed: the resulting image tag is
derived from a hash of the Dockerfile + every file in the build context,
so identical inputs short-circuit to the cached image without re-invoking
docker build.
Example::
from armnet_client import Image, execute
img = Image.build(
dockerfile="Dockerfile",
context_dir=".",
name="my-eval-runner",
)
result = execute(image=img, embodiment="lerobot/so-101",
task="assemble_block_tower", args={"seed": 0})
The cache lives in the local Docker daemon (we tag with a content hash). For M0.5 the cell runs against the same daemon so cached images are immediately available; for M1+ we'll add a registry push step.
ImageBuildError
¶
Bases: RuntimeError
Raised when docker build fails.
Source code in client/src/armnet_client/image.py
114 115 | |
ImagePushError
¶
Bases: RuntimeError
Raised when docker login / docker tag / docker push fails.
Source code in client/src/armnet_client/image.py
118 119 | |
RegistryCredentialsUnavailableError
¶
Bases: RuntimeError
Raised when the orchestrator can't issue image-registry credentials.
This is the recoverable shape of an :meth:Image.push failure — the
orchestrator either doesn't have the endpoint (404), isn't configured
for credential issuance (503), or isn't reachable at all (connection
refused, typical for pure-local M0.5 dev). Callers can pass
if_possible=True to :meth:Image.push to log + fall back to the
local image ref instead of propagating this.
Source code in client/src/armnet_client/image.py
122 123 124 125 126 127 128 129 130 131 | |
ImageOwnershipError
¶
Bases: ValueError
A prebuilt image is outside the authenticated customer's namespace.
Source code in client/src/armnet_client/image.py
134 135 | |
Image
dataclass
¶
A reference to a Docker image, either pre-existing or built locally.
Most customer code constructs one with :meth:build. To wrap an
existing registry-pushed image, use :meth:from_ref or just pass the
image string directly to :func:execute.
Source code in client/src/armnet_client/image.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 | |
from_ref
classmethod
¶
from_ref(ref: str) -> 'Image'
Source code in client/src/armnet_client/image.py
152 153 154 | |
validate_for_current_user
¶
validate_for_current_user(*, if_possible: bool = False) -> 'Image'
Fast client-side ownership check; the orchestrator remains authoritative.
In pure-local development the registry endpoint may be unavailable.
if_possible=True preserves that workflow while production callers
still get immediate feedback for another user's or external image.
Source code in client/src/armnet_client/image.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
planned_ref
classmethod
¶
planned_ref(dockerfile: Union[str, Path], name: str, context_dir: Union[str, Path, None] = None, *, platform: Optional[str] = None, build_contexts: Optional[Mapping[str, Union[str, Path]]] = None) -> str
The registry ref :meth:build then :meth:push would produce.
Worked out from the source alone: the tag is a hash of the build context, so the answer needs no Docker daemon and no build. It does need the registry credentials, since only they say which namespace the image lands in.
This is what lets a caller ask "is this already pushed?" before spending twenty minutes producing a byte-identical image.
Source code in client/src/armnet_client/image.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
exists_in_registry
staticmethod
¶
exists_in_registry(ref: str) -> bool
Whether ref is already in the registry.
Asks the registry's manifest endpoint directly with the same short-lived token a push would use, so it costs one request and needs no Docker.
Source code in client/src/armnet_client/image.py
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | |
push
¶
push(*, if_possible: bool = False) -> 'Image'
Push this image to the platform's registry; return a new Image whose ref points at the registry.
Flow:
- Fetch short-lived OAuth2 credentials from the orchestrator's
POST /registry/credentialsendpoint (cached on disk; refreshed when within 5 minutes of expiry). docker login <registry> -u oauth2accesstoken --password-stdin.docker tag <local> <registry>/<namespace>/<local>.docker pushthe new tag.- Return
Image(ref=<full registry ref>).
Customers don't need any GCP credentials; the orchestrator does the IAM dance on their behalf via service-account impersonation.
For pure-local M0.5 dev where the orchestrator + cell share a
docker daemon and pushing isn't necessary, pass if_possible=True
— the method then logs a warning and returns self unchanged
if the orchestrator can't issue credentials, instead of raising.
Real docker push failures (network, auth, etc.) always raise.
Source code in client/src/armnet_client/image.py
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | |
build
classmethod
¶
build(dockerfile: Union[str, Path], name: str, context_dir: Union[str, Path, None] = None, force_rebuild: bool = False, platform: Optional[str] = None, build_contexts: Optional[Mapping[str, Union[str, Path]]] = None, cache_from: Optional[Iterable[str]] = None, cache_to: Optional[Iterable[str]] = None) -> 'Image'
Build a Docker image and return a content-addressed reference.
:param dockerfile: Path to the Dockerfile.
:param name: Image name (the part before the tag); used as a
human-readable prefix on the resulting tag.
:param context_dir: Build context directory. Defaults to the
Dockerfile's parent.
:param force_rebuild: If True, always invoke docker build even
when an image with the computed content-hash tag already
exists.
:param platform: Optional Docker platform, e.g. linux/arm64/v8 or
linux/amd64. Defaults to ARMNET_DOCKER_PLATFORM when
set. Otherwise an x86 host uses Docker's native platform, and
any other host (Apple Silicon included) builds linux/amd64
so the image can run on a cell.
:param build_contexts: Optional named build contexts forwarded to
docker build --build-context name=path. The Dockerfile can
then reference them via COPY --from=name .... Useful for
pulling in source trees that live outside the primary build
context (e.g. a sibling checkout on the build host). Contents
participate in the content hash so changes invalidate the cache.
:param cache_from: Optional BuildKit --cache-from specs (e.g.
["type=gha,scope=my-image"]). When any cache is requested the
build runs via docker buildx build --load so layers can be
reused across machines/CI runs. Falls back to a plain
docker build (no cache) if buildx or the cache backend is
unavailable, so enabling caching can never break a build.
:param cache_to: Optional BuildKit --cache-to specs (e.g.
["type=gha,scope=my-image,mode=max"]).
:returns: An :class:Image whose ref is
<name>:armnet-<short-hash>.
As a convenience for CI, setting ARMNET_DOCKER_BUILDX_CACHE=gha in
the environment enables GitHub-Actions layer caching (scoped per image
name) without passing cache_from / cache_to explicitly.
Source code in client/src/armnet_client/image.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 | |
default_docker_platform
¶
default_docker_platform() -> str | None
Platform to build when the caller did not pass one.
ARMNET_DOCKER_PLATFORM wins. An x86 host keeps Docker's native
platform. Any other host, including Apple Silicon, builds
linux/amd64 because the cells are x86.
Source code in client/src/armnet_client/image.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 | |
resolve_docker_platform
¶
resolve_docker_platform(explicit: str | None = None) -> str | None
Caller argument, else :func:default_docker_platform.
Source code in client/src/armnet_client/image.py
75 76 77 78 79 80 | |
armnet_client.client
¶
HTTP client for the orchestrator.
Thin wrapper around the two M0.5 endpoints. Used internally by
:func:armnet_client.execute; exposed publicly for power users that
need fine-grained control (custom polling, fire-and-forget submissions,
etc.).
OrchestratorError
¶
Bases: RuntimeError
Raised when the orchestrator returns a non-2xx response.
status_code is the HTTP status returned by the orchestrator;
None if the request never got a response (e.g. connection refused).
Callers can branch on it to distinguish e.g. "endpoint not implemented"
(404) or "configured-but-unavailable" (503) from real failures.
Source code in client/src/armnet_client/client.py
35 36 37 38 39 40 41 42 43 44 45 46 | |
OrchestratorClient
¶
Synchronous client for the orchestrator HTTP API.
Defaults to Armnet's production API and the ARMNET_API_KEY environment
variable. Pass base_url or api_key explicitly for development and
custom integrations.
Source code in client/src/armnet_client/client.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
close
¶
close() -> None
Source code in client/src/armnet_client/client.py
71 72 | |
submit
¶
submit(spec: JobSpec, *, allow_queue: bool = True) -> Job
POST /jobs — create a new job.
The returned :class:Job carries dispatched: True if it was sent to
a cell immediately, False if it was accepted but queued.
allow_queue (default True): when no matching cell is free, queue the
job (True) or have the orchestrator refuse it with HTTP 409 (False, for
callers that only make sense running immediately).
Source code in client/src/armnet_client/client.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
get
¶
get(job_id: str) -> Job
GET /jobs/{id} — fetch the current state of a job.
Source code in client/src/armnet_client/client.py
98 99 100 101 102 | |
list_jobs
¶
list_jobs(*, limit: int = 10, queued_only: bool = False) -> list[Job]
GET /jobs — the current user's most-recent jobs (newest first).
Source code in client/src/armnet_client/client.py
104 105 106 107 108 109 110 111 112 | |
get_status_transitions
¶
get_status_transitions(job_id: str) -> list[JobStatusTransition]
GET /jobs/{id}/status-transitions — a job's status-change audit trail.
Source code in client/src/armnet_client/client.py
114 115 116 117 118 | |
stop_job
¶
stop_job(job_id: str, *, reason: str = 'client requested stop') -> Job
POST /jobs/{id}/stop — request an immediate stop of a running job.
Used when the operator intentionally aborts (e.g. Ctrl-C): this cancels the job now rather than waiting out the orchestrator's log-disconnect grace window.
Source code in client/src/armnet_client/client.py
120 121 122 123 124 125 126 127 128 129 | |
get_cell_status
¶
get_cell_status(*, embodiment: str = DEFAULT_EMBODIMENT, task: Optional[str] = None) -> CellStatusSummary
GET /cells/status — fetch current availability for a task.
Source code in client/src/armnet_client/client.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
get_registry_credentials
¶
get_registry_credentials() -> RegistryCredentials
POST /registry/credentials — fetch fresh image-registry credentials.
Used by :meth:armnet_client.Image.push to get a short-lived
OAuth2 token for pushing customer images to the platform's
Artifact Registry repo. The orchestrator returns 503 if it isn't
configured for credential issuance (typical for local dev).
Source code in client/src/armnet_client/client.py
146 147 148 149 150 151 152 153 154 155 156 | |
whoami
¶
whoami() -> WhoAmI
GET /whoami — fetch the username for the current API key.
Source code in client/src/armnet_client/client.py
158 159 160 161 162 163 | |
list_secrets
¶
list_secrets() -> SecretList
GET /secrets — list secret names for the current user.
Source code in client/src/armnet_client/client.py
165 166 167 168 169 170 | |
create_secret
¶
create_secret(name: str, value: str) -> SecretInfo
POST /secrets — create or replace one secret value.
Source code in client/src/armnet_client/client.py
172 173 174 175 176 177 178 179 180 | |
delete_secret
¶
delete_secret(name: str) -> None
DELETE /secrets/{name} — delete one user secret if it exists.
Source code in client/src/armnet_client/client.py
182 183 184 185 186 | |
get_volume_credentials
¶
get_volume_credentials() -> VolumeCredentials
POST /volume/credentials — fetch short-lived GCS volume credentials.
Source code in client/src/armnet_client/client.py
188 189 190 191 192 193 | |
wait
¶
wait(job_id: str, poll_interval_seconds: float = 1.0, deadline_seconds: Optional[float] = None) -> Job
Poll GET /jobs/{id} until the job reaches a terminal state.
Returns the final :class:Job. Raises :class:TimeoutError if
deadline_seconds elapses first.
Source code in client/src/armnet_client/client.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
armnet_client.volume
¶
armnet volume helpers (client side).
Thin wrappers over :mod:armnet_core.volume_sync, which holds the shared
cloud <-> local transfer engine. This keeps the GCS logic in one place so the
cell process (which cannot import armnet-client) reuses the exact same
mirroring behaviour. The wrappers add tqdm progress bars for interactive CLI use
and preserve the historical function signatures.
upload_to_cloud_volume
¶
upload_to_cloud_volume(*, credentials: VolumeCredentials, username: str, local_path: str | Path, volume_path: str | Path, overwrite: bool = False) -> list[str]
Source code in client/src/armnet_client/volume.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
download_from_cloud_volume
¶
download_from_cloud_volume(*, credentials: VolumeCredentials, username: str, volume_path: str | Path, local_path: str | Path) -> Path
Source code in client/src/armnet_client/volume.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 | |
delete_from_cloud_volume
¶
delete_from_cloud_volume(*, credentials: VolumeCredentials, volume_path: str | Path) -> None
Source code in client/src/armnet_client/volume.py
62 63 | |
list_cloud_volume
¶
list_cloud_volume(*, credentials: VolumeCredentials, volume_path: str | None = None) -> list[tuple[str, int]]
Source code in client/src/armnet_client/volume.py
66 67 68 69 | |
armnet_client.local
¶
execute_local — in-process execution of a @main script.
Faster-iteration counterpart to :func:execute: no Docker, no
orchestrator, no NATS. Loads the customer's Python file directly, finds
the @main-decorated function, builds a
:class:~armnet_runtime.Context with a user-supplied robot_port, and
calls it in the current process.
Returns the same :class:~armnet_core.JobResult shape as
:func:execute so downstream code (result.return_value,
result.status, etc.) is identical.
Notes for M0.5:
- The LeRobot import-system swap that
armnet-runtimewill perform in M3+ is intentionally not done here — the whole point ofexecute_localis to talk to a real local robot driver, not a safety-aware connector. - Stdout / stderr are not captured (they go to the user's terminal);
the corresponding :class:
JobResultfields are empty strings. timeout_secondsis recorded intoctx.timeout_secondsfor the function to honour, but is not enforced byexecute_localitself. Killing arbitrary in-process Python by deadline is hard to do cleanly without subprocesses; deferred until we need it.
execute_local
¶
execute_local(main_module: Union[str, Path], *, args: Optional[Mapping[str, Any]] = None, port: Optional[str] = None, robot_id: Optional[str] = None, calibration_dir: Optional[Union[str, Path]] = None, calibration_file_path: Optional[Union[str, Path]] = None, camera_configs: Optional[Mapping[str, Any]] = None, embodiment: str = DEFAULT_EMBODIMENT, task: str = DEFAULT_TASK, timeout_seconds: int = 120, job_id: Optional[str] = None) -> JobResult
Run a @main-decorated script in the current Python process.
:param main_module: Path to the Python file containing exactly one
@main-decorated function.
:param args: Mapping passed through as ctx.args.
:param port: Value placed at ctx.cell.robot_port — typically the path
to a real local serial device, e.g. "/dev/ttyUSB0".
:param robot_id: Value placed at ctx.cell.robot_id — typically the
name of the robot, e.g. "praveen_so101".
:param embodiment: Recorded on ctx.embodiment (defaults to the
only embodiment supported in M0.5).
:param task: Recorded on ctx.task (defaults to the only task
supported in M0.5).
:param timeout_seconds: Recorded on ctx.timeout_seconds. Not
enforced by execute_local (see module docstring).
:param job_id: Optional explicit job id for ctx.job_id. Defaults
to a stable synthetic id derived from the script path.
:returns: A :class:JobResult with return_value set to whatever
@main returned, or error populated with a traceback if it
raised.
Source code in client/src/armnet_client/local.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
armnet_client.local_container
¶
Docker-backed local execution for robot cell development.
LocalContext
dataclass
¶
Developer-facing context for local Docker execution.
This is intentionally smaller than RobotCellConfig. It describes the
runtime values a containerized program needs, without exposing operator-only
cell settings such as NATS, cell IDs, or completion model configuration.
Source code in client/src/armnet_client/local_container.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
camera_configs
class-attribute
instance-attribute
¶
camera_configs: Mapping[str, Any] = field(default_factory=dict)
calibration_dir
class-attribute
instance-attribute
¶
calibration_dir: Optional[Union[str, Path]] = None
calibration_file_path
class-attribute
instance-attribute
¶
calibration_file_path: Optional[Union[str, Path]] = None
language_instruction
class-attribute
instance-attribute
¶
language_instruction: Optional[str] = None
execute_local_container
¶
execute_local_container(*, image: Union[str, Image], local_context: Optional[LocalContext] = None, args: Optional[Mapping[str, Any]] = None, embodiment: str = DEFAULT_EMBODIMENT, task: str = DEFAULT_TASK, timeout_seconds: int = 120, cell_socket: Optional[str] = None, cell_endpoint: Optional[str] = None, robot_id: Optional[str] = None, calibration_dir: Optional[Union[str, Path]] = None, calibration_file_path: Optional[Union[str, Path]] = None, cell_config: Optional[RobotCellConfig] = None, cell_config_path: Optional[Union[str, Path]] = None, mount_calibration: bool = True, username: str = 'remoterobo-test', job_id: str = 'job_local_container', stream_output: bool = True, docker_gpus: Optional[str] = None, docker_runtime: Optional[str] = None, gpus: Optional[str] = None) -> JobResult
Run a runtime image locally through Docker.
This is the M2 local-dev path for robot examples: it validates the same container + armnet-runtime + Unix-socket route as remote execution, without going through NATS or the cloud orchestrator.
Source code in client/src/armnet_client/local_container.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
armnet_client.rerun_stream
¶
Client-side Rerun streaming.
Connects to the orchestrator's /jobs/{job_id}/rerun websocket, decodes the
binary RerunPacket frames the cell republished from the container's
ctx.log_rerun_data(...) calls, and replays each into the active Rerun
recording via rr.log(...).
Call :func:init_rerun (or rerun.init(...) directly) once in your
orchestrate script before submitting the job so the packets land in a live
viewer.
init_rerun
¶
init_rerun(application_id: str = 'armnet', *, spawn: bool = True, serve_web: bool = False, web_port: Optional[int] = None, grpc_port: Optional[int] = None, open_browser: bool = True, flush_tick_seconds: Optional[float] = None) -> Any
Initialize Rerun and start a viewer.
Thin convenience wrapper so orchestrate scripts don't need to import the
Rerun SDK directly. Raises a clear error if rerun-sdk isn't installed
(pip install 'armnet-client[viz]').
Viewer selection:
serve_web=Truealways hosts the web viewer over HTTP (open the printed URL in a browser; forward the port first if you're on a remote host).spawn=True(default) opens a native desktop window — but only when a display is available. On a headless machine (noDISPLAY/ Wayland, e.g. SSH or a GUI-less workstation) it automatically falls back to the web viewer instead of failing to open a window.
flush_tick_seconds sets Rerun's micro-batching flush interval (the
RERUN_FLUSH_TICK_SECS knob). A very small value (e.g. 0.002 = 2ms)
minimizes how long logged data waits before being sent to the viewer —
noticeably snappier for teleoperation. Left unset, Rerun's default applies.
Source code in client/src/armnet_client/rerun_stream.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
stream_job_rerun
¶
stream_job_rerun(job_id: str, stop: Event) -> None
Consume the rerun websocket for job_id until stop is set.
Intended to run on a background thread. Silently returns if no API key is configured, the websocket can't be reached, or the rerun SDK is missing, so visualization never blocks or fails the job itself.
Source code in client/src/armnet_client/rerun_stream.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
armnet_client.teleop_stream
¶
Client-side remote teleoperation.
Connects to a locally-attached LeRobot leader arm (e.g. an SO-101 leader),
samples its joint positions at a high rate, and pushes each reading to the
orchestrator's /jobs/{job_id}/teleop websocket. The orchestrator forwards
them to the job's cell, which keeps only the freshest action and serves it to
the runtime container (ctx.cell.get_teleop_action()), which drives the
remote follower.
Sampling faster than the robot's control rate (default 60Hz vs a ~20Hz robot loop) makes teleop feel snappier: the cell always has a near-current command, so a dropped or late frame doesn't force the follower to wait a full robot tick for the next one.
When recording a dataset (armnet-lerobot-record), discrete
recording-control events ride the same websocket: pass an events queue to
:func:stream_job_teleop and feed it from
:func:start_recording_key_listener, which binds LeRobot's standard dataset
recording shortcuts (Right Arrow → save episode and move on, Left Arrow →
discard and re-record, Esc → stop the session).
LeRobot is an optional dependency — install with armnet-client[teleop].
It is imported lazily so the rest of the client works without it.
TeleoperatorConfig
dataclass
¶
How to connect to the local leader arm being used to teleoperate.
Mirrors what LeRobot needs to construct a teleoperator, plus the sample
rate. id is the LeRobot teleoperator id used to find calibration.
Source code in client/src/armnet_client/teleop_stream.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
yam_left_gravity_profile
class-attribute
instance-attribute
¶
yam_left_gravity_profile: Optional[str] = None
yam_right_gravity_profile
class-attribute
instance-attribute
¶
yam_right_gravity_profile: Optional[str] = None
yam_gravity_assist_gain
class-attribute
instance-attribute
¶
yam_gravity_assist_gain: Optional[float] = None
yam_gravity_current_limit_ma
class-attribute
instance-attribute
¶
yam_gravity_current_limit_ma: Optional[int] = None
yam_gravity_joint_signs
class-attribute
instance-attribute
¶
yam_gravity_joint_signs: Optional[tuple[int, ...]] = None
yam_gravity_joint_offsets
class-attribute
instance-attribute
¶
yam_gravity_joint_offsets: Optional[tuple[float, ...]] = None
yam_gravity_link_masses
class-attribute
instance-attribute
¶
yam_gravity_link_masses: Optional[tuple[float, ...]] = None
yam_gravity_joints
class-attribute
instance-attribute
¶
yam_gravity_joints: Optional[tuple[str, ...]] = None
yam_gravity_joint_ranges
class-attribute
instance-attribute
¶
yam_gravity_joint_ranges: Optional[tuple[float, ...]] = None
yam_gripper_return_current_ma
class-attribute
instance-attribute
¶
yam_gripper_return_current_ma: Optional[int] = None
start_recording_key_listener
¶
start_recording_key_listener(events: 'queue.Queue[str]')
Bind LeRobot's dataset-recording keyboard shortcuts to events.
Right Arrow → next_episode, Left Arrow → rerecord_episode, Esc →
stop_recording. Each press is enqueued for :func:stream_job_teleop
to forward to the cell. Esc does not tear anything down client-side: the
runtime ends the session on receipt and the job result unblocks the
orchestrate script, which then stops its streams.
Linux reads the kernel input device via evdev, because pynput only
hears keys through X11 and a Wayland session never delivers them. macOS
keeps pynput. Returns the started listener (call .stop() when
done), or None when no keyboard is available.
Source code in client/src/armnet_client/teleop_stream.py
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | |
stream_job_teleop
¶
stream_job_teleop(job_id: str, stop: Event, config: TeleoperatorConfig, events: 'queue.Queue[str] | None' = None) -> None
Sample the local leader and push actions to job_id until stop is set.
Intended to run on a background thread (alongside the rerun stream). Returns quietly if no API key is configured, LeRobot is missing, or the leader/ websocket can't be reached, so teleop never crashes the orchestrate script.
events, when given, is a queue of recording-control event strings (see
:func:start_recording_key_listener); each is sent on the same websocket
as a :class:TeleopEvent frame. Unlike action frames — which are
freshest-wins and freely dropped — a queued event survives websocket
reconnects: it is re-queued on a failed send and retried.
Source code in client/src/armnet_client/teleop_stream.py
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 | |
armnet_client.current_gate
¶
Current-gate profiles derived from robot-telemetry sidecars.
DEFAULT_PROFILE_PATH
module-attribute
¶
DEFAULT_PROFILE_PATH = Path(str(resources.files('armnet_client').joinpath('current_gate_profile.json')))
DEFAULT_DATASETS_PATH
module-attribute
¶
DEFAULT_DATASETS_PATH = Path(str(resources.files('armnet_client').joinpath('current_gate_datasets.json')))
collect_current_samples
¶
collect_current_samples(table: Any) -> dict[tuple[str, str], list[int]]
Source code in client/src/armnet_client/current_gate.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
compute_percentile_wire_counts
¶
compute_percentile_wire_counts(samples: Mapping[tuple[str, str], Sequence[int]], percentiles: Sequence[int] = DEFAULT_PERCENTILES) -> dict[int, dict[str, dict[str, int]]]
Source code in client/src/armnet_client/current_gate.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
derive_profile_from_dataset_roots
¶
derive_profile_from_dataset_roots(dataset_roots: Sequence[Path], sources: Sequence[str], percentiles: Sequence[int] = DEFAULT_PERCENTILES) -> dict[str, Any]
Source code in client/src/armnet_client/current_gate.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
download_dataset_root
¶
download_dataset_root(repo_id: str, *, cache_dir: Path | None = None) -> Path
Source code in client/src/armnet_client/current_gate.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
derive_profile_from_repo_ids
¶
derive_profile_from_repo_ids(repo_ids: Sequence[str], *, cache_dir: Path | None = None, percentiles: Sequence[int] = DEFAULT_PERCENTILES) -> dict[str, Any]
Source code in client/src/armnet_client/current_gate.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
load_profile
¶
load_profile(path: Path | None = None) -> dict[str, Any]
Source code in client/src/armnet_client/current_gate.py
204 205 206 207 208 209 210 | |
write_profile
¶
write_profile(path: Path, profile: Mapping[str, Any]) -> None
Source code in client/src/armnet_client/current_gate.py
213 214 215 216 217 218 | |
reference_wire_counts_from_profile
¶
reference_wire_counts_from_profile(profile: Mapping[str, Any], percentile: int) -> dict[str, dict[str, int]]
Source code in client/src/armnet_client/current_gate.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
max_wire_counts_from_profile
¶
max_wire_counts_from_profile(profile: Mapping[str, Any]) -> dict[str, dict[str, int]]
Source code in client/src/armnet_client/current_gate.py
249 250 251 252 253 254 255 | |
current_gate_config
¶
current_gate_config(percentile: int = DEFAULT_GATE_PERCENTILE, percent_above: float | None = DEFAULT_GATE_PERCENT_ABOVE, duration_ms: float = DEFAULT_GATE_DURATION_MS, *, profile_path: Path | None = None) -> dict | None
Build the job-arg payload for the edge current gate.
Joints whose baseline max is at or below TINY_MAX_THRESHOLD use that
max as the trip limit instead. Pass percent_above <= 0 (or None)
to omit the optional job-level percentile tightening; the cell-owned edge
gate remains active.
Source code in client/src/armnet_client/current_gate.py
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
current_gate_job_args
¶
current_gate_job_args(percentile: int = DEFAULT_GATE_PERCENTILE, percent_above: float | None = DEFAULT_GATE_PERCENT_ABOVE, duration_ms: float = DEFAULT_GATE_DURATION_MS, *, profile_path: Path | None = None) -> dict[str, dict]
Source code in client/src/armnet_client/current_gate.py
294 295 296 297 298 299 300 301 302 303 304 305 306 307 | |
main
¶
main(argv: Iterable[str] | None = None) -> int
Source code in client/src/armnet_client/current_gate.py
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |