sequence-base 0.4.26__tar.gz → 0.4.28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {sequence_base-0.4.26 → sequence_base-0.4.28}/PKG-INFO +1 -1
  2. {sequence_base-0.4.26 → sequence_base-0.4.28}/pyproject.toml +1 -1
  3. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/__init__.py +14 -0
  4. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/constants/limits.py +9 -0
  5. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/constants/version.py +4 -1
  6. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/__init__.py +7 -0
  7. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/connect.py +5 -0
  8. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/deployments.py +27 -6
  9. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/flags.py +4 -0
  10. sequence_base-0.4.28/sequence_base/contract/obs_schedule.py +170 -0
  11. sequence_base-0.4.28/sequence_base/contract/observe.py +66 -0
  12. sequence_base-0.4.28/sequence_base/contract/own_io.py +238 -0
  13. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/policy.py +36 -7
  14. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/wire.py +5 -0
  15. sequence_base-0.4.28/sequence_base/episode.py +58 -0
  16. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/presets/robot_arm.py +30 -0
  17. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schema_export.py +9 -0
  18. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ActRequest.json +15 -1
  19. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ActResponse.json +1 -1
  20. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ActionChunk.json +1 -1
  21. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ActionContract.json +1 -1
  22. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ActionStep.json +1 -1
  23. sequence_base-0.4.28/sequence_base/schemas/Actions.json +51 -0
  24. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/AdapterPair.json +1 -1
  25. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/AddLocal.json +1 -1
  26. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Autoscaler.json +1 -1
  27. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
  28. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/BenchmarkRegisterRequest.json +1 -1
  29. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/BenchmarkSpec.json +7 -4
  30. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
  31. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/BootstrapResponse.json +1 -1
  32. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Camera.json +51 -1
  33. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/CodeBundle.json +1 -1
  34. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Concurrency.json +1 -1
  35. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Conditions.json +1 -1
  36. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ConnectRequest.json +1 -1
  37. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ConnectResponse.json +259 -1
  38. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/DeploymentAccepted.json +1 -1
  39. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/DeploymentPolicy.json +291 -25
  40. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/DeploymentRequest.json +305 -25
  41. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
  42. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Embodiment.json +51 -1
  43. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ErrorBody.json +1 -1
  44. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ErrorCodes.json +1 -1
  45. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Estimate.json +1 -1
  46. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
  47. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/EvalReport.json +1 -1
  48. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/EvalRequest.json +1 -1
  49. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/EvalResponse.json +1 -1
  50. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageFrame.json +1 -1
  51. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageSpec.json +1 -1
  52. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
  53. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepApt.json +1 -1
  54. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepEnv.json +1 -1
  55. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepProject.json +1 -1
  56. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepRun.json +1 -1
  57. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
  58. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ImageStepUvPip.json +1 -1
  59. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/InferParam.json +1 -1
  60. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Lease.json +1 -1
  61. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/MetricAgg.json +1 -1
  62. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Observation.json +1 -1
  63. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ObservationContract.json +51 -1
  64. sequence_base-0.4.28/sequence_base/schemas/Observations.json +264 -0
  65. sequence_base-0.4.28/sequence_base/schemas/ObserveRequest.json +57 -0
  66. sequence_base-0.4.28/sequence_base/schemas/ObserveResponse.json +17 -0
  67. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/PolicyRegistration.json +305 -36
  68. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/PolicySpec.json +298 -32
  69. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Project.json +1 -1
  70. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Proprioception.json +1 -1
  71. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/RenderSpec.json +1 -1
  72. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/ReplaySpec.json +1 -1
  73. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Resources.json +1 -1
  74. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/RolloutResult.json +1 -1
  75. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/SecretCreateRequest.json +1 -1
  76. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/SecretInfo.json +1 -1
  77. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/SecretRef.json +1 -1
  78. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/SeqFlags.json +4 -2
  79. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Step.json +1 -1
  80. sequence_base-0.4.28/sequence_base/schemas/Stream.json +193 -0
  81. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/TaskAgg.json +1 -1
  82. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/TemplateFiles.json +1 -1
  83. sequence_base-0.4.28/sequence_base/schemas/Tensors.json +14 -0
  84. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Timeouts.json +15 -1
  85. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/UploadTarget.json +1 -1
  86. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/Variant.json +1 -1
  87. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeFile.json +1 -1
  88. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeInfo.json +1 -1
  89. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeMount.json +1 -1
  90. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeRef.json +1 -1
  91. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
  92. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
  93. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/schemas/WarmingError.json +1 -1
  94. {sequence_base-0.4.26 → sequence_base-0.4.28}/.gitignore +0 -0
  95. {sequence_base-0.4.26 → sequence_base-0.4.28}/README.md +0 -0
  96. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/auth.py +0 -0
  97. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/codec/__init__.py +0 -0
  98. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/codec/image.py +0 -0
  99. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/constants/__init__.py +0 -0
  100. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/constants/cards.py +0 -0
  101. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/constants/headers.py +0 -0
  102. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/construct.py +0 -0
  103. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/accounts.py +0 -0
  104. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/act.py +0 -0
  105. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/adapters.py +0 -0
  106. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/benchmarks.py +0 -0
  107. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/errors.py +0 -0
  108. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/eval.py +0 -0
  109. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/io.py +0 -0
  110. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/templates.py +0 -0
  111. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/contract/volumes.py +0 -0
  112. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/errors/__init__.py +0 -0
  113. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/errors/transport.py +0 -0
  114. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/msgpack_io.py +0 -0
  115. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/obs/__init__.py +0 -0
  116. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/obs/log.py +0 -0
  117. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/obs/rid.py +0 -0
  118. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/presets/__init__.py +0 -0
  119. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/presets/frames.py +0 -0
  120. {sequence_base-0.4.26 → sequence_base-0.4.28}/sequence_base/telemetry/__init__.py +0 -0
  121. {sequence_base-0.4.26 → sequence_base-0.4.28}/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.26
3
+ Version: 0.4.28
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.26"
3
+ version = "0.4.28"
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"]
@@ -26,6 +26,9 @@ from sequence_base.contract import (
26
26
  Camera,
27
27
  Embodiment,
28
28
  ObservationContract,
29
+ ObserveFrame,
30
+ ObserveRequest,
31
+ ObserveResponse,
29
32
  MAX_CODE_BUNDLE_BYTES,
30
33
  UploadTarget,
31
34
  VolumeFile,
@@ -81,6 +84,10 @@ from sequence_base.contract import (
81
84
  PolicyConstructor,
82
85
  PolicyRegistration,
83
86
  PolicySpec,
87
+ Observations,
88
+ Stream,
89
+ Actions,
90
+ Tensors,
84
91
  Proprioception,
85
92
  RenderSpec,
86
93
  ReplaySpec,
@@ -120,6 +127,9 @@ from sequence_base.errors.transport import (
120
127
  )
121
128
 
122
129
 
130
+ from sequence_base.episode import Episode
131
+
132
+
123
133
  from sequence_base import auth
124
134
 
125
135
 
@@ -131,11 +141,13 @@ __all__ = [
131
141
 
132
142
  "ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
133
143
  "ActRequest", "ActResponse",
144
+ "ObserveFrame", "ObserveRequest", "ObserveResponse",
134
145
 
135
146
  "SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
136
147
  "FROZEN_PATH", "TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "ImageStep", "ImageSpec",
137
148
  "VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
138
149
  "PolicySpec", "PolicyRegistration", "PolicyConstructor",
150
+ "Observations", "Stream", "Actions", "Tensors",
139
151
 
140
152
  "MAX_CODE_BUNDLE_BYTES", "CodeBundle",
141
153
  "Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
@@ -162,6 +174,8 @@ __all__ = [
162
174
 
163
175
  "TransportUnavailable", "TransportWarming", "TransportRejected", "TransportOverloaded",
164
176
 
177
+ "Episode",
178
+
165
179
  "auth",
166
180
  ]
167
181
 
@@ -28,3 +28,12 @@ TASK_DESCRIPTION_MAX = 300
28
28
  METRIC_UNITS_MAX = 64
29
29
  METRIC_UNIT_MAX = 16
30
30
 
31
+
32
+
33
+
34
+
35
+
36
+ MAX_STREAM_FRAMES = 1024
37
+ MAX_STREAM_BYTES = 64 * 1024 * 1024
38
+ MAX_WORKER_STREAM_BYTES = 1024 * 1024 * 1024
39
+ STREAM_WAIT_S = 10.0
@@ -4,7 +4,10 @@
4
4
 
5
5
 
6
6
 
7
- CONTRACT_VERSION = "0.5.23"
7
+ CONTRACT_VERSION = "0.5.25"
8
+
9
+
10
+
8
11
 
9
12
 
10
13
 
@@ -15,6 +15,8 @@ from sequence_base.contract.act import (
15
15
 
16
16
  from sequence_base.contract.wire import ActRequest, ActResponse
17
17
 
18
+ from sequence_base.contract.observe import ObserveFrame, ObserveRequest, ObserveResponse
19
+
18
20
 
19
21
  from sequence_base.contract.flags import SeqFlags
20
22
  from sequence_base.contract.policy import (
@@ -44,6 +46,9 @@ from sequence_base.contract.policy import (
44
46
  )
45
47
 
46
48
 
49
+ from sequence_base.contract.own_io import Actions, Observations, Stream, Tensors
50
+
51
+
47
52
  from sequence_base.contract.deployments import (
48
53
  MAX_CODE_BUNDLE_BYTES,
49
54
  Autoscaler,
@@ -119,12 +124,14 @@ __all__ = [
119
124
 
120
125
  "ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
121
126
  "ActRequest", "ActResponse",
127
+ "ObserveFrame", "ObserveRequest", "ObserveResponse",
122
128
 
123
129
  "SeqFlags",
124
130
  "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
125
131
  "TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "FROZEN_PATH", "ImageStep", "ImageSpec",
126
132
  "VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
127
133
  "PolicySpec", "PolicyRegistration", "PolicyConstructor",
134
+ "Observations", "Stream", "Actions", "Tensors",
128
135
 
129
136
  "MAX_CODE_BUNDLE_BYTES", "CodeBundle",
130
137
  "Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
@@ -16,6 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field
16
16
 
17
17
  from sequence_base.contract.errors import WarmingError
18
18
  from sequence_base.contract.io import ActionContract, ObservationContract
19
+ from sequence_base.contract.own_io import Actions, Observations
19
20
 
20
21
  __all__ = ["Lease", "ConnectRequest", "ConnectResponse", "WarmingError"]
21
22
 
@@ -57,3 +58,7 @@ class ConnectResponse(BaseModel):
57
58
  embodiment: str | None = None
58
59
 
59
60
  io: str | None = None
61
+
62
+
63
+ observations: Observations | None = None
64
+ actions: Actions | None = None
@@ -22,10 +22,12 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
22
22
 
23
23
  from sequence_base.contract.accounts import SecretRef
24
24
  from sequence_base.contract.io import Embodiment, ObservationContract
25
+ from sequence_base.contract.own_io import Actions, Observations, Tensors, check_own_io
25
26
  from sequence_base.contract.policy import (
26
27
  KIND_NAME,
27
28
  ActionContract,
28
29
  Concurrency,
30
+ Episodes,
29
31
  ImageSpec,
30
32
  VolumeMount,
31
33
  )
@@ -105,6 +107,11 @@ class Timeouts(BaseModel):
105
107
  startup: int = Field(..., ge=60, le=18000)
106
108
 
107
109
 
110
+
111
+
112
+ episode_idle: int | None = Field(default=None, ge=10, le=86400)
113
+
114
+
108
115
  class InferParam(BaseModel):
109
116
  """One inference parameter the policy's `@seq.infer` declares — `def infer(self, obs, *, num_steps: int = 10)`
110
117
  — with its type and default. An /act or an eval may set it; the worker passes only declared names, cast to
@@ -171,7 +178,12 @@ class DeploymentPolicy(BaseModel):
171
178
 
172
179
  handler_ref: str
173
180
  action: ActionContract | None = None
174
- streaming: bool = False
181
+
182
+
183
+ episodes: Episodes | None = None
184
+
185
+
186
+ streaming: bool = Field(default=False, exclude=True)
175
187
  secrets: list[SecretRef] = Field(default_factory=list)
176
188
 
177
189
 
@@ -204,15 +216,24 @@ class DeploymentPolicy(BaseModel):
204
216
 
205
217
 
206
218
 
219
+ observations: Observations | None = None
220
+ actions: Actions | None = None
221
+ tensors: Tensors | None = None
207
222
 
208
- own_observations: Literal["latest", "since_last_act"] | None = None
209
- own_actions: Literal["one", "steps"] | None = None
223
+ @model_validator(mode="after")
224
+ def _episodes_of_streaming(self) -> "DeploymentPolicy":
225
+
226
+ if self.streaming and self.episodes not in (None, "own"):
227
+ raise ValueError(f"streaming is what a record from before contract 0.5.24 says; beside "
228
+ f"episodes={self.episodes!r}, leave it out")
229
+ if self.streaming:
230
+ self.episodes = "own"
231
+ self.streaming = self.episodes == "own"
232
+ return self
210
233
 
211
234
  @model_validator(mode="after")
212
235
  def _own_io(self) -> "DeploymentPolicy":
213
- if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
214
- raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
215
- "declares the robot-arm contract, where observation history and the action chunk say it")
236
+ check_own_io(self)
216
237
  return self
217
238
 
218
239
  @model_validator(mode="after")
@@ -48,6 +48,10 @@ class SeqFlags(IntFlag):
48
48
 
49
49
 
50
50
 
51
+ SAMPLE = 524288
52
+
53
+
54
+
51
55
 
52
56
 
53
57
 
@@ -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}
@@ -0,0 +1,66 @@
1
+ """
2
+ The observation stream: a policy of its own inputs that reads every observation since its last act is sent each one as
3
+ the robot makes it, on a connection of its own (POST /observe), and an act carries only the newest.
4
+
5
+ In one sentence: the robot's SDK numbers each observation of an episode from 0 → posts them as they come, each post the
6
+ ones that came while the last was on its way (ObserveRequest) → the worker keeps them by episode and answers how far it
7
+ has every one (ObserveResponse.through) → an act carries the newest observation and its number (ActRequest.seq) → the
8
+ worker waits for the ones before it, STREAM_WAIT_S at most, and hands the policy {"observations": [...]}, oldest first,
9
+ the act's own last - what an eval's since_last_act sends in one act today.
10
+
11
+ WHY: an act was the only upload, so every frame between two acts waited for the next one - 24 frames, 1304 KB and a read
12
+ p50 of 425 ms from a robot at home. Sent as they are made, they
13
+ are on the worker before the act is.
14
+
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
+ 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 fed every observation since its last act (Observations(every=1); own_observations "since_last_act" until
18
+ contract 0.5.25).
19
+
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from typing import Any
24
+
25
+ from pydantic import BaseModel, ConfigDict, Field
26
+
27
+ __all__ = ["FORMAT", "FRAMES_MISSING", "FRAMES_OVER_LIMIT", "ObserveFrame", "ObserveRequest", "ObserveResponse"]
28
+
29
+
30
+
31
+ FORMAT = "x-seq-observe/1"
32
+
33
+
34
+
35
+
36
+ FRAMES_MISSING = "frames_missing"
37
+ FRAMES_OVER_LIMIT = "frames_over_limit"
38
+
39
+
40
+ class ObserveFrame(BaseModel):
41
+ """One observation of an episode: its number in it, from 0, and the inputs, as the policy's infer() takes one."""
42
+
43
+ model_config = ConfigDict(extra="forbid")
44
+
45
+ seq: int = Field(..., ge=0, description="The observation's number in its episode, from 0")
46
+ inputs: Any = Field(..., description="One observation, exactly as the robot's code built it")
47
+
48
+
49
+ class ObserveRequest(BaseModel):
50
+ """POST /observe: the observations of one episode that came since the last post, and the numbers the robot left out
51
+ on purpose, which the worker then does not wait for."""
52
+
53
+ model_config = ConfigDict(extra="forbid")
54
+
55
+ episode_id: str = Field(..., min_length=1, max_length=256)
56
+ frames: list[ObserveFrame] = Field(default_factory=list)
57
+ skipped: list[int] = Field(default_factory=list, description="Numbers left out on purpose, never to come")
58
+
59
+
60
+ class ObserveResponse(BaseModel):
61
+ """200 from POST /observe: the highest number up to which the worker holds every observation of the episode, or has
62
+ been told one is skipped (-1: none yet). A number sent again is answered the same, never kept twice."""
63
+
64
+ model_config = ConfigDict(extra="forbid")
65
+
66
+ through: int = Field(..., ge=-1)