sequence-base 0.3.16__tar.gz → 0.3.18__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 (115) hide show
  1. {sequence_base-0.3.16 → sequence_base-0.3.18}/PKG-INFO +2 -2
  2. {sequence_base-0.3.16 → sequence_base-0.3.18}/README.md +2 -2
  3. {sequence_base-0.3.16 → sequence_base-0.3.18}/pyproject.toml +4 -2
  4. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/__init__.py +16 -5
  5. sequence_base-0.3.18/sequence_base/codec/__init__.py +19 -0
  6. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/codec/image.py +11 -62
  7. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/constants/env.py +0 -7
  8. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/constants/headers.py +0 -1
  9. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/constants/version.py +3 -1
  10. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/__init__.py +8 -1
  11. sequence_base-0.3.18/sequence_base/contract/act.py +17 -0
  12. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/connect.py +2 -0
  13. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/deployments.py +43 -19
  14. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/eval.py +65 -2
  15. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/flags.py +14 -31
  16. sequence_base-0.3.18/sequence_base/contract/io.py +17 -0
  17. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/policy.py +66 -10
  18. sequence_base-0.3.18/sequence_base/contract/wire.py +53 -0
  19. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/errors/transport.py +0 -1
  20. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/obs/log.py +1 -1
  21. sequence_base-0.3.18/sequence_base/presets/__init__.py +4 -0
  22. sequence_base-0.3.18/sequence_base/presets/robot_arm.py +197 -0
  23. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schema_export.py +8 -0
  24. sequence_base-0.3.18/sequence_base/schemas/ActRequest.json +69 -0
  25. sequence_base-0.3.18/sequence_base/schemas/ActResponse.json +79 -0
  26. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ActionChunk.json +3 -2
  27. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ActionContract.json +3 -2
  28. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ActionStep.json +1 -1
  29. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/AdapterTransform.json +1 -1
  30. sequence_base-0.3.18/sequence_base/schemas/AddLocal.json +27 -0
  31. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Autoscaler.json +32 -2
  32. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
  33. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/BenchmarkRegisterRequest.json +47 -4
  34. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/BenchmarkSpec.json +45 -3
  35. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
  36. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/BootstrapResponse.json +1 -1
  37. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Camera.json +1 -1
  38. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ClampInfo.json +1 -1
  39. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/CodeBundle.json +2 -2
  40. sequence_base-0.3.18/sequence_base/schemas/Concurrency.json +65 -0
  41. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Conditions.json +1 -1
  42. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ConnectRequest.json +1 -1
  43. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ConnectResponse.json +15 -2
  44. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/DeploymentAccepted.json +1 -1
  45. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/DeploymentPolicy.json +27 -6
  46. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/DeploymentRequest.json +209 -26
  47. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/DeploymentStatusResponse.json +18 -1
  48. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Embodiment.json +3 -2
  49. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ErrorBody.json +1 -1
  50. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ErrorCodes.json +1 -1
  51. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Estimate.json +1 -1
  52. sequence_base-0.3.18/sequence_base/schemas/EvalDryRunResponse.json +185 -0
  53. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/EvalReport.json +2 -1
  54. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/EvalRequest.json +43 -13
  55. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/EvalResponse.json +56 -1
  56. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageFrame.json +1 -1
  57. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageSpec.json +44 -2
  58. sequence_base-0.3.18/sequence_base/schemas/ImageStepAddLocal.json +42 -0
  59. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageStepApt.json +1 -1
  60. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageStepEnv.json +1 -1
  61. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageStepRun.json +1 -1
  62. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ImageStepUvPip.json +1 -1
  63. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/InferParam.json +1 -1
  64. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Lease.json +1 -1
  65. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/LossyTransform.json +1 -1
  66. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/MetricAgg.json +1 -1
  67. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Observation.json +1 -1
  68. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/ObservationContract.json +1 -1
  69. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/PolicyRegistration.json +154 -17
  70. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/PolicySpec.json +153 -16
  71. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Proprioception.json +1 -1
  72. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/RenderSpec.json +1 -1
  73. sequence_base-0.3.18/sequence_base/schemas/Resources.json +88 -0
  74. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/RolloutResult.json +1 -1
  75. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/SecretCreateRequest.json +1 -1
  76. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/SecretInfo.json +1 -1
  77. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/SecretRef.json +1 -1
  78. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/SeqFlags.json +1 -1
  79. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Step.json +1 -1
  80. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/SuiteAgg.json +1 -1
  81. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/TaskAgg.json +1 -1
  82. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Timeouts.json +1 -1
  83. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/UploadTarget.json +1 -1
  84. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/UsageReport.json +1 -1
  85. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/Variant.json +1 -1
  86. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeAttach.json +1 -1
  87. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeFile.json +1 -1
  88. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeInfo.json +1 -1
  89. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeMount.json +1 -1
  90. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeRef.json +1 -1
  91. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
  92. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
  93. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/schemas/WarmingError.json +1 -1
  94. sequence_base-0.3.18/sequence_base/telemetry/__init__.py +8 -0
  95. sequence_base-0.3.18/sequence_base/telemetry/events.py +50 -0
  96. sequence_base-0.3.16/sequence_base/codec/__init__.py +0 -25
  97. sequence_base-0.3.16/sequence_base/codec/resize.py +0 -147
  98. sequence_base-0.3.16/sequence_base/contract/act.py +0 -100
  99. sequence_base-0.3.16/sequence_base/contract/io.py +0 -110
  100. sequence_base-0.3.16/sequence_base/schemas/Concurrency.json +0 -21
  101. sequence_base-0.3.16/sequence_base/schemas/EvalDryRunResponse.json +0 -41
  102. sequence_base-0.3.16/sequence_base/schemas/Resources.json +0 -42
  103. sequence_base-0.3.16/sequence_base/telemetry/__init__.py +0 -13
  104. sequence_base-0.3.16/sequence_base/telemetry/events.py +0 -101
  105. {sequence_base-0.3.16 → sequence_base-0.3.18}/.gitignore +0 -0
  106. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/auth.py +0 -0
  107. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/constants/__init__.py +0 -0
  108. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/accounts.py +0 -0
  109. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/benchmarks.py +0 -0
  110. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/errors.py +0 -0
  111. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/templates.py +0 -0
  112. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/contract/volumes.py +0 -0
  113. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/errors/__init__.py +0 -0
  114. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/obs/__init__.py +0 -0
  115. {sequence_base-0.3.16 → sequence_base-0.3.18}/sequence_base/obs/rid.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sequence-base
3
- Version: 0.3.16
3
+ Version: 0.3.18
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
- Requires-Python: >=3.11
5
+ Requires-Python: >=3.10
6
6
  Requires-Dist: pydantic>=2.0
7
7
  Provides-Extra: codec
8
8
  Requires-Dist: numpy>=1.26; extra == 'codec'
@@ -34,5 +34,5 @@ values (per-model client_resize, pricing, licensing) stay out — in container /
34
34
  `pip install -e .` (core = pydantic). The image codec pulls PIL/numpy, so it is an optional extra —
35
35
  `pip install -e ".[codec]"` — which the gateway (which never encodes/decodes) skips.
36
36
 
37
- Extracted from `general-sequences/backend/sequences/` (`api/schema.py`, `obs.py`,
38
- `api/handoff/runpod.py`). Renamed from `sequence-contract` once it grew past just the contract.
37
+ Extracted from the original backend's schema and observability modules. Renamed from `sequence-contract` once it
38
+ grew past just the contract.
@@ -1,8 +1,10 @@
1
1
  [project]
2
2
  name = "sequence-base"
3
- version = "0.3.16"
3
+ version = "0.3.18"
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
- requires-python = ">=3.11"
5
+ # 3.10, not 3.11: `seq` (which imports this) must run on ROS 2 Humble's Python 3.10, and nothing here needs 3.11.
6
+ # {2026-10-03 the full suite on CPython 3.10.20: "147 PASSED"; vermin --eval-annotations: "MINIMUM REQUIRED VERSIONS: 3.9"}
7
+ requires-python = ">=3.10"
6
8
  dependencies = ["pydantic>=2.0"]
7
9
 
8
10
  # codec (image encode/decode) is P1 and pulls PIL/numpy — an optional extra so the gateway, which
@@ -14,6 +14,10 @@ catalogue values stay in container / gateway / spec.py.
14
14
  # contract — the full shared wire contract (obs/action + registry + deployments + connect + eval +
15
15
  # accounts + error bodies). One definition per shape; see sequence_base/contract for the grouping.
16
16
  from sequence_base.contract import ( # noqa: F401 (re-export surface)
17
+ ActRequest,
18
+ ActResponse,
19
+ AddLocal,
20
+ ImageStepAddLocal,
17
21
  Camera,
18
22
  Embodiment,
19
23
  ObservationContract,
@@ -112,8 +116,6 @@ from sequence_base.errors.transport import (
112
116
  # auth — Bearer secret helpers (gateway direct-connect / internal; container guard; worker emit)
113
117
  from sequence_base import auth
114
118
 
115
- # telemetry — cold-start event schema (worker emits, gateway ingests)
116
- from sequence_base.telemetry.events import WorkerLogEvent
117
119
 
118
120
  # NOTE: codec (image encode/decode) is intentionally NOT re-exported here — it needs PIL + numpy
119
121
  # (the [codec] extra), and the gateway installs the light core. Import it explicitly where needed:
@@ -122,8 +124,10 @@ from sequence_base.telemetry.events import WorkerLogEvent
122
124
  __all__ = [
123
125
  # contract 4 — /act data plane
124
126
  "ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
127
+ "ActRequest", "ActResponse",
125
128
  # contract 1 — flags + policy registry
126
- "SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "ImageStep", "ImageSpec",
129
+ "SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
130
+ "ImageStep", "ImageSpec",
127
131
  "VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
128
132
  "PolicySpec", "PolicyRegistration",
129
133
  # contract 2 — deployments
@@ -150,7 +154,14 @@ __all__ = [
150
154
  # errors (exception taxonomy)
151
155
  "TransportUnavailable", "TransportWarming", "TransportRejected", "TransportOverloaded",
152
156
  # auth + telemetry
153
- "auth", "WorkerLogEvent",
157
+ "auth",
154
158
  ]
155
159
 
156
- __version__ = "0.3.6"
160
+ try:
161
+ # The installed distribution's own number, so this can never drift from pyproject.toml again (it said 0.3.6
162
+ # while the package was 0.3.12). A source tree that was never installed reports "dev".
163
+ from importlib.metadata import PackageNotFoundError, version as _version
164
+
165
+ __version__ = _version("sequence-base")
166
+ except PackageNotFoundError: # pragma: no cover - running from an uninstalled checkout
167
+ __version__ = "dev"
@@ -0,0 +1,19 @@
1
+ """
2
+ Image codec — the frame <-> wire transform, shared so the SDK (encode) and the worker (decode) cannot drift
3
+ on the wire format.
4
+
5
+ Needs Pillow + numpy: `pip install 'sequence-base[codec]'`. The gateway, which neither encodes nor
6
+ decodes, installs the light core and never imports this.
7
+
8
+ from sequence_base.codec import encode, decode
9
+ """
10
+ from sequence_base.codec.image import (
11
+ DEFAULT_JPEG_QUALITY,
12
+ MAX_UPLOAD_EDGE,
13
+ decode,
14
+ decode_depth,
15
+ encode,
16
+ encode_depth,
17
+ )
18
+
19
+ __all__ = ["encode", "decode", "encode_depth", "decode_depth", "DEFAULT_JPEG_QUALITY", "MAX_UPLOAD_EDGE"]
@@ -15,7 +15,6 @@ import base64
15
15
  import io
16
16
  from typing import Any
17
17
 
18
- from .resize import MAX_UPLOAD_EDGE, ResizeSpec, filters, refuse_wrong_aspect, resize_with_pad
19
18
 
20
19
  # Quality 95, and the number is measured rather than chosen for looking safe. On a real DROID frame
21
20
  # with the sampling noise pinned, the encoding moves the resulting action by: q95 0.51% of action
@@ -25,20 +24,22 @@ from .resize import MAX_UPLOAD_EDGE, ResizeSpec, filters, refuse_wrong_aspect, r
25
24
  # {sequences-models models/pi05_droid/handler.py "Q95 MAX|DELTA| 0.0048 0.51% ... Q85 1.86%"}
26
25
  DEFAULT_JPEG_QUALITY = 95
27
26
 
27
+ # The longest edge `encode` uploads. Every policy resizes a frame to its own input size anyway, so pixels above
28
+ # that only cost transport — and past a point they do not cost, they fail: a body over ~2 MB was dropped by a
29
+ # worker queue with no error and no job id, surfacing as a hang. 640 is above every input size in use (pi0.5's
30
+ # 224), so it discards nothing a policy reads. [MEASURED 2026-09-12: capping the long edge at 640 took a
31
+ # three-view observation from 1.98 MB to 0.12 MB]
32
+ MAX_UPLOAD_EDGE = 640
28
33
 
29
- def encode(frame: Any, spec: ResizeSpec | None = None, *, quality: int = DEFAULT_JPEG_QUALITY) -> str:
34
+
35
+ def encode(frame: Any, *, quality: int = DEFAULT_JPEG_QUALITY) -> str:
30
36
  """
31
37
  One camera frame to the base64 image that `ImageFrame.data` carries.
32
38
 
33
39
  `frame` may be a numpy array, a PIL image, raw `bytes` already encoded, or a `str` already base64
34
40
  — the last two pass through untouched, so a caller with their own pipeline is not forced through
35
- this one.
36
-
37
- `spec` (a `ResizeSpec`) carries the model's geometry. When it declares a training aspect ratio, a
38
- frame outside it is REFUSED — see `refuse_wrong_aspect`, not quietly reshaped. With no spec (or no
39
- `client_resize`) the frame falls to `MAX_UPLOAD_EDGE`. Codec is JPEG at `quality`, unconditionally
40
- — every served handler asks for JPEG in as many words, so there is no size probe and no second
41
- encode. {handlers pi05_droid/cosmos3/groot all "SEND BASE64 JPEG"}
41
+ this one. A frame whose long edge is over `MAX_UPLOAD_EDGE` is scaled down to it first. Codec is
42
+ JPEG at `quality`, unconditionally.
42
43
  """
43
44
  if isinstance(frame, str):
44
45
  return frame
@@ -56,59 +57,7 @@ def encode(frame: Any, spec: ResizeSpec | None = None, *, quality: int = DEFAULT
56
57
  img = frame if hasattr(frame, "save") else Image.fromarray(frame)
57
58
  if img.mode != "RGB":
58
59
  img = img.convert("RGB")
59
-
60
- # Downscale before encoding. Every model resizes the frame to its own input size anyway, so pixels
61
- # above that only cost transport — and past a point they do not cost, they fail (a body over ~2 MB
62
- # is dropped by the worker queue with no error and no job id, surfacing as a hang). The target and
63
- # the filter both come from the spec; neither is derived here. `client_resize=None` means the
64
- # model declined pre-resizing and is not a gap: it falls to the cap below, as an unknown model
65
- # does. HOW the frame reaches that size comes from `input_resize` ("pad" = the server's own
66
- # resize_with_pad, "stretch" = unconditional interpolate).
67
- want = None
68
- cr = spec.client_resize if spec else None
69
- if cr:
70
- # `shortest_edge` scales the short side to n and lets the ratio follow — the only way to
71
- # express a server step defined as "SmallestMaxSize(256)" (1280x720 and 1920x1080 both to
72
- # 455x256), which no fixed [w, h] covers. round(), matching albumentations. `size` is a fixed
73
- # target.
74
- if "shortest_edge" in cr:
75
- # Shrink only. SmallestMaxSize also UPSCALES a frame whose short edge is under the target,
76
- # and reproducing that here is strictly worse: the payload grows and the upscale happens
77
- # in PIL when the server was going to do it in cv2 anyway. Leaving a small frame alone
78
- # hands the whole operation to the server, where it is exact.
79
- n = int(cr["shortest_edge"])
80
- if min(img.size) > n:
81
- scale = n / min(img.size)
82
- want = (max(1, round(img.width * scale)), max(1, round(img.height * scale)))
83
- else:
84
- want = img.size
85
- else:
86
- want = (int(cr["size"][0]), int(cr["size"][1]))
87
- if img.size != want:
88
- # The filter is named by the server, not chosen here: this resize stands in for one the
89
- # model's own transform would otherwise run, so it has to be the SAME operation. An
90
- # unrecognised name raises rather than falling back — a silent default is how the LANCZOS
91
- # bug shipped. {MEASURED: BILINEAR to pi05's content block is 0.000/255, LANCZOS 0.779/15}
92
- try:
93
- resample = filters()[cr["filter"]]
94
- except KeyError:
95
- raise ValueError(
96
- f"{spec.label} asks for resize filter {cr['filter']!r}, which this version of "
97
- f"sequence-base does not implement (has: {sorted(filters())}). Upgrade, or pass "
98
- f"an already-encoded frame to bypass this step."
99
- ) from None
100
- # Refused before either branch, not inside "pad": a stretch model distorts a wrong ratio
101
- # instead of letterboxing it, which is worse rather than exempt.
102
- refuse_wrong_aspect(img, spec.aspect_ratio, max(want), spec.label)
103
- if "shortest_edge" in cr:
104
- # Already proportional, so nothing to letterbox — and letterboxing would actively
105
- # damage it (a spurious black row the server carries into its crop as if it were scene).
106
- img = img.resize(want, resample)
107
- elif (spec.input_resize or "pad") == "pad":
108
- img = resize_with_pad(img, want[0], want[1], resample)
109
- else:
110
- img = img.resize(want, resample)
111
- elif max(img.size) > MAX_UPLOAD_EDGE:
60
+ if max(img.size) > MAX_UPLOAD_EDGE:
112
61
  scale = MAX_UPLOAD_EDGE / max(img.size)
113
62
  # round() not int(): int() truncates a 640.9 on one axis and 359.9 on the other, drifting the
114
63
  # aspect ratio by up to half a percent for no reason.
@@ -5,10 +5,3 @@ INTERNAL_SECRET = "SEQ_INTERNAL_SECRET" # auth for container → gateway teleme
5
5
  GATEWAY_URL = "SEQ_GATEWAY_URL" # where the container sends telemetry
6
6
  VOLUME_ROOT = "SEQ_VOLUME_ROOT" # where fetched weights land
7
7
  MANIFEST = "SEQ_MANIFEST" # the policy manifest a container serves
8
-
9
- # Weight store (Cloudflare R2).
10
- DIST_ENDPOINT = "SEQ_DIST_ENDPOINT"
11
- DIST_BUCKET = "SEQ_DIST_BUCKET"
12
- DIST_KEY = "SEQ_DIST_KEY"
13
- DIST_SECRET = "SEQ_DIST_SECRET"
14
- DIST_REGION = "SEQ_DIST_REGION"
@@ -1,5 +1,4 @@
1
1
  """HTTP header names — one source, so sdk / gateway / container never spell them differently."""
2
2
 
3
3
  REQUEST_ID = "X-Seq-Request-Id" # correlation id; == sequence_base.obs.rid.HEADER
4
- MODEL = "X-Seq-Model" # gateway → worker model routing
5
4
  AUTH_SCHEME = "Bearer" # scheme for SEQ_DIRECT_SECRET on the direct-connect path
@@ -4,7 +4,9 @@
4
4
  # across deployments / repos can tell which version of a shape it is validating against. MUST change
5
5
  # whenever any shared wire shape changes (a field added / removed / retyped) — that is what makes
6
6
  # contract evolution mechanically detectable instead of eyeballed. Bump on any shape change.
7
- CONTRACT_VERSION = "0.4.13" # 0.4.12 -> 0.4.13: template://<name>[@v<n>] — official template weights (parse_template); gs:// / s3:// / https:// still accepted until their removal
7
+ CONTRACT_VERSION = "0.4.15" # 0.4.14 -> 0.4.15: limits count GPUs and CPU/memory are billed like Modal — Resources.cpu / memory_gb optional floats + cpu_limit / memory_limit_gb; Autoscaler.max_gpus / on_full / queue_timeout_s replace max_containers; Concurrency fields optional + replicas_per_gpu / gpus_per_replica; DeploymentStatusResponse.reason; EvalRequest.policy_instances / policy_gpu / benchmark_instances / benchmark_gpu / envs replace gpus / envs_per_gpu; EvalStatus.QUEUED; EvalCards / EvalQueue
8
+ # previous: 0.4.14 # 0.4.13 -> 0.4.14: the /act wire is opaque (ActRequest.inputs / ActResponse.outputs); action / observation contracts optional + DeploymentPolicy/PolicySpec/ConnectResponse .io; the robot-arm types live in sequence_base.presets.robot_arm (contract.act / contract.io re-export them); ImageStepAddLocal (seq.Image.add_local_*); ActionSpace.JOINT_VELOCITY (pi0.5 DROID's joint velocities + gripper position)
9
+ # previous: 0.4.13 # 0.4.12 -> 0.4.13: template://<name>[@v<n>] — official template weights (parse_template); gs:// / s3:// / https:// still accepted until their removal
8
10
  # previous: 0.4.12 # 0.4.11 -> 0.4.12: one error definition — ErrorBody is every non-2xx body (detail, code, retryable, retry_after_s, request_id, hint, details), ErrorCode covers every case, ERRORS / ErrorCodes.json give each code its status, retryability, SDK class and CLI exit code
9
11
  # previous: 0.4.11 # 0.4.10 -> 0.4.11: weights in named parts (weights_name, extra_weights)
10
12
  # previous: 0.4.8 # 0.4.7 -> 0.4.8: ImageSpec.python, ImageStepRun.secrets, Resources.region, DeploymentRequest/BenchmarkRegisterRequest.image_context
@@ -12,6 +12,9 @@ from sequence_base.contract.act import (
12
12
  Proprioception,
13
13
  )
14
14
 
15
+ # contract 4, version 2 — the opaque /act envelope (inputs in, outputs out)
16
+ from sequence_base.contract.wire import ActRequest, ActResponse
17
+
15
18
  # contract 1 — lifecycle flags + policy registry
16
19
  from sequence_base.contract.flags import SeqFlags
17
20
  from sequence_base.contract.policy import (
@@ -26,6 +29,8 @@ from sequence_base.contract.policy import (
26
29
  ImageStepEnv,
27
30
  ImageStepRun,
28
31
  ImageStepUvPip,
32
+ AddLocal,
33
+ ImageStepAddLocal,
29
34
  PolicyRegistration,
30
35
  PolicySpec,
31
36
  VolumeMount,
@@ -110,9 +115,11 @@ from sequence_base.contract.errors import ERRORS, ErrorBody, ErrorCode, ErrorSpe
110
115
  __all__ = [
111
116
  # contract 4
112
117
  "ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
118
+ "ActRequest", "ActResponse",
113
119
  # contract 1
114
120
  "SeqFlags",
115
- "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "ImageStep", "ImageSpec",
121
+ "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal", "ImageStep",
122
+ "ImageSpec",
116
123
  "VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
117
124
  "PolicySpec", "PolicyRegistration",
118
125
  # contract 2
@@ -0,0 +1,17 @@
1
+ """
2
+ The robot-arm /act data types, re-exported from their home in `sequence_base.presets.robot_arm`.
3
+
4
+ They are no longer the platform's wire: since contract 0.4.14 the worker passes a policy's `inputs` through untouched
5
+ (`sequence_base.contract.wire`). This module keeps `from sequence_base.contract.act import Observation` resolving to the
6
+ same classes for the gateway, harness and SDK code that still uses the preset.
7
+ """
8
+ from sequence_base.presets.robot_arm import ( # noqa: F401 — re-exports
9
+ ActionChunk,
10
+ ActionSpace,
11
+ ActionStep,
12
+ ImageFrame,
13
+ Observation,
14
+ Proprioception,
15
+ )
16
+
17
+ __all__ = ["ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk"]
@@ -57,3 +57,5 @@ class ConnectResponse(BaseModel):
57
57
  action: ActionContract | None = None
58
58
  observation: ObservationContract | None = None
59
59
  embodiment: str | None = None
60
+ # The deployment's own description of its inputs and outputs (DeploymentPolicy.io), when the author wrote one.
61
+ io: str | None = None
@@ -1,8 +1,8 @@
1
1
  """
2
2
  契约 2 — `POST /v1/deployments` (SDK `seq deploy` → gateway) and its responses.
3
3
 
4
- The request is the normalized wire form of a PolicySpec: one big body (à la Modal's single `Function`
5
- message — new config adds a field, never a new endpoint) whose sub-shapes reuse contract 1's types
4
+ The request is the normalized wire form of a PolicySpec: one big body (new config adds a field, never a
5
+ new endpoint) whose sub-shapes reuse contract 1's types
6
6
  (ImageSpec, VolumeMount, Concurrency, ActionContract). Field names align with the PolicySpec side —
7
7
  `gpu` is normalized to list[str] here, the memory field is `memory_gb` (goal 7).
8
8
 
@@ -47,27 +47,48 @@ __all__ = [
47
47
 
48
48
 
49
49
  class Resources(BaseModel):
50
- """= Modal Resources + GPUConfig. `gpu` is list[str] (the PolicySpec `str | list[str]` normalized)."""
50
+ """The hardware a worker runs on. `gpu` is list[str] (the PolicySpec `str | list[str]` normalized)."""
51
51
 
52
52
  model_config = ConfigDict(extra="forbid")
53
53
 
54
54
  gpu: list[str]
55
- cpu: int
56
- memory_gb: int
55
+ # Physical cores and GiB the worker requests, billed like Modal: per second, at the greater of the request and what
56
+ # it uses. None requests the minimum (0.125 cores, 0.125 GiB) and the worker may use more. A limit caps what it may
57
+ # use; None leaves it free to burst. {gs-plans/parallel/cpu-memory.md "4. 默认申请量照 Modal"}
58
+ cpu: float | None = Field(default=None, ge=0.125, le=64)
59
+ cpu_limit: float | None = Field(default=None, ge=0.125, le=128)
60
+ memory_gb: float | None = Field(default=None, ge=0.125, le=1024)
61
+ memory_limit_gb: float | None = Field(default=None, ge=0.125, le=1024)
57
62
  # Where the workers run (near the robots): one of the regions the platform offers. None: the platform picks.
58
63
  # {gs-plans/customization/policy.md "区域"}
59
64
  region: str | None = Field(default=None, max_length=32)
60
65
 
66
+ @model_validator(mode="after")
67
+ def _limits_cover_requests(self) -> "Resources":
68
+ for what, req, lim in (("cpu", self.cpu, self.cpu_limit), ("memory", self.memory_gb, self.memory_limit_gb)):
69
+ if req is not None and lim is not None and lim < req:
70
+ raise ValueError(f"the {what} limit must be at least the {what} request")
71
+ return self
72
+
61
73
 
62
74
  class Autoscaler(BaseModel):
63
- """= Modal AutoscalerSettings. Defaults mirror PolicySpec so an omitted block scale-to-zeroes."""
75
+ """How the deployment scales. Defaults mirror PolicySpec so an omitted block scales to zero."""
64
76
 
65
77
  model_config = ConfigDict(extra="forbid")
66
78
 
67
79
  min_containers: int = 0
80
+ # Accepted and ignored: limits count GPUs (`max_gpus`). sequence-ai <= 0.28 still sends it and does not pin
81
+ # sequence-base, so removing it would break their deploys; it goes when no supported SDK sends it.
68
82
  max_containers: int | None = None
83
+ # The most GPUs this deployment may hold at once. None: no cap of its own — only the account's and the platform's
84
+ # GPU limits bound it. Limits count cards, never machines. {gs-plans/parallel/serving.md "1. 限制按卡数,不按台数"}
85
+ max_gpus: int | None = Field(default=None, ge=1, le=256)
69
86
  buffer_containers: int | None = None
70
87
  scaledown_window: int = 600
88
+ # When every slot is taken and no card is left: queue the new robot before its control loop starts, or refuse it at
89
+ # once; how long a queued robot waits before it is told why. {gs-plans/parallel/serving-queue.md "connect 的处理顺序"}
90
+ on_full: Literal["queue", "reject"] = "queue"
91
+ queue_timeout_s: int = Field(default=120, ge=5, le=3600)
71
92
 
72
93
 
73
94
  class Timeouts(BaseModel):
@@ -104,8 +125,9 @@ class InferParam(BaseModel):
104
125
 
105
126
 
106
127
  class DeploymentPolicy(BaseModel):
107
- """The policy block of the deployments body. `handler_ref` and `action` are required; `action` is the
108
- fixed obs→ActionChunk contract, not a bare dict.
128
+ """The policy block of the deployments body. Only `handler_ref` is required. The robot-arm preset's contracts
129
+ (`action`, `observation`, `embodiments`) are optional since contract 0.4.14: a deployment that declares none is held
130
+ to nothing and its inputs / outputs pass through the worker untouched.
109
131
 
110
132
  `secrets` carries the policy's declared secret BUNDLES BY NAME ONLY (list[SecretRef]) — the gateway
111
133
  validates each name exists and belongs to the deployer at submit time (accounts ⑦ / S3 goal 14), and the
@@ -116,7 +138,7 @@ class DeploymentPolicy(BaseModel):
116
138
  model_config = ConfigDict(extra="forbid")
117
139
 
118
140
  handler_ref: str
119
- action: ActionContract
141
+ action: ActionContract | None = None
120
142
  streaming: bool = False
121
143
  frames_per_chunk: int | None = None
122
144
  secrets: list[SecretRef] = Field(default_factory=list)
@@ -124,11 +146,14 @@ class DeploymentPolicy(BaseModel):
124
146
  # contracts for a multi-embodiment policy — the worker picks one by the session's embodiment.
125
147
  observation: ObservationContract | None = None
126
148
  embodiments: dict[str, Embodiment] | None = None
127
- # Did the author declare `action=` (or embodiments)? `action` is required on the wire, so an SDK that had
128
- # nothing declared sends a placeholder; only a declared contract is enforced on the worker and handed to the
129
- # robot. None (an older SDK) is treated as undeclared, so an older policy is never refused for the
130
- # placeholder it never chose. {io-contract.md "旧 SDK 写的 POLICY 照样能 BUILD"}
149
+ # Did the author declare `action=` (or embodiments)? SDKs before contract 0.4.14 always sent an `action` (a
150
+ # placeholder when none was declared); only a declared contract is enforced on the worker and handed to the robot.
151
+ # None (an older SDK) is treated as undeclared, so an older policy is never refused for the placeholder it never
152
+ # chose. A current SDK sends `action` only when declared. {io-contract.md "旧 SDK 写的 POLICY 照样能 BUILD"}
131
153
  action_declared: bool | None = None
154
+ # The author's own description of what infer() takes and returns; stored, handed to the robot at connect, never
155
+ # interpreted by the platform.
156
+ io: str | None = Field(default=None, max_length=4000)
132
157
  # The inference parameters @seq.infer takes beyond the observation (name -> type, default). None: an older
133
158
  # SDK that did not look; {}: the policy declares none.
134
159
  params: dict[str, InferParam] | None = Field(default=None, max_length=32)
@@ -149,7 +174,7 @@ request limit (base64 adds a third)."""
149
174
 
150
175
  class CodeBundle(BaseModel):
151
176
  """The policy's own source, shipped with the deploy so the worker can import `policy.handler_ref`
152
- (what Modal's automount does for the entrypoint module). A gzip'd tar of the handler's top-level module
177
+ — the entrypoint module, mounted for it. A gzip'd tar of the handler's top-level module
153
178
  or package, base64 on the wire, with the sha256 of the decoded bytes so a truncated or altered upload
154
179
  is refused at the edge instead of failing as an ImportError on a GPU."""
155
180
 
@@ -201,11 +226,8 @@ class DeploymentRequest(BaseModel):
201
226
  # both key and value; the gateway only stores the dump, no consumer needs dict lookup.
202
227
  volumes: list[VolumeAttach] = Field(default_factory=list)
203
228
  autoscaler: Autoscaler = Field(default_factory=Autoscaler)
204
- # An omitted @seq.concurrent must NOT send null: the gateway would then have no concurrency to scale by.
205
- # Materialize the spec default — contracts.md §2's example {"max_inputs": 1, "target_inputs": 1}, i.e.
206
- # one input per container (Modal's no-`@modal.concurrent` semantics) — same discipline as `autoscaler`
207
- # (goal 13).
208
- concurrency: Concurrency = Field(default_factory=lambda: Concurrency(max_inputs=1, target_inputs=1))
229
+ # Every field optional: an omitted @seq.concurrent serves one robot session per model copy, one copy per card.
230
+ concurrency: Concurrency = Field(default_factory=Concurrency)
209
231
  timeouts: Timeouts | None = None
210
232
  # Weights in several named parts: `weights` is the first and `weights_name` its name; `extra_weights` the others,
211
233
  # each provisioned like `weights`. @seq.load then receives {name: directory}. Both None (and absent from the wire):
@@ -279,3 +301,5 @@ class DeploymentStatusResponse(BaseModel):
279
301
  status: DeploymentState
280
302
  model: str
281
303
  error: str | None = Field(default=None, description="Build-failure reason; set only when FAILED")
304
+ # Why a deployment that was serving was marked FAILED: its workers kept dying of the same cause.
305
+ reason: Literal["gpu_out_of_memory", "host_out_of_memory", "crashed"] | None = None
@@ -20,6 +20,10 @@ __all__ = [
20
20
  "Estimate",
21
21
  "EvalResponse",
22
22
  "EvalDryRunResponse",
23
+ "CardSide",
24
+ "CardInUse",
25
+ "EvalCards",
26
+ "EvalQueue",
23
27
  "RolloutResult",
24
28
  "EvalStatus",
25
29
  "SuiteAgg",
@@ -42,8 +46,15 @@ class EvalRequest(BaseModel):
42
46
  benchmark: str
43
47
  suite: str | None = None
44
48
  trials: int | None = None
45
- gpus: int | None = None
46
- envs_per_gpu: int | None = None
49
+ # How the eval is laid out, written by the user and never derived: how many policy machines and on which card
50
+ # (default: one, on the deployment's declared card), how many benchmark machines and on which card (default: one,
51
+ # on the benchmark's declared card), and how many environments each benchmark machine runs (default: the
52
+ # benchmark author's `envs=`, else 1). {gs-plans/parallel/eval.md "1. 两边写法对齐"}
53
+ policy_instances: int = Field(default=1, ge=1, le=64)
54
+ policy_gpu: str | None = Field(default=None, max_length=32)
55
+ benchmark_instances: int = Field(default=1, ge=1, le=64)
56
+ benchmark_gpu: str | None = Field(default=None, max_length=32)
57
+ envs: int | None = Field(default=None, ge=1, le=100000)
47
58
  vlm_max_concurrency: int | None = None
48
59
  adapter: str | None = None
49
60
  compare: str | None = Field(default=None, description="Another model id to compare against")
@@ -90,6 +101,56 @@ class EvalResponse(BaseModel):
90
101
  gpus_granted: int
91
102
  estimate: Estimate
92
103
  clamped_by: ClampInfo | None = None
104
+ # Set when the account was short of GPUs: the eval waits (up to its timeout) instead of being refused.
105
+ queue: EvalQueue | None = None
106
+
107
+
108
+ class CardSide(BaseModel):
109
+ """One side of an eval's machines: how many, on which card, and how many GPUs each holds."""
110
+
111
+ model_config = ConfigDict(extra="forbid")
112
+
113
+ instances: int
114
+ gpu: str
115
+ cards_each: int
116
+
117
+
118
+ class CardInUse(BaseModel):
119
+ """The account's GPUs held now, serving and evals apart; None when it could not be read."""
120
+
121
+ model_config = ConfigDict(extra="forbid")
122
+
123
+ serving: int | None = None
124
+ eval: int | None = None
125
+
126
+
127
+ class EvalCards(BaseModel):
128
+ """The dry-run's card report — the same checks a real submit runs, without starting or charging anything.
129
+ {gs-plans/parallel/eval.md "4. 预演里卡的部分"}"""
130
+
131
+ model_config = ConfigDict(extra="forbid")
132
+
133
+ policy: CardSide
134
+ benchmark: CardSide
135
+ total: int
136
+ limit: int
137
+ in_use: CardInUse
138
+ available: int | None = None
139
+ short: int = 0
140
+ reason: Literal["gpu_limit_reached", "gpu_busy", "gpu_low_stock"] | None = None
141
+ would_queue: bool = False
142
+ queue_timeout_s: int = 600
143
+
144
+
145
+ class EvalQueue(BaseModel):
146
+ """An eval waiting for the account's GPUs: what it needs, what is free, how long it has waited, when it gives up."""
147
+
148
+ model_config = ConfigDict(extra="forbid")
149
+
150
+ cards: int
151
+ available: int | None = None
152
+ waited_s: float = 0
153
+ gives_up_at: str | None = None
93
154
 
94
155
 
95
156
  class EvalDryRunResponse(BaseModel):
@@ -98,6 +159,7 @@ class EvalDryRunResponse(BaseModel):
98
159
  model_config = ConfigDict(extra="forbid")
99
160
 
100
161
  estimate: Estimate
162
+ cards: EvalCards | None = None
101
163
 
102
164
 
103
165
  class RolloutResult(BaseModel):
@@ -124,6 +186,7 @@ class EvalStatus(str, Enum):
124
186
  """Restricted enum — same discipline as DeploymentState (not a free string)."""
125
187
 
126
188
  PENDING = "pending"
189
+ QUEUED = "queued"
127
190
  RUNNING = "running"
128
191
  COMPLETED = "completed"
129
192
  FAILED = "failed"
@@ -3,10 +3,8 @@
3
3
  reads back. One IntFlag, so a registration can carry several lifecycle entrypoints in one integer and
4
4
  the harness recovers each by testing a bit.
5
5
 
6
- Modeled on Modal's `_PartialFunctionFlags` (one IntFlag, one bit per lifecycle hook)
7
- {modal _partial_function.py:29}. Four members map bit-for-bit onto Modal's flags so the harness can be
8
- a thin adaptation of Modal's loader; the rest are platform-only lifecycle hooks Modal has no analogue
9
- for (a warm-up hook, a plan step, and the benchmark SETUP / RESET / CHECK / SCORE quartet).
6
+ One bit per lifecycle hook: load, infer, an episode boundary and shutdown for a policy; a warm-up hook and a
7
+ plan step; and the benchmark SETUP / RESET / CHECK / SCORE quartet.
10
8
 
11
9
  eval-raw revision: the platform provides the reset→step→check loop as a tool; the user defines the goal
12
10
  through four hooks — SETUP (load sim + assets), RESET (build one episode's env), CHECK (per-step: read raw
@@ -14,10 +12,10 @@ env state → done + user-named metrics), SCORE (cross-episode aggregate). RESET
14
12
  the new hook bits. The old single-hook `ROLLOUT` is DEPRECATED: bit 8192 stays reserved (never reused for
15
13
  another member) but appears in NO bindings — the setup/reset/check/score model replaces it.
16
14
 
17
- The exact bit values are the contract — see `MODAL_MAPPING` / `PLATFORM_ONLY` for the authoritative
18
- "member → bit value → Modal counterpart" list. A member taking the wrong value, WARMUP collapsing back
19
- into LOAD, RESET/CHECK overlapping an existing bit, or ROLLOUT being re-bound is caught bit-by-bit (see
20
- tests), not waved away with "an IntFlag is naturally bit-composable".
15
+ The exact bit values are the contract: a registration serialized by one version must decode the same in
16
+ another. A member taking the wrong value, WARMUP collapsing back into LOAD, RESET/CHECK overlapping an
17
+ existing bit, or ROLLOUT being re-bound is caught bit-by-bit (see tests), not waved away with "an IntFlag
18
+ is naturally bit-composable".
21
19
  """
22
20
  from __future__ import annotations
23
21
 
@@ -25,18 +23,18 @@ from enum import IntFlag
25
23
 
26
24
 
27
25
  class SeqFlags(IntFlag):
28
- # ── mapped bit-for-bit onto Modal `_PartialFunctionFlags` ────────────────────────────────────
29
- LOAD = 2 # Modal ENTER — load weights into VRAM (one-shot; no snapshot bisection)
30
- SHUTDOWN = 8 # Modal EXIT — teardown / release the container
31
- INFER = 16 # Modal CALLABLE_INTERFACE — the obs→ActionChunk call
32
- ON_EPISODE = 1024 # Modal ~SESSIONED — sticky / session boundary for stateful policies
26
+ # ── a policy's lifecycle ────────────────────────────────────────────────────────────────────────
27
+ LOAD = 2 # load weights into VRAM (one-shot; no snapshot bisection)
28
+ SHUTDOWN = 8 # teardown / release the container
29
+ INFER = 16 # the obs→ActionChunk call
30
+ ON_EPISODE = 1024 # sticky / session boundary for stateful policies
33
31
 
34
- # ── platform-only: no Modal counterpart; each an independent, disjoint bit ────────────────────
32
+ # ── each an independent bit, disjoint from the four above ──────────────────────────────────────
35
33
  # WARMUP is its OWN bit, NOT an alias of LOAD (S1.yaml goal 5 — "WARMUP=1(独立 bit,明确不并入
36
34
  # LOAD)"). A policy may bind @seq.load and @seq.warmup to two different methods → two independent
37
35
  # bindings, so the bit must be distinct from LOAD. Value 1 is bit 0, unused by any mapped flag.
38
- WARMUP = 1 # JIT warm-up hook (ours; Modal folded warm-up into ENTER, we keep it separate)
39
- PLAN = 4 # a policy planning step (ours; Modal has none) — bit 2, unused by Modal
36
+ WARMUP = 1 # JIT warm-up hook, kept separate from LOAD
37
+ PLAN = 4 # a policy planning step — bit 2
40
38
  # benchmark lifecycle (eval-raw: platform provides the loop tool, user hooks the goal in) — each an
41
39
  # independent, disjoint bit, disjoint too from every mapped bit:
42
40
  SETUP = 4096 # load sim + assets, once (≈ @seq.load)
@@ -49,21 +47,6 @@ class SeqFlags(IntFlag):
49
47
  ROLLOUT = 8192
50
48
 
51
49
 
52
- # Authoritative "member → Modal counterpart" list. Anything here must equal the bit above; anything in
53
- # PLATFORM_ONLY must be disjoint from every mapped bit and from each other.
54
- MODAL_MAPPING: dict[str, str] = {
55
- "LOAD": "ENTER",
56
- "SHUTDOWN": "EXIT",
57
- "INFER": "CALLABLE_INTERFACE",
58
- "ON_EPISODE": "~SESSIONED",
59
- }
60
-
61
- # Platform-only members Modal has no flag for. ROLLOUT is included: it is still a real (if deprecated)
62
- # member and its bit must stay disjoint from every other, so the disjointness check must cover it.
63
- PLATFORM_ONLY: frozenset[str] = frozenset(
64
- {"WARMUP", "PLAN", "SETUP", "SCORE", "RESET", "CHECK", "ROLLOUT"}
65
- )
66
-
67
50
  # The benchmark lifecycle bits a live registration may bind (eval-raw): SETUP / RESET / CHECK / SCORE.
68
51
  # ROLLOUT is deliberately absent — deprecated, never bound.
69
52
  BENCHMARK_BINDABLE: frozenset[SeqFlags] = frozenset(
@@ -0,0 +1,17 @@
1
+ """
2
+ The robot-arm I/O contracts (`action=`, `observation=`, `embodiments=`), re-exported from
3
+ `sequence_base.presets.robot_arm`.
4
+
5
+ Declaring one is optional since contract 0.4.14: a deployment that declares none is held to nothing, and its
6
+ `inputs` / `outputs` pass through the worker untouched. A declared contract is still enforced by the worker and
7
+ handed to the robot at connect. {gs-plans/custom-io/README.md "2. worker:原样交给用户代码"}
8
+ """
9
+ from sequence_base.presets.robot_arm import ( # noqa: F401 — re-exports
10
+ PROPRIO_FIELDS,
11
+ ActionContract,
12
+ Camera,
13
+ Embodiment,
14
+ ObservationContract,
15
+ )
16
+
17
+ __all__ = ["ActionContract", "PROPRIO_FIELDS", "Camera", "ObservationContract", "Embodiment"]