sequence-base 0.4.27__tar.gz → 0.4.29__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.
- {sequence_base-0.4.27 → sequence_base-0.4.29}/PKG-INFO +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/pyproject.toml +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/__init__.py +4 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/constants/version.py +3 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/connect.py +3 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/deployments.py +3 -9
- sequence_base-0.4.29/sequence_base/contract/obs_schedule.py +170 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/observe.py +2 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/own_io.py +69 -7
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/policy.py +4 -9
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/wire.py +5 -0
- sequence_base-0.4.29/sequence_base/feedback.py +106 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/presets/robot_arm.py +4 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ActRequest.json +15 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ActResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ActionChunk.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ActionContract.json +13 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Actions.json +13 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/AdapterPair.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/AddLocal.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Autoscaler.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/BenchmarkRegisterRequest.json +13 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/BenchmarkSpec.json +2 -2
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/CodeBundle.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Concurrency.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Conditions.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ConnectRequest.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ConnectResponse.json +37 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/DeploymentPolicy.json +37 -33
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/DeploymentRequest.json +37 -33
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Embodiment.json +13 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageSpec.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepEnv.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepProject.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Observations.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ObserveRequest.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ObserveResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/PolicyRegistration.json +38 -34
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/PolicySpec.json +37 -33
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Project.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/RenderSpec.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/ReplaySpec.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Resources.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Step.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Stream.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/TemplateFiles.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Tensors.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/Variant.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schemas/WarmingError.json +1 -1
- {sequence_base-0.4.27 → sequence_base-0.4.29}/.gitignore +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/README.md +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/auth.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/codec/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/codec/image.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/constants/cards.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/constants/headers.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/constants/limits.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/construct.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/act.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/adapters.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/benchmarks.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/flags.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/io.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/episode.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/errors/transport.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/msgpack_io.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/obs/log.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/obs/rid.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/presets/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/presets/frames.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/schema_export.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/telemetry/__init__.py +0 -0
- {sequence_base-0.4.27 → sequence_base-0.4.29}/sequence_base/telemetry/events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sequence-base
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.29
|
|
4
4
|
Summary: Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness.
|
|
5
5
|
Project-URL: Changelog, https://generalsequences.com/docs/changelog/
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.29"
|
|
4
4
|
description = "Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness."
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
dependencies = ["pydantic>=2.0"]
|
|
@@ -130,6 +130,9 @@ from sequence_base.errors.transport import (
|
|
|
130
130
|
from sequence_base.episode import Episode
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
from sequence_base.feedback import Feedback
|
|
134
|
+
|
|
135
|
+
|
|
133
136
|
from sequence_base import auth
|
|
134
137
|
|
|
135
138
|
|
|
@@ -174,7 +177,7 @@ __all__ = [
|
|
|
174
177
|
|
|
175
178
|
"TransportUnavailable", "TransportWarming", "TransportRejected", "TransportOverloaded",
|
|
176
179
|
|
|
177
|
-
"Episode",
|
|
180
|
+
"Episode", "Feedback",
|
|
178
181
|
|
|
179
182
|
"auth",
|
|
180
183
|
]
|
|
@@ -184,6 +184,9 @@ class DeploymentPolicy(BaseModel):
|
|
|
184
184
|
|
|
185
185
|
|
|
186
186
|
streaming: bool = Field(default=False, exclude=True)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
feedback: bool | None = None
|
|
187
190
|
secrets: list[SecretRef] = Field(default_factory=list)
|
|
188
191
|
|
|
189
192
|
|
|
@@ -216,12 +219,6 @@ class DeploymentPolicy(BaseModel):
|
|
|
216
219
|
|
|
217
220
|
|
|
218
221
|
|
|
219
|
-
|
|
220
|
-
own_observations: Literal["latest", "since_last_act"] | None = None
|
|
221
|
-
own_actions: Literal["one", "steps"] | None = None
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
222
|
observations: Observations | None = None
|
|
226
223
|
actions: Actions | None = None
|
|
227
224
|
tensors: Tensors | None = None
|
|
@@ -239,9 +236,6 @@ class DeploymentPolicy(BaseModel):
|
|
|
239
236
|
|
|
240
237
|
@model_validator(mode="after")
|
|
241
238
|
def _own_io(self) -> "DeploymentPolicy":
|
|
242
|
-
if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
|
|
243
|
-
raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
|
|
244
|
-
"declares the robot-arm contract, where observation history and the action chunk say it")
|
|
245
239
|
check_own_io(self)
|
|
246
240
|
return self
|
|
247
241
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Which steps of an episode a policy of its own inputs is handed at an act, and which keys of each: its Observations
|
|
3
|
+
(contract.own_io) read for one act - the one rule a worker hands a robot's streamed frames by
|
|
4
|
+
, an eval hands its benchmark's by (obs_history), and a robot's SDK uploads by.
|
|
5
|
+
|
|
6
|
+
In one sentence: an act at step t, the act before it at t_prev (-1: none yet) → each key's steps - its `at` offsets
|
|
7
|
+
t + off (a window: the worker keeps the frames), every k steps of (t_prev, t] counting back from t (fed: the policy keeps
|
|
8
|
+
them), else t alone; Observations(every=k) the whole observation so → a step before the episode's first reads as its
|
|
9
|
+
first (pad "repeat_first") or goes ("drop") → oldest first, each with the keys read at it, the act's own last:
|
|
10
|
+
{"observations": [...], "steps": [...]}. unit "seconds" counts seconds, taken to steps by the control rate of whoever
|
|
11
|
+
steps the episode - the robot's, the benchmark's - which its caller passes.
|
|
12
|
+
|
|
13
|
+
WHY one rule in one place: the worker, an eval and the SDK each act on the same declaration - which frames to keep,
|
|
14
|
+
which to render, which to upload - and a step one of them reads differently is a frame a policy is handed that it never
|
|
15
|
+
asked for, or never handed one it did. A robot's SDK restates it (it never imports sequence_base); its tests hold it to
|
|
16
|
+
this.
|
|
17
|
+
|
|
18
|
+
sched = obs_schedule.of(observations, rate=10.0) # None: nothing declared - the act's own, as always
|
|
19
|
+
obs_schedule.picks(sched, t=20, t_prev=12) # [(5, ("cam",)), (20, ("cam", "state"))]
|
|
20
|
+
obs_schedule.envelope(picked, frames={5: {...}}, own={...}, t=20)
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
__all__ = ["Schedule", "envelope", "keeps_from", "needed", "of", "picks", "reads_past", "wanted"]
|
|
28
|
+
|
|
29
|
+
Keys = tuple[str, ...] | None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Schedule:
|
|
34
|
+
"""An Observations declaration in steps. `windows`: key -> its offsets (each 0 or below, oldest first); `fed`:
|
|
35
|
+
key -> every how many steps; `alone`: keys read at the act's step alone; `every`: the whole observation fed every
|
|
36
|
+
so many steps (no keys named); `whole`: no keys named - the whole observation; `pad`: "repeat_first" or "drop"."""
|
|
37
|
+
|
|
38
|
+
windows: dict
|
|
39
|
+
fed: dict
|
|
40
|
+
alone: tuple
|
|
41
|
+
every: int | None
|
|
42
|
+
whole: bool
|
|
43
|
+
pad: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get(d: Any, name: str) -> Any:
|
|
47
|
+
return d.get(name) if isinstance(d, dict) else getattr(d, name, None)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def of(observations: Any, rate: float | None = None) -> Schedule | None:
|
|
51
|
+
"""The schedule an Observations declaration - the model, or its JSON - reads by; None when there is none (the act's
|
|
52
|
+
own observation, as before). `rate`: the control steps a second of whoever steps the episode, for unit "seconds"
|
|
53
|
+
alone. Raises ValueError for unit "seconds" without it."""
|
|
54
|
+
if observations is None:
|
|
55
|
+
return None
|
|
56
|
+
unit = _get(observations, "unit") or "steps"
|
|
57
|
+
|
|
58
|
+
def steps(x: Any) -> int:
|
|
59
|
+
if unit != "seconds":
|
|
60
|
+
return int(x)
|
|
61
|
+
if not rate:
|
|
62
|
+
raise ValueError("this policy counts its observations in seconds (unit='seconds'): the control rate of the "
|
|
63
|
+
"robot or benchmark stepping the episode takes them to steps, and none was given")
|
|
64
|
+
return int(round(float(x) * float(rate)))
|
|
65
|
+
|
|
66
|
+
windows, fed, alone = {}, {}, []
|
|
67
|
+
streams = _get(observations, "streams")
|
|
68
|
+
for key, stream in (streams or {}).items():
|
|
69
|
+
at, every = _get(stream, "at"), _get(stream, "every")
|
|
70
|
+
if at is not None:
|
|
71
|
+
windows[key] = tuple(sorted({min(0, steps(x)) for x in at}))
|
|
72
|
+
elif every is not None:
|
|
73
|
+
fed[key] = max(1, steps(every))
|
|
74
|
+
else:
|
|
75
|
+
alone.append(key)
|
|
76
|
+
every = _get(observations, "every")
|
|
77
|
+
return Schedule(windows=windows, fed=fed, alone=tuple(alone), every=max(1, steps(every)) if every is not None else
|
|
78
|
+
None, whole=streams is None, pad=_get(observations, "pad") or "repeat_first")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def reads_past(declared: Any) -> bool:
|
|
82
|
+
"""Whether an act reads a step before its own - a window reaching back, a fed key, the whole observation fed -
|
|
83
|
+
whose frames must reach the worker before the act does (its observation stream, POST /observe). `declared`: a
|
|
84
|
+
Schedule, or the Observations declaration itself (the model or its JSON) - read with no control rate, which only
|
|
85
|
+
scales a count in seconds."""
|
|
86
|
+
if declared is None:
|
|
87
|
+
return False
|
|
88
|
+
if isinstance(declared, Schedule):
|
|
89
|
+
return (any(off < 0 for offsets in declared.windows.values() for off in offsets) or bool(declared.fed)
|
|
90
|
+
or (declared.whole and declared.every is not None))
|
|
91
|
+
if _get(declared, "every") is not None:
|
|
92
|
+
return True
|
|
93
|
+
for stream in (_get(declared, "streams") or {}).values():
|
|
94
|
+
if _get(stream, "every") is not None or any(x < 0 for x in _get(stream, "at") or ()):
|
|
95
|
+
return True
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def picks(sched: Schedule | None, t: int, t_prev: int = -1) -> list[tuple[int, Keys]]:
|
|
100
|
+
"""(step, keys) of each observation the act at step `t` is handed, oldest first, the act's own last - keys None for
|
|
101
|
+
the whole observation. A step before the episode's first is its first (step 0) under pad "repeat_first", once for
|
|
102
|
+
each such step asked, and is left out under "drop". `t_prev`: the act before it (-1: the episode's first)."""
|
|
103
|
+
if sched is None:
|
|
104
|
+
return [(t, None)]
|
|
105
|
+
asked: dict[int, set] = {}
|
|
106
|
+
whole: set[int] = set()
|
|
107
|
+
for key, offsets in sched.windows.items():
|
|
108
|
+
for off in offsets:
|
|
109
|
+
asked.setdefault(t + off, set()).add(key)
|
|
110
|
+
for key, k in sched.fed.items():
|
|
111
|
+
for s in range(t, t_prev, -k):
|
|
112
|
+
asked.setdefault(s, set()).add(key)
|
|
113
|
+
for key in sched.alone:
|
|
114
|
+
asked.setdefault(t, set()).add(key)
|
|
115
|
+
if sched.whole:
|
|
116
|
+
whole.update(range(t, t_prev, -sched.every) if sched.every else (t,))
|
|
117
|
+
out: list[tuple[int, Keys]] = []
|
|
118
|
+
for s in sorted(set(asked) | whole):
|
|
119
|
+
if s < 0 and sched.pad == "drop":
|
|
120
|
+
continue
|
|
121
|
+
out.append((max(0, s), None if s in whole else tuple(sorted(asked[s]))))
|
|
122
|
+
return out
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def needed(sched: Schedule | None, t: int, t_prev: int = -1) -> set[int]:
|
|
126
|
+
"""The steps the act at `t` reads but does not carry itself: their frames come before it (the stream)."""
|
|
127
|
+
return {s for s, _ in picks(sched, t, t_prev) if s != t}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def keeps_from(sched: Schedule | None, t: int) -> int:
|
|
131
|
+
"""The earliest step an act after the one at `t` may read: a window reaches back its furthest offset from an act
|
|
132
|
+
one step on at the soonest; a fed key, the whole observation and a key read alone, nothing at or before `t`."""
|
|
133
|
+
reach = min((offsets[0] for offsets in (sched.windows.values() if sched else ())), default=0)
|
|
134
|
+
return max(0, t + 1 + min(0, reach))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _merge(into: dict, step: int, keys: Keys) -> None:
|
|
138
|
+
if keys is None or into.get(step, ()) is None:
|
|
139
|
+
into[step] = None
|
|
140
|
+
else:
|
|
141
|
+
into[step] = tuple(sorted({*into.get(step, ()), *keys}))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def wanted(sched: Schedule | None, t: int, t_next: int) -> dict[int, Keys]:
|
|
145
|
+
"""{step: keys} to read - an eval's benchmark - or upload - a robot's SDK - among the steps after the act at `t` and
|
|
146
|
+
before the next at `t_next` (t_next itself is that act's own, carried by it): those the act at t_next reads, and
|
|
147
|
+
those a later act may - an act after t_next reads a window key at any step from t_next + 1 + its furthest offset,
|
|
148
|
+
which a window longer than one act's turn reaches back into."""
|
|
149
|
+
out: dict[int, Keys] = {}
|
|
150
|
+
for s, keys in picks(sched, t_next, t):
|
|
151
|
+
if t < s < t_next:
|
|
152
|
+
_merge(out, s, keys)
|
|
153
|
+
for key, offsets in (sched.windows.items() if sched else ()):
|
|
154
|
+
for s in range(max(t + 1, t_next + 1 + offsets[0]), t_next):
|
|
155
|
+
_merge(out, s, (key,))
|
|
156
|
+
return dict(sorted(out.items()))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def envelope(picked: list[tuple[int, Keys]], frames: dict, own: Any, t: int) -> dict:
|
|
160
|
+
"""{"observations": [...], "steps": [...]}: each picked observation - `own`, the act's, at `t`, else `frames[step]`
|
|
161
|
+
- with only the keys read at it (a key it lacks is left out), oldest first. Raises KeyError for a step not in
|
|
162
|
+
`frames`."""
|
|
163
|
+
observations, steps = [], []
|
|
164
|
+
for step, keys in picked:
|
|
165
|
+
obs = own if step == t else frames[step]
|
|
166
|
+
if keys is not None and isinstance(obs, dict):
|
|
167
|
+
obs = {k: obs[k] for k in keys if k in obs}
|
|
168
|
+
observations.append(obs)
|
|
169
|
+
steps.append(step)
|
|
170
|
+
return {"observations": observations, "steps": steps}
|
|
@@ -14,7 +14,8 @@ are on the worker before the act is.
|
|
|
14
14
|
|
|
15
15
|
What a policy declares of what it reads (its cameras, how many a second, when its upload falls behind) is plan 87's
|
|
16
16
|
first layer, which waits for the comparison of each part (the user's 69): the stream here is the transport alone, for
|
|
17
|
-
a policy
|
|
17
|
+
a policy fed every observation since its last act (Observations(every=1); own_observations "since_last_act" until
|
|
18
|
+
contract 0.5.25).
|
|
18
19
|
|
|
19
20
|
"""
|
|
20
21
|
from __future__ import annotations
|
|
@@ -20,13 +20,15 @@ breaks a policy is a frame fitted or encoded other than it was trained on, which
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import math
|
|
23
|
+
import numbers
|
|
24
|
+
from collections.abc import Mapping
|
|
23
25
|
from typing import Any, Literal
|
|
24
26
|
|
|
25
27
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
26
28
|
|
|
27
29
|
from sequence_base.presets.robot_arm import Camera
|
|
28
30
|
|
|
29
|
-
__all__ = ["Actions", "MAX_STREAMS", "MAX_WINDOW", "Observations", "Stream", "Tensors", "check_own_io"]
|
|
31
|
+
__all__ = ["Actions", "MAX_STREAMS", "MAX_WINDOW", "Observations", "Stream", "Tensors", "check_own_io", "rows_of"]
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
|
|
@@ -103,6 +105,15 @@ class Observations(BaseModel):
|
|
|
103
105
|
streams: dict[str, Stream] | None = Field(default=None, max_length=MAX_STREAMS)
|
|
104
106
|
every: int | float | None = Field(default=None, gt=0)
|
|
105
107
|
unit: Literal["steps", "seconds"] = "steps"
|
|
108
|
+
|
|
109
|
+
def __init__(self, streams: dict[str, Stream] | None = None, /, **data: Any) -> None:
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if streams is not None:
|
|
113
|
+
if "streams" in data:
|
|
114
|
+
raise TypeError("Observations takes the keys it reads once: first, or as streams=")
|
|
115
|
+
data["streams"] = streams
|
|
116
|
+
super().__init__(**data)
|
|
106
117
|
pad: Literal["repeat_first", "drop"] = "repeat_first"
|
|
107
118
|
when_behind: Literal["wait", "drop_oldest"] = "wait"
|
|
108
119
|
|
|
@@ -152,6 +163,9 @@ class Actions(BaseModel):
|
|
|
152
163
|
open_loop_horizon: int | None = Field(default=None, ge=1, le=100_000)
|
|
153
164
|
|
|
154
165
|
|
|
166
|
+
aligned: bool | None = None
|
|
167
|
+
|
|
168
|
+
|
|
155
169
|
class Tensors(BaseModel):
|
|
156
170
|
"""A policy's inputs reach its code as torch tensors on its device - a batch dimension first when `batch` - and the
|
|
157
171
|
tensors it answers with go back as arrays; float64 arrays arrive as float32. Converted in the replica, last before
|
|
@@ -162,10 +176,62 @@ class Tensors(BaseModel):
|
|
|
162
176
|
batch: bool = True
|
|
163
177
|
|
|
164
178
|
|
|
179
|
+
def rows_of(answer: Any, actions: Any = None) -> list:
|
|
180
|
+
"""The rows of a policy's own answer that play, in order - one rule for an eval and a robot. `actions`: its Actions,
|
|
181
|
+
the model or its JSON; declared, each element of the field its `key` names (the answer itself for none) is a row,
|
|
182
|
+
a dict one too, cut at its `open_loop_horizon`. Undeclared, the default: a dict, or a list of numbers, is one action;
|
|
183
|
+
a list of lists of one length, or a 2-D array, plays row by row; a ragged or deeper nesting, or a list of dicts, is
|
|
184
|
+
refused, saying to declare actions= - and a number, a text or any other object is one action, as before.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
if actions is not None:
|
|
188
|
+
key = actions.get("key") if isinstance(actions, dict) else actions.key
|
|
189
|
+
horizon = actions.get("open_loop_horizon") if isinstance(actions, dict) else actions.open_loop_horizon
|
|
190
|
+
where = "answer" if key is None else f"answer's {key!r} field"
|
|
191
|
+
if key is not None:
|
|
192
|
+
if not isinstance(answer, Mapping) or key not in answer:
|
|
193
|
+
raise ValueError(f"the policy's answer has no {key!r} field, which its actions=seq.Actions(key={key!r}) "
|
|
194
|
+
"names as the rows to play")
|
|
195
|
+
answer = answer[key]
|
|
196
|
+
if isinstance(answer, (Mapping, str, bytes)) or not (isinstance(answer, (list, tuple))
|
|
197
|
+
or (getattr(answer, "ndim", 0) or 0) >= 1):
|
|
198
|
+
raise ValueError(f"the policy's {where} is {type(answer).__name__}, not a list of rows or an array of them")
|
|
199
|
+
rows = list(answer)
|
|
200
|
+
if not rows:
|
|
201
|
+
raise ValueError(f"the policy's {where} holds no rows to play")
|
|
202
|
+
return rows[:horizon] if horizon else rows
|
|
203
|
+
ndim = getattr(answer, "ndim", None)
|
|
204
|
+
if isinstance(ndim, int) and not isinstance(answer, (Mapping, str, bytes)):
|
|
205
|
+
if ndim <= 1:
|
|
206
|
+
return [answer]
|
|
207
|
+
if ndim == 2:
|
|
208
|
+
return list(answer)
|
|
209
|
+
raise ValueError(_unplayable(f"an array of {ndim} dimensions"))
|
|
210
|
+
if isinstance(answer, (list, tuple)):
|
|
211
|
+
if all(isinstance(v, numbers.Number) for v in answer):
|
|
212
|
+
return [answer]
|
|
213
|
+
if all(isinstance(v, (list, tuple)) or getattr(v, "ndim", None) == 1 for v in answer):
|
|
214
|
+
lengths = [len(v) for v in answer]
|
|
215
|
+
if len(set(lengths)) == 1:
|
|
216
|
+
return list(answer)
|
|
217
|
+
raise ValueError(_unplayable(f"a list of lists of lengths {', '.join(map(str, lengths))}"))
|
|
218
|
+
if all(isinstance(v, Mapping) for v in answer):
|
|
219
|
+
raise ValueError(_unplayable(f"a list of {len(answer)} dicts"))
|
|
220
|
+
kinds = dict.fromkeys("numbers" if isinstance(v, numbers.Number) else "lists" if isinstance(v, (list, tuple))
|
|
221
|
+
else type(v).__name__ for v in answer)
|
|
222
|
+
raise ValueError(_unplayable(f"a list of {' and '.join(kinds)}"))
|
|
223
|
+
return [answer]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _unplayable(what: str) -> str:
|
|
227
|
+
return (f"the policy's answer is {what}, which plays neither as one action (a dict, or a list of numbers) nor row by "
|
|
228
|
+
"row (a list of lists of one length, or a 2-D array): declare actions=seq.Actions() for its rows to play in "
|
|
229
|
+
"order, or turn it into actions yourself (actions_of= on a robot, an adapter in an eval)")
|
|
230
|
+
|
|
231
|
+
|
|
165
232
|
def check_own_io(spec: Any) -> None:
|
|
166
233
|
"""What PolicySpec and DeploymentPolicy both hold a policy's own-I/O declarations to: none beside the robot-arm
|
|
167
|
-
contract, which says what it reads and plays its own way
|
|
168
|
-
not beside own_observations / own_actions, which they take over from."""
|
|
234
|
+
contract, which says what it reads and plays its own way."""
|
|
169
235
|
new = [name for name in ("observations", "actions", "tensors") if getattr(spec, name) is not None]
|
|
170
236
|
if not new:
|
|
171
237
|
return
|
|
@@ -173,7 +239,3 @@ def check_own_io(spec: Any) -> None:
|
|
|
173
239
|
raise ValueError(f"{', '.join(new)} {'is' if len(new) == 1 else 'are'} for a policy of its own inputs and "
|
|
174
240
|
"outputs; this one declares the robot-arm contract, where observation history and the action "
|
|
175
241
|
"chunk say it")
|
|
176
|
-
old = [name for name in ("own_observations", "own_actions") if getattr(spec, name)]
|
|
177
|
-
if old:
|
|
178
|
-
raise ValueError(f"{', '.join(new)} and {', '.join(old)} say what a policy reads and plays two ways; declare "
|
|
179
|
-
"observations= and actions= alone")
|
|
@@ -377,6 +377,10 @@ class PolicySpec(BaseModel):
|
|
|
377
377
|
|
|
378
378
|
streaming: bool = Field(default=False, exclude=True)
|
|
379
379
|
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
feedback: bool | None = None
|
|
383
|
+
|
|
380
384
|
action: ActionContract | None = None
|
|
381
385
|
|
|
382
386
|
observation: ObservationContract | None = None
|
|
@@ -396,12 +400,6 @@ class PolicySpec(BaseModel):
|
|
|
396
400
|
|
|
397
401
|
|
|
398
402
|
|
|
399
|
-
own_observations: Literal["latest", "since_last_act"] | None = None
|
|
400
|
-
own_actions: Literal["one", "steps"] | None = None
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
403
|
observations: Observations | None = None
|
|
406
404
|
actions: Actions | None = None
|
|
407
405
|
tensors: Tensors | None = None
|
|
@@ -433,9 +431,6 @@ class PolicySpec(BaseModel):
|
|
|
433
431
|
|
|
434
432
|
@model_validator(mode="after")
|
|
435
433
|
def _own_io(self) -> "PolicySpec":
|
|
436
|
-
if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
|
|
437
|
-
raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
|
|
438
|
-
"declares the robot-arm contract, where observation history and the action chunk say it")
|
|
439
434
|
check_own_io(self)
|
|
440
435
|
return self
|
|
441
436
|
|
|
@@ -46,6 +46,11 @@ class ActRequest(BaseModel):
|
|
|
46
46
|
description="The newest observation's number in its episode's stream (POST /observe)")
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
|
|
50
|
+
feedback: dict[str, Any] | None = Field(default=None,
|
|
51
|
+
description="What the robot was playing: left, delay, played, last")
|
|
52
|
+
|
|
53
|
+
|
|
49
54
|
class ActResponse(BaseModel):
|
|
50
55
|
"""200 from /act, version 2. `outputs` is infer()'s return value, unchanged; the rest is platform metadata."""
|
|
51
56
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
What a policy that asks for execution feedback is handed (PolicySpec.feedback): its infer declares `feedback` and gets,
|
|
3
|
+
with each act, what the robot was playing when it sent that act - the rows it had not played yet, how many control
|
|
4
|
+
steps it expects to play before the answer is back, how many rows of its chunk it had played, and the row it played
|
|
5
|
+
last.
|
|
6
|
+
|
|
7
|
+
In one sentence: the robot's executor (sequence_ai.Player, or an eval's) → puts {left, delay, played, last} in the act
|
|
8
|
+
it sends → the worker checks it by feedback_of and hands infer a Feedback → a policy that inpaints (RTC) freezes the rows
|
|
9
|
+
the delay covers, one trained on its last action reads `last`.
|
|
10
|
+
|
|
11
|
+
WHY the executor fills it, act by act, and the platform keeps none of it: which rows are left and how long a round
|
|
12
|
+
trip takes only the robot knows - a worker times the inference alone.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import numbers
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
__all__ = ["Feedback", "feedback_of", "KEYS"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
KEYS = ("left", "delay", "played", "last")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Feedback:
|
|
28
|
+
"""What the robot was playing when it sent this act: `left` - the rows of the chunk it was playing that it had not
|
|
29
|
+
played yet, in order, the ones due first and then the chunk's spare ones (empty on an episode's first act, or when
|
|
30
|
+
it held none); `delay` - how many control steps it expects to play before this answer is back, its longest of its
|
|
31
|
+
last 10 round trips times its control rate, rounded up (0 before it measured one, and in an eval); `played` - how
|
|
32
|
+
many rows of that chunk it had played when it observed; `last` - the row it played last, as the platform clipped it
|
|
33
|
+
(None before it played one). All four are the robot's: they are read, never set."""
|
|
34
|
+
|
|
35
|
+
__slots__ = ("_left", "_delay", "_played", "_last")
|
|
36
|
+
|
|
37
|
+
def __init__(self, left: Any = (), delay: int = 0, played: int = 0, last: Any = None) -> None:
|
|
38
|
+
object.__setattr__(self, "_left", tuple(left))
|
|
39
|
+
object.__setattr__(self, "_delay", int(delay))
|
|
40
|
+
object.__setattr__(self, "_played", int(played))
|
|
41
|
+
object.__setattr__(self, "_last", last)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def left(self) -> tuple:
|
|
45
|
+
return self._left
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def delay(self) -> int:
|
|
49
|
+
return self._delay
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def played(self) -> int:
|
|
53
|
+
return self._played
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def last(self) -> Any:
|
|
57
|
+
return self._last
|
|
58
|
+
|
|
59
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
60
|
+
raise AttributeError(f"Feedback.{name} is what the robot was playing: read it, never set it")
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
return (f"Feedback(left={len(self._left)} rows, delay={self._delay}, played={self._played}, "
|
|
64
|
+
f"last={self._last!r})")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def feedback_of(value: Any, width: int | None = None) -> Feedback:
|
|
68
|
+
"""The Feedback an act's `feedback` says - a dict of `left` (a list of rows, or an array of them), `delay` and
|
|
69
|
+
`played` (counts from 0) and `last` (a row, or None) - checked, the one rule a worker and a robot's SDK read it by:
|
|
70
|
+
with `width` (the robot-arm preset's action_dim) each row is that many numbers; without, a row of a policy's own
|
|
71
|
+
outputs is handed as it came. Raises ValueError saying what is wrong."""
|
|
72
|
+
if not isinstance(value, Mapping):
|
|
73
|
+
raise ValueError(f"feedback is a dict of {', '.join(KEYS)}, not {type(value).__name__}")
|
|
74
|
+
unknown = [k for k in value if k not in KEYS]
|
|
75
|
+
if unknown:
|
|
76
|
+
raise ValueError(f"feedback has no {', '.join(map(repr, unknown))}: it holds {', '.join(KEYS)}")
|
|
77
|
+
left = value.get("left")
|
|
78
|
+
if left is None:
|
|
79
|
+
left = ()
|
|
80
|
+
elif isinstance(left, (str, bytes, Mapping)) or not (isinstance(left, (list, tuple))
|
|
81
|
+
or (getattr(left, "ndim", 0) or 0) >= 1):
|
|
82
|
+
raise ValueError(f"feedback's left is the rows the robot had not played, a list of them, not "
|
|
83
|
+
f"{type(left).__name__}")
|
|
84
|
+
rows = list(left)
|
|
85
|
+
counts = {}
|
|
86
|
+
for key in ("delay", "played"):
|
|
87
|
+
n = value.get(key, 0)
|
|
88
|
+
if isinstance(n, bool) or not isinstance(n, numbers.Integral) or n < 0:
|
|
89
|
+
raise ValueError(f"feedback's {key} is a count of control steps from 0, not {n!r}")
|
|
90
|
+
counts[key] = int(n)
|
|
91
|
+
last = value.get("last")
|
|
92
|
+
if width is not None:
|
|
93
|
+
for where, row in [*((f"left[{i}]", r) for i, r in enumerate(rows)),
|
|
94
|
+
*((("last", last),) if last is not None else ())]:
|
|
95
|
+
_fits(where, row, width)
|
|
96
|
+
return Feedback(rows, counts["delay"], counts["played"], last)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _fits(where: str, row: Any, width: int) -> None:
|
|
100
|
+
if isinstance(row, (str, bytes, Mapping)) or not (isinstance(row, (list, tuple)) or getattr(row, "ndim", None) == 1):
|
|
101
|
+
raise ValueError(f"feedback's {where} is a row of {width} numbers (the policy's action_dim), not "
|
|
102
|
+
f"{type(row).__name__}")
|
|
103
|
+
if len(row) != width:
|
|
104
|
+
raise ValueError(f"feedback's {where} has {len(row)} values; the policy's action_dim is {width}")
|
|
105
|
+
if not all(isinstance(v, numbers.Real) and not isinstance(v, bool) for v in row):
|
|
106
|
+
raise ValueError(f"feedback's {where} is a row of {width} numbers (the policy's action_dim)")
|
|
@@ -129,6 +129,10 @@ class ActionContract(BaseModel):
|
|
|
129
129
|
bounds: list[tuple[float, float]] | None = Field(default=None, max_length=256)
|
|
130
130
|
max_delta: float | list[float] | None = None
|
|
131
131
|
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
aligned: bool | None = None
|
|
135
|
+
|
|
132
136
|
@model_validator(mode="after")
|
|
133
137
|
def _limits_fit(self) -> "ActionContract":
|
|
134
138
|
if self.bounds is not None:
|
|
@@ -28,6 +28,20 @@
|
|
|
28
28
|
"description": "The episode this call belongs to (per-episode state)",
|
|
29
29
|
"title": "Episode Id"
|
|
30
30
|
},
|
|
31
|
+
"feedback": {
|
|
32
|
+
"anyOf": [
|
|
33
|
+
{
|
|
34
|
+
"additionalProperties": true,
|
|
35
|
+
"type": "object"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "null"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"default": null,
|
|
42
|
+
"description": "What the robot was playing: left, delay, played, last",
|
|
43
|
+
"title": "Feedback"
|
|
44
|
+
},
|
|
31
45
|
"inputs": {
|
|
32
46
|
"description": "Whatever the policy's infer() takes, exactly as the robot's code built it",
|
|
33
47
|
"title": "Inputs"
|
|
@@ -93,5 +107,5 @@
|
|
|
93
107
|
],
|
|
94
108
|
"title": "ActRequest",
|
|
95
109
|
"type": "object",
|
|
96
|
-
"x-contract-version": "0.5.
|
|
110
|
+
"x-contract-version": "0.5.26"
|
|
97
111
|
}
|
|
@@ -23,6 +23,18 @@
|
|
|
23
23
|
"action_space": {
|
|
24
24
|
"$ref": "#/$defs/ActionSpace"
|
|
25
25
|
},
|
|
26
|
+
"aligned": {
|
|
27
|
+
"anyOf": [
|
|
28
|
+
{
|
|
29
|
+
"type": "boolean"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "null"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"default": null,
|
|
36
|
+
"title": "Aligned"
|
|
37
|
+
},
|
|
26
38
|
"bounds": {
|
|
27
39
|
"anyOf": [
|
|
28
40
|
{
|
|
@@ -84,5 +96,5 @@
|
|
|
84
96
|
],
|
|
85
97
|
"title": "ActionContract",
|
|
86
98
|
"type": "object",
|
|
87
|
-
"x-contract-version": "0.5.
|
|
99
|
+
"x-contract-version": "0.5.26"
|
|
88
100
|
}
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
"additionalProperties": false,
|
|
3
3
|
"description": "Which rows of a policy's answer play. `key`: the answer's field that holds them (None: the answer itself) - a\nlist of rows or a 2-D array, played in order, `hz` rows a second, `open_loop_horizon` of them before the next act\n(None: all). A shorter answer plays whole and the next act comes sooner; a longer one is cut there. With `hz` a worker\nknows how long one answer plays (open_loop_horizon / hz) and a control period (1 / hz) and times a copy's robots by\nthem, as a robot-arm chunk's covers_seconds and control_frequency_hz do. Undeclared, an answer plays as the default\nsays: a dict or a 1-D list one action, a 2-D one row by row.",
|
|
4
4
|
"properties": {
|
|
5
|
+
"aligned": {
|
|
6
|
+
"anyOf": [
|
|
7
|
+
{
|
|
8
|
+
"type": "boolean"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "null"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"default": null,
|
|
15
|
+
"title": "Aligned"
|
|
16
|
+
},
|
|
5
17
|
"hz": {
|
|
6
18
|
"anyOf": [
|
|
7
19
|
{
|
|
@@ -47,5 +59,5 @@
|
|
|
47
59
|
},
|
|
48
60
|
"title": "Actions",
|
|
49
61
|
"type": "object",
|
|
50
|
-
"x-contract-version": "0.5.
|
|
62
|
+
"x-contract-version": "0.5.26"
|
|
51
63
|
}
|