sequence-base 0.4.26__tar.gz → 0.4.27__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 (120) hide show
  1. {sequence_base-0.4.26 → sequence_base-0.4.27}/PKG-INFO +1 -1
  2. {sequence_base-0.4.26 → sequence_base-0.4.27}/pyproject.toml +1 -1
  3. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/__init__.py +14 -0
  4. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/constants/limits.py +9 -0
  5. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/constants/version.py +3 -1
  6. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/__init__.py +7 -0
  7. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/connect.py +5 -0
  8. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/deployments.py +31 -1
  9. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/flags.py +4 -0
  10. sequence_base-0.4.27/sequence_base/contract/observe.py +65 -0
  11. sequence_base-0.4.27/sequence_base/contract/own_io.py +179 -0
  12. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/policy.py +40 -2
  13. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/wire.py +5 -0
  14. sequence_base-0.4.27/sequence_base/episode.py +58 -0
  15. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/presets/robot_arm.py +30 -0
  16. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schema_export.py +9 -0
  17. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ActRequest.json +15 -1
  18. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ActResponse.json +1 -1
  19. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ActionChunk.json +1 -1
  20. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ActionContract.json +1 -1
  21. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ActionStep.json +1 -1
  22. sequence_base-0.4.27/sequence_base/schemas/Actions.json +51 -0
  23. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/AdapterPair.json +1 -1
  24. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/AddLocal.json +1 -1
  25. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Autoscaler.json +1 -1
  26. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
  27. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/BenchmarkRegisterRequest.json +1 -1
  28. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/BenchmarkSpec.json +7 -4
  29. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
  30. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/BootstrapResponse.json +1 -1
  31. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Camera.json +51 -1
  32. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/CodeBundle.json +1 -1
  33. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Concurrency.json +1 -1
  34. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Conditions.json +1 -1
  35. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ConnectRequest.json +1 -1
  36. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ConnectResponse.json +259 -1
  37. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/DeploymentAccepted.json +1 -1
  38. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/DeploymentPolicy.json +299 -1
  39. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/DeploymentRequest.json +313 -1
  40. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
  41. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Embodiment.json +51 -1
  42. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ErrorBody.json +1 -1
  43. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ErrorCodes.json +1 -1
  44. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Estimate.json +1 -1
  45. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
  46. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/EvalReport.json +1 -1
  47. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/EvalRequest.json +1 -1
  48. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/EvalResponse.json +1 -1
  49. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageFrame.json +1 -1
  50. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageSpec.json +1 -1
  51. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
  52. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepApt.json +1 -1
  53. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepEnv.json +1 -1
  54. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepProject.json +1 -1
  55. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepRun.json +1 -1
  56. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
  57. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ImageStepUvPip.json +1 -1
  58. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/InferParam.json +1 -1
  59. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Lease.json +1 -1
  60. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/MetricAgg.json +1 -1
  61. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Observation.json +1 -1
  62. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ObservationContract.json +51 -1
  63. sequence_base-0.4.27/sequence_base/schemas/Observations.json +264 -0
  64. sequence_base-0.4.27/sequence_base/schemas/ObserveRequest.json +57 -0
  65. sequence_base-0.4.27/sequence_base/schemas/ObserveResponse.json +17 -0
  66. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/PolicyRegistration.json +306 -5
  67. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/PolicySpec.json +299 -1
  68. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Project.json +1 -1
  69. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Proprioception.json +1 -1
  70. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/RenderSpec.json +1 -1
  71. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/ReplaySpec.json +1 -1
  72. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Resources.json +1 -1
  73. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/RolloutResult.json +1 -1
  74. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/SecretCreateRequest.json +1 -1
  75. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/SecretInfo.json +1 -1
  76. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/SecretRef.json +1 -1
  77. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/SeqFlags.json +4 -2
  78. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Step.json +1 -1
  79. sequence_base-0.4.27/sequence_base/schemas/Stream.json +193 -0
  80. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/TaskAgg.json +1 -1
  81. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/TemplateFiles.json +1 -1
  82. sequence_base-0.4.27/sequence_base/schemas/Tensors.json +14 -0
  83. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Timeouts.json +15 -1
  84. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/UploadTarget.json +1 -1
  85. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/Variant.json +1 -1
  86. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeFile.json +1 -1
  87. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeInfo.json +1 -1
  88. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeMount.json +1 -1
  89. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeRef.json +1 -1
  90. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
  91. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
  92. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/schemas/WarmingError.json +1 -1
  93. {sequence_base-0.4.26 → sequence_base-0.4.27}/.gitignore +0 -0
  94. {sequence_base-0.4.26 → sequence_base-0.4.27}/README.md +0 -0
  95. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/auth.py +0 -0
  96. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/codec/__init__.py +0 -0
  97. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/codec/image.py +0 -0
  98. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/constants/__init__.py +0 -0
  99. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/constants/cards.py +0 -0
  100. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/constants/headers.py +0 -0
  101. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/construct.py +0 -0
  102. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/accounts.py +0 -0
  103. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/act.py +0 -0
  104. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/adapters.py +0 -0
  105. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/benchmarks.py +0 -0
  106. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/errors.py +0 -0
  107. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/eval.py +0 -0
  108. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/io.py +0 -0
  109. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/templates.py +0 -0
  110. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/contract/volumes.py +0 -0
  111. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/errors/__init__.py +0 -0
  112. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/errors/transport.py +0 -0
  113. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/msgpack_io.py +0 -0
  114. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/obs/__init__.py +0 -0
  115. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/obs/log.py +0 -0
  116. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/obs/rid.py +0 -0
  117. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/presets/__init__.py +0 -0
  118. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/presets/frames.py +0 -0
  119. {sequence_base-0.4.26 → sequence_base-0.4.27}/sequence_base/telemetry/__init__.py +0 -0
  120. {sequence_base-0.4.26 → sequence_base-0.4.27}/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.27
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.27"
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,9 @@
4
4
 
5
5
 
6
6
 
7
- CONTRACT_VERSION = "0.5.23"
7
+ CONTRACT_VERSION = "0.5.24"
8
+
9
+
8
10
 
9
11
 
10
12
 
@@ -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
 
@@ -208,11 +220,29 @@ class DeploymentPolicy(BaseModel):
208
220
  own_observations: Literal["latest", "since_last_act"] | None = None
209
221
  own_actions: Literal["one", "steps"] | None = None
210
222
 
223
+
224
+
225
+ observations: Observations | None = None
226
+ actions: Actions | None = None
227
+ tensors: Tensors | None = None
228
+
229
+ @model_validator(mode="after")
230
+ def _episodes_of_streaming(self) -> "DeploymentPolicy":
231
+
232
+ if self.streaming and self.episodes not in (None, "own"):
233
+ raise ValueError(f"streaming is what a record from before contract 0.5.24 says; beside "
234
+ f"episodes={self.episodes!r}, leave it out")
235
+ if self.streaming:
236
+ self.episodes = "own"
237
+ self.streaming = self.episodes == "own"
238
+ return self
239
+
211
240
  @model_validator(mode="after")
212
241
  def _own_io(self) -> "DeploymentPolicy":
213
242
  if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
214
243
  raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
215
244
  "declares the robot-arm contract, where observation history and the action chunk say it")
245
+ check_own_io(self)
216
246
  return self
217
247
 
218
248
  @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,65 @@
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 that declares own_observations "since_last_act".
18
+
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from typing import Any
23
+
24
+ from pydantic import BaseModel, ConfigDict, Field
25
+
26
+ __all__ = ["FORMAT", "FRAMES_MISSING", "FRAMES_OVER_LIMIT", "ObserveFrame", "ObserveRequest", "ObserveResponse"]
27
+
28
+
29
+
30
+ FORMAT = "x-seq-observe/1"
31
+
32
+
33
+
34
+
35
+ FRAMES_MISSING = "frames_missing"
36
+ FRAMES_OVER_LIMIT = "frames_over_limit"
37
+
38
+
39
+ class ObserveFrame(BaseModel):
40
+ """One observation of an episode: its number in it, from 0, and the inputs, as the policy's infer() takes one."""
41
+
42
+ model_config = ConfigDict(extra="forbid")
43
+
44
+ seq: int = Field(..., ge=0, description="The observation's number in its episode, from 0")
45
+ inputs: Any = Field(..., description="One observation, exactly as the robot's code built it")
46
+
47
+
48
+ class ObserveRequest(BaseModel):
49
+ """POST /observe: the observations of one episode that came since the last post, and the numbers the robot left out
50
+ on purpose, which the worker then does not wait for."""
51
+
52
+ model_config = ConfigDict(extra="forbid")
53
+
54
+ episode_id: str = Field(..., min_length=1, max_length=256)
55
+ frames: list[ObserveFrame] = Field(default_factory=list)
56
+ skipped: list[int] = Field(default_factory=list, description="Numbers left out on purpose, never to come")
57
+
58
+
59
+ class ObserveResponse(BaseModel):
60
+ """200 from POST /observe: the highest number up to which the worker holds every observation of the episode, or has
61
+ been told one is skipped (-1: none yet). A number sent again is answered the same, never kept twice."""
62
+
63
+ model_config = ConfigDict(extra="forbid")
64
+
65
+ through: int = Field(..., ge=-1)
@@ -0,0 +1,179 @@
1
+ """
2
+ What a policy of its own inputs and outputs declares (contract 0.5.24): which keys of an observation it reads and at
3
+ which steps, a camera's frames as it was trained to see them, which rows of its answer play and how fast, and whether
4
+ its inputs reach it as torch tensors. Each part is optional; a policy that declares none is sent and handed what it is
5
+ today.
6
+
7
+ In one sentence: `Observations` names the keys a policy reads, each a `Stream` - read at the act's step and the offsets
8
+ in `at` before it (a window the worker keeps), or handed every `every` steps since the last act (fed: the policy keeps
9
+ its own history) - counted in steps or seconds (`unit`) → the robot and the eval send, and a benchmark renders, only
10
+ those steps → infer is handed {"observations": [...], "steps": [...]}, oldest first, the act's own last; `Actions` says
11
+ which field of the answer holds the rows, how many a second they play (`hz`) and how many play before the next act
12
+ (`open_loop_horizon`), so a worker can time a copy's robots by them.
13
+
14
+ WHY by key and not by kind of input: every policy, benchmark and serving framework read for the user's 69 names its
15
+ inputs and gives each its own schedule - GR00T DROID reads its cameras at t and t-15 and its state at t alone - and what
16
+ breaks a policy is a frame fitted or encoded other than it was trained on, which a kind of input does not say and a
17
+ `Camera` does.
18
+
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import math
23
+ from typing import Any, Literal
24
+
25
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
26
+
27
+ from sequence_base.presets.robot_arm import Camera
28
+
29
+ __all__ = ["Actions", "MAX_STREAMS", "MAX_WINDOW", "Observations", "Stream", "Tensors", "check_own_io"]
30
+
31
+
32
+
33
+ MAX_STREAMS = 64
34
+ MAX_WINDOW = 256
35
+
36
+
37
+ def _counts(v: Any, what: str) -> None:
38
+
39
+ if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v):
40
+ raise ValueError(f"{what} is a number of steps or seconds, got {v!r}")
41
+
42
+
43
+ class Stream(BaseModel):
44
+ """One key of the observation a policy reads. `at`: the offsets before the act's step it reads at each act, 0 the
45
+ act's own - a window the worker keeps, so the policy keeps none. `every`: handed every that many steps since its last
46
+ act, the act's own always among them - fed, the policy keeping its own history. Neither: the act's step alone.
47
+ `camera`: the key is a camera's frame, read at its width x height - fitted by `resize` and `filter` before it is sent
48
+ when both are declared, sent as `codec` says (unset: as the robot sent it), decoded in the replica to HWC uint8, or
49
+ CHW float in 0..1 for layout "chw_float", before the policy's code."""
50
+
51
+ model_config = ConfigDict(extra="forbid")
52
+
53
+ at: list[int | float] | None = Field(default=None, min_length=1, max_length=MAX_WINDOW,
54
+ description="offsets before the act's step, each 0 or below")
55
+ every: int | float | None = Field(default=None, gt=0, description="handed every this many steps since the last act")
56
+ camera: Camera | None = None
57
+
58
+ @field_validator("at", mode="before")
59
+ @classmethod
60
+ def _offsets_as_given(cls, v: Any) -> Any:
61
+ for x in v if isinstance(v, list) else ():
62
+ _counts(x, "an offset in at=")
63
+ return v
64
+
65
+ @field_validator("at")
66
+ @classmethod
67
+ def _window(cls, v: list[int | float] | None) -> list[int | float] | None:
68
+ if v is None:
69
+ return v
70
+ if any(x > 0 for x in v):
71
+ raise ValueError("at= holds offsets before the act's step: each 0 (the act's own) or below")
72
+ if len(set(v)) != len(v):
73
+ raise ValueError("at= names each offset once")
74
+ return sorted(v)
75
+
76
+ @field_validator("every", mode="before")
77
+ @classmethod
78
+ def _fed(cls, v: Any) -> Any:
79
+ if v is not None:
80
+ _counts(v, "every=")
81
+ return v
82
+
83
+ @model_validator(mode="after")
84
+ def _one_schedule(self) -> "Stream":
85
+ if self.at is not None and self.every is not None:
86
+ raise ValueError("a key is read at= offsets of each act (the worker keeps them) or every= so many steps "
87
+ "(the policy keeps them), not both")
88
+ return self
89
+
90
+
91
+ class Observations(BaseModel):
92
+ """What a policy of its own inputs reads of each observation, and when. `streams`: the keys it reads, each a Stream;
93
+ only these are sent. None: the whole observation, as the robot built it. `every`: the whole observation handed every
94
+ that many steps since the last act (fed, no keys named). `unit`: what `at` and `every` count - "steps", an episode's
95
+ control steps from 0 (one action played on a robot, one env step in an eval), or "seconds", taken to steps by the
96
+ robot's or the benchmark's control rate. `pad`: a window's step before the episode's first reads as its first
97
+ ("repeat_first") or is left out ("drop"). `when_behind`: what the robot does when its upload falls behind - "wait",
98
+ losing none, or "drop_oldest". Declared, infer is handed {"observations": [...], "steps": [...]}: each observation
99
+ the keys read at its step, oldest first, the act's own last."""
100
+
101
+ model_config = ConfigDict(extra="forbid")
102
+
103
+ streams: dict[str, Stream] | None = Field(default=None, max_length=MAX_STREAMS)
104
+ every: int | float | None = Field(default=None, gt=0)
105
+ unit: Literal["steps", "seconds"] = "steps"
106
+ pad: Literal["repeat_first", "drop"] = "repeat_first"
107
+ when_behind: Literal["wait", "drop_oldest"] = "wait"
108
+
109
+ @field_validator("streams")
110
+ @classmethod
111
+ def _keys(cls, v: dict[str, Stream] | None) -> dict[str, Stream] | None:
112
+ for key in v or {}:
113
+ if not key or len(key) > 128 or key != key.strip():
114
+ raise ValueError(f"stream {key!r} is not a key of an observation: 1 to 128 characters, no blanks around")
115
+ return v
116
+
117
+ @field_validator("every", mode="before")
118
+ @classmethod
119
+ def _fed(cls, v: Any) -> Any:
120
+ if v is not None:
121
+ _counts(v, "every=")
122
+ return v
123
+
124
+ @model_validator(mode="after")
125
+ def _counted(self) -> "Observations":
126
+ if self.streams is not None and self.every is not None:
127
+ raise ValueError("Observations(every=...) hands the whole observation; with keys named, give each key its "
128
+ "own Stream(every=...)")
129
+ if self.unit == "steps":
130
+ named = [("every=", self.every)]
131
+ for key, stream in (self.streams or {}).items():
132
+ named += [(f"{key!r} every=", stream.every)] + [(f"{key!r} at=", x) for x in stream.at or []]
133
+ for what, x in named:
134
+ if x is not None and not isinstance(x, int):
135
+ raise ValueError(f"unit 'steps' counts whole steps and {what}{x!r} is not one; for a time, write "
136
+ "unit='seconds'")
137
+ return self
138
+
139
+
140
+ class Actions(BaseModel):
141
+ """Which rows of a policy's answer play. `key`: the answer's field that holds them (None: the answer itself) - a
142
+ list of rows or a 2-D array, played in order, `hz` rows a second, `open_loop_horizon` of them before the next act
143
+ (None: all). A shorter answer plays whole and the next act comes sooner; a longer one is cut there. With `hz` a worker
144
+ knows how long one answer plays (open_loop_horizon / hz) and a control period (1 / hz) and times a copy's robots by
145
+ them, as a robot-arm chunk's covers_seconds and control_frequency_hz do. Undeclared, an answer plays as the default
146
+ says: a dict or a 1-D list one action, a 2-D one row by row."""
147
+
148
+ model_config = ConfigDict(extra="forbid")
149
+
150
+ key: str | None = Field(default=None, min_length=1, max_length=128)
151
+ hz: float | None = Field(default=None, gt=0, le=1000)
152
+ open_loop_horizon: int | None = Field(default=None, ge=1, le=100_000)
153
+
154
+
155
+ class Tensors(BaseModel):
156
+ """A policy's inputs reach its code as torch tensors on its device - a batch dimension first when `batch` - and the
157
+ tensors it answers with go back as arrays; float64 arrays arrive as float32. Converted in the replica, last before
158
+ the policy's code; how a camera's frame is laid out is its Stream's camera.layout."""
159
+
160
+ model_config = ConfigDict(extra="forbid")
161
+
162
+ batch: bool = True
163
+
164
+
165
+ def check_own_io(spec: Any) -> None:
166
+ """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, and the 0.5.24 ones (observations / actions / tensors)
168
+ not beside own_observations / own_actions, which they take over from."""
169
+ new = [name for name in ("observations", "actions", "tensors") if getattr(spec, name) is not None]
170
+ if not new:
171
+ return
172
+ if spec.action or spec.observation or spec.embodiments:
173
+ raise ValueError(f"{', '.join(new)} {'is' if len(new) == 1 else 'are'} for a policy of its own inputs and "
174
+ "outputs; this one declares the robot-arm contract, where observation history and the action "
175
+ "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")
@@ -24,8 +24,10 @@ from sequence_base.contract.io import (
24
24
  ObservationContract,
25
25
  )
26
26
  from sequence_base.contract.flags import SeqFlags
27
+ from sequence_base.contract.own_io import Actions, Observations, Tensors, check_own_io
27
28
 
28
29
  __all__ = [
30
+ "Episodes",
29
31
  "ImageStepUvPip",
30
32
  "ImageStepApt",
31
33
  "ImageStepEnv",
@@ -337,6 +339,13 @@ class Concurrency(BaseModel):
337
339
  KIND_NAME = r"^[a-z0-9][a-z0-9._-]{0,31}$"
338
340
 
339
341
 
342
+
343
+
344
+
345
+
346
+ Episodes = Literal["shared", "own"]
347
+
348
+
340
349
  class PolicySpec(BaseModel):
341
350
  """What `@seq.policy(...)` accrues — a policy's complete infra declaration, losslessly. `action` /
342
351
  `observation` / `embodiments` are optional (contract 0.4.14): a policy that declares none defines its I/O in its own
@@ -360,7 +369,13 @@ class PolicySpec(BaseModel):
360
369
  on_full: Literal["queue", "reject"] = "queue"
361
370
  queue_timeout_s: int = Field(default=120, ge=5, le=3600)
362
371
  concurrency: Concurrency | None = None
363
- streaming: bool = False
372
+
373
+
374
+ episodes: Episodes | None = None
375
+
376
+
377
+
378
+ streaming: bool = Field(default=False, exclude=True)
364
379
 
365
380
  action: ActionContract | None = None
366
381
 
@@ -384,6 +399,13 @@ class PolicySpec(BaseModel):
384
399
  own_observations: Literal["latest", "since_last_act"] | None = None
385
400
  own_actions: Literal["one", "steps"] | None = None
386
401
 
402
+
403
+
404
+
405
+ observations: Observations | None = None
406
+ actions: Actions | None = None
407
+ tensors: Tensors | None = None
408
+
387
409
  @field_validator("weights")
388
410
  @classmethod
389
411
  def _weights_scheme(cls, v: str | None) -> str | None:
@@ -398,11 +420,23 @@ class PolicySpec(BaseModel):
398
420
  check_weights_uri(uri, f"weights[{name!r}]")
399
421
  return self
400
422
 
423
+ @model_validator(mode="after")
424
+ def _episodes_of_streaming(self) -> "PolicySpec":
425
+
426
+ if self.streaming and self.episodes not in (None, "own"):
427
+ raise ValueError(f"streaming is what a record from before contract 0.5.24 says; beside "
428
+ f"episodes={self.episodes!r}, leave it out")
429
+ if self.streaming:
430
+ self.episodes = "own"
431
+ self.streaming = self.episodes == "own"
432
+ return self
433
+
401
434
  @model_validator(mode="after")
402
435
  def _own_io(self) -> "PolicySpec":
403
436
  if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
404
437
  raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
405
438
  "declares the robot-arm contract, where observation history and the action chunk say it")
439
+ check_own_io(self)
406
440
  return self
407
441
 
408
442
 
@@ -477,7 +511,11 @@ class PolicyRegistration(BaseModel):
477
511
 
478
512
  A class that names its own methods (`constructor` set) is built by its constructor or factory, so it binds no LOAD,
479
513
  and its bindings name its own methods: INFER (what answers an act), and as declared OBSERVE (each observation an
480
- act carries, before INFER is called with none), RESET (an episode's first act) and ON_EPISODE (its end)."""
514
+ act carries, before INFER is called with none), RESET (an episode's first act) and ON_EPISODE (its end).
515
+
516
+ Any policy may bind SAMPLE (contract 0.5.24, `@seq.policy(sample="<method>")`): the method returning one episode's
517
+ act inputs, in order, each as an act carries it - what its build check acts with and its worker times its infer on
518
+ when no contract says what it reads."""
481
519
 
482
520
  model_config = ConfigDict(extra="forbid")
483
521
 
@@ -41,6 +41,11 @@ class ActRequest(BaseModel):
41
41
  description="The robot's session, the steps it holds, its rate and network p95")
42
42
 
43
43
 
44
+
45
+ seq: int | None = Field(default=None, ge=0,
46
+ description="The newest observation's number in its episode's stream (POST /observe)")
47
+
48
+
44
49
  class ActResponse(BaseModel):
45
50
  """200 from /act, version 2. `outputs` is infer()'s return value, unchanged; the rest is platform metadata."""
46
51
 
@@ -0,0 +1,58 @@
1
+ """
2
+ What a policy that keeps its episodes' state on the platform is handed (PolicySpec.episodes "shared"): its infer
3
+ declares `episode` and gets that episode's Episode - its id, how many of its acts were answered, and `state`, a dict of
4
+ the policy's own.
5
+
6
+ In one sentence: an episode's first act → the copy it is placed on makes its Episode (its id, steps 0, state empty) →
7
+ every act of it hands infer that same object, one more step after each answer → at the episode's end the copy drops it.
8
+
9
+ WHY a class of the platform's rather than a dict: the id and the step count come with it, and what the policy keeps has
10
+ a place of its own - `state` - that no field the platform adds later can collide with.
11
+
12
+ WHY nothing in it is serialized: it lives in the process of the copy serving its episode and is handed by reference,
13
+ so anything goes in `state` - tensors, a model's cache - and an act costs no more for it.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ __all__ = ["Episode", "answered"]
20
+
21
+
22
+ class Episode:
23
+ """One episode, as its policy's infer sees it: `id` - the episode's id; `steps` - how many acts of it were answered
24
+ before this one (0 on its first; an act, however many control steps its answer is played as); `state` - a dict for
25
+ the policy's own, empty on the first act and the same object on every act after. `id` and `steps` are the
26
+ platform's: they are read, never set."""
27
+
28
+ __slots__ = ("_id", "_steps", "state")
29
+
30
+ def __init__(self, id: str) -> None:
31
+ object.__setattr__(self, "_id", id)
32
+ object.__setattr__(self, "_steps", 0)
33
+ object.__setattr__(self, "state", {})
34
+
35
+ @property
36
+ def id(self) -> str:
37
+ return self._id
38
+
39
+ @property
40
+ def steps(self) -> int:
41
+ return self._steps
42
+
43
+ def __setattr__(self, name: str, value: Any) -> None:
44
+ if name == "state":
45
+ object.__setattr__(self, name, value)
46
+ elif name in ("id", "steps"):
47
+ raise AttributeError(f"Episode.{name} is the platform's: read it, never set it")
48
+ else:
49
+ raise AttributeError(f"an Episode keeps what a policy puts in it in its state, a dict: "
50
+ f"episode.state[{name!r}] = ...")
51
+
52
+ def __repr__(self) -> str:
53
+ return f"Episode(id={self._id!r}, steps={self._steps})"
54
+
55
+
56
+ def answered(episode: Episode) -> None:
57
+ """One more act of the episode answered - the platform's, once its infer returned."""
58
+ object.__setattr__(episode, "_steps", episode._steps + 1)