sequence-base 0.4.28__tar.gz → 0.4.30__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 (123) hide show
  1. {sequence_base-0.4.28 → sequence_base-0.4.30}/PKG-INFO +1 -1
  2. {sequence_base-0.4.28 → sequence_base-0.4.30}/pyproject.toml +1 -1
  3. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/__init__.py +4 -1
  4. sequence_base-0.4.30/sequence_base/by_name.py +90 -0
  5. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/constants/version.py +3 -1
  6. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/construct.py +26 -5
  7. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/connect.py +3 -0
  8. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/deployments.py +3 -0
  9. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/own_io.py +3 -0
  10. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/policy.py +18 -7
  11. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/wire.py +5 -0
  12. sequence_base-0.4.30/sequence_base/feedback.py +106 -0
  13. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/presets/robot_arm.py +4 -0
  14. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ActRequest.json +15 -1
  15. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ActResponse.json +1 -1
  16. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ActionChunk.json +1 -1
  17. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ActionContract.json +13 -1
  18. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ActionStep.json +1 -1
  19. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Actions.json +13 -1
  20. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/AdapterPair.json +1 -1
  21. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/AddLocal.json +1 -1
  22. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Autoscaler.json +1 -1
  23. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
  24. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/BenchmarkRegisterRequest.json +13 -1
  25. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/BenchmarkSpec.json +2 -2
  26. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
  27. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/BootstrapResponse.json +1 -1
  28. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Camera.json +1 -1
  29. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/CodeBundle.json +1 -1
  30. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Concurrency.json +1 -1
  31. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Conditions.json +1 -1
  32. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ConnectRequest.json +1 -1
  33. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ConnectResponse.json +37 -1
  34. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/DeploymentAccepted.json +1 -1
  35. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/DeploymentPolicy.json +37 -1
  36. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/DeploymentRequest.json +37 -1
  37. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
  38. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Embodiment.json +13 -1
  39. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ErrorBody.json +1 -1
  40. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ErrorCodes.json +1 -1
  41. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Estimate.json +1 -1
  42. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
  43. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/EvalReport.json +1 -1
  44. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/EvalRequest.json +1 -1
  45. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/EvalResponse.json +1 -1
  46. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageFrame.json +1 -1
  47. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageSpec.json +1 -1
  48. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
  49. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepApt.json +1 -1
  50. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepEnv.json +1 -1
  51. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepProject.json +1 -1
  52. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepRun.json +1 -1
  53. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
  54. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ImageStepUvPip.json +1 -1
  55. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/InferParam.json +1 -1
  56. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Lease.json +1 -1
  57. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/MetricAgg.json +1 -1
  58. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Observation.json +1 -1
  59. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ObservationContract.json +1 -1
  60. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Observations.json +1 -1
  61. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ObserveRequest.json +1 -1
  62. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ObserveResponse.json +1 -1
  63. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/PolicyRegistration.json +42 -5
  64. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/PolicySpec.json +37 -1
  65. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Project.json +1 -1
  66. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Proprioception.json +1 -1
  67. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/RenderSpec.json +1 -1
  68. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/ReplaySpec.json +1 -1
  69. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Resources.json +1 -1
  70. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/RolloutResult.json +1 -1
  71. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/SecretCreateRequest.json +1 -1
  72. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/SecretInfo.json +1 -1
  73. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/SecretRef.json +1 -1
  74. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/SeqFlags.json +1 -1
  75. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Step.json +1 -1
  76. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Stream.json +1 -1
  77. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/TaskAgg.json +1 -1
  78. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/TemplateFiles.json +1 -1
  79. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Tensors.json +1 -1
  80. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Timeouts.json +1 -1
  81. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/UploadTarget.json +1 -1
  82. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/Variant.json +1 -1
  83. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeFile.json +1 -1
  84. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeInfo.json +1 -1
  85. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeMount.json +1 -1
  86. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeRef.json +1 -1
  87. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
  88. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
  89. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schemas/WarmingError.json +1 -1
  90. {sequence_base-0.4.28 → sequence_base-0.4.30}/.gitignore +0 -0
  91. {sequence_base-0.4.28 → sequence_base-0.4.30}/README.md +0 -0
  92. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/auth.py +0 -0
  93. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/codec/__init__.py +0 -0
  94. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/codec/image.py +0 -0
  95. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/constants/__init__.py +0 -0
  96. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/constants/cards.py +0 -0
  97. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/constants/headers.py +0 -0
  98. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/constants/limits.py +0 -0
  99. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/__init__.py +0 -0
  100. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/accounts.py +0 -0
  101. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/act.py +0 -0
  102. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/adapters.py +0 -0
  103. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/benchmarks.py +0 -0
  104. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/errors.py +0 -0
  105. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/eval.py +0 -0
  106. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/flags.py +0 -0
  107. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/io.py +0 -0
  108. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/obs_schedule.py +0 -0
  109. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/observe.py +0 -0
  110. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/templates.py +0 -0
  111. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/contract/volumes.py +0 -0
  112. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/episode.py +0 -0
  113. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/errors/__init__.py +0 -0
  114. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/errors/transport.py +0 -0
  115. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/msgpack_io.py +0 -0
  116. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/obs/__init__.py +0 -0
  117. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/obs/log.py +0 -0
  118. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/obs/rid.py +0 -0
  119. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/presets/__init__.py +0 -0
  120. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/presets/frames.py +0 -0
  121. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/schema_export.py +0 -0
  122. {sequence_base-0.4.28 → sequence_base-0.4.30}/sequence_base/telemetry/__init__.py +0 -0
  123. {sequence_base-0.4.28 → sequence_base-0.4.30}/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.28
3
+ Version: 0.4.30
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.28"
3
+ version = "0.4.30"
4
4
  description = "Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness."
5
5
  requires-python = ">=3.10"
6
6
  dependencies = ["pydantic>=2.0"]
@@ -130,6 +130,9 @@ from sequence_base.errors.transport import (
130
130
  from sequence_base.episode import Episode
131
131
 
132
132
 
133
+ from sequence_base.feedback import Feedback
134
+
135
+
133
136
  from sequence_base import auth
134
137
 
135
138
 
@@ -174,7 +177,7 @@ __all__ = [
174
177
 
175
178
  "TransportUnavailable", "TransportWarming", "TransportRejected", "TransportOverloaded",
176
179
 
177
- "Episode",
180
+ "Episode", "Feedback",
178
181
 
179
182
  "auth",
180
183
  ]
@@ -0,0 +1,90 @@
1
+ """
2
+ Which arguments of a method a policy class names (infer=, observe=) an act's inputs fill: the inputs whole, its one
3
+ argument - or, when it takes two or more without a default, each key of the inputs to its argument of that name.
4
+
5
+ In one sentence: `takes_by_name(parameters, platform)` reads the method's arguments - less *args, **kwargs and the ones
6
+ the platform hands it itself (`platform`: INFER_PLATFORM, OBSERVE_PLATFORM) → two or more of them without a default:
7
+ it takes its inputs by name (ByName), each of its arguments a key - one without a default a key the inputs must hold,
8
+ one with a default a key they may → `ByName.call(inputs, what)` makes what it is called with, refusing inputs that are
9
+ no dict, lack a key it needs, or hold one it takes no argument for (unless it takes **kwargs), each named; fewer than
10
+ two: None, the inputs go to its first argument whole and its others with a default are inference parameters, as before.
11
+
12
+ WHY by the arguments without a default: a method that needs two or more could never be handed an act's inputs whole -
13
+ the platform refused to deploy one - so binding them by name changes nothing a class deployed today does, and a method
14
+ that takes one beside its inference parameters keeps taking its inputs whole. The SDK reads the rule when a class is
15
+ declared (seq/named.py, registry.infer_params), the worker when it calls the method (harness/handler/loader.py): one
16
+ rule on both sides.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import inspect
21
+ from collections.abc import Iterable, Mapping
22
+ from dataclasses import dataclass
23
+ from typing import Any
24
+
25
+ __all__ = ["INFER_PLATFORM", "OBSERVE_PLATFORM", "ByName", "takes_by_name"]
26
+
27
+
28
+
29
+ INFER_PLATFORM = frozenset({"seed", "episode_id", "episode", "feedback", "plan", "robot", "embodiment"})
30
+ OBSERVE_PLATFORM = frozenset({"step"})
31
+
32
+ _VARIADIC = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class ByName:
37
+ """A method that takes an act's inputs by name: `names`, the arguments they fill, in order; `required`, those with
38
+ no default; `positional`, those it takes by position only (before a `/`); `rest`, whether it takes **kwargs - any
39
+ other key of the inputs going there; `platform`, the names the platform hands it itself."""
40
+
41
+ names: tuple[str, ...]
42
+ required: tuple[str, ...]
43
+ positional: tuple[str, ...]
44
+ rest: bool
45
+ platform: frozenset[str]
46
+
47
+ def call(self, inputs: Any, what: str) -> tuple[list, dict]:
48
+ """(args, kwargs) the method `what` ("Model.predict") is called with for an act's `inputs`; ValueError, in its
49
+ words, for inputs that are not a dict of its arguments."""
50
+ takes = ", ".join(n if n in self.required else f"{n} (optional)" for n in self.names)
51
+ if not isinstance(inputs, Mapping):
52
+ raise ValueError(f"{what} takes its inputs by name ({takes}): an act's inputs are a dict of them, and "
53
+ f"these are a {type(inputs).__name__}")
54
+ missing = [n for n in self.required if n not in inputs]
55
+ extra = [k for k in inputs if k not in self.names
56
+ and not (self.rest and isinstance(k, str) and k not in self.platform)]
57
+ if missing or extra:
58
+ held = f"lack {_keys(missing)}" if missing else ""
59
+ if extra:
60
+ held += (" and " if held else "") + f"hold {_keys(extra)}, which it takes no argument for"
61
+ ours = [k for k in extra if k in self.platform]
62
+ mine = f" ({_keys(ours)}: the platform's to hand it)" if ours else ""
63
+ raise ValueError(f"{what} takes its inputs by name ({takes}), and these {held}{mine}")
64
+ args: list = []
65
+ for i, name in enumerate(self.positional):
66
+ if name not in inputs:
67
+ later = next((n for n in self.positional[i + 1:] if n in inputs), None)
68
+ if later is not None:
69
+ raise ValueError(f"{what} takes {name} and {later} by position only, in that order: these hold "
70
+ f"{later!r} and no {name!r}")
71
+ break
72
+ args.append(inputs[name])
73
+ return args, {k: v for k, v in inputs.items() if k not in self.positional}
74
+
75
+
76
+ def takes_by_name(parameters: Iterable[inspect.Parameter], platform: frozenset[str] = frozenset()) -> ByName | None:
77
+ """How a method of these `parameters` (self left out) takes an act's inputs: by name (ByName) when two or more of
78
+ its arguments - *args, **kwargs and `platform`'s left out - have no default, else None: whole, as one argument."""
79
+ every = list(parameters)
80
+ params = [p for p in every if p.kind not in _VARIADIC and p.name not in platform]
81
+ required = tuple(p.name for p in params if p.default is inspect.Parameter.empty)
82
+ if len(required) < 2:
83
+ return None
84
+ return ByName(names=tuple(p.name for p in params), required=required,
85
+ positional=tuple(p.name for p in params if p.kind is inspect.Parameter.POSITIONAL_ONLY),
86
+ rest=any(p.kind is inspect.Parameter.VAR_KEYWORD for p in every), platform=frozenset(platform))
87
+
88
+
89
+ def _keys(names: list) -> str:
90
+ return ", ".join(repr(n) for n in names)
@@ -4,7 +4,9 @@
4
4
 
5
5
 
6
6
 
7
- CONTRACT_VERSION = "0.5.25"
7
+ CONTRACT_VERSION = "0.5.27"
8
+
9
+
8
10
 
9
11
 
10
12
 
@@ -1,10 +1,12 @@
1
1
  """
2
2
  How a policy class that names its own methods is built (PolicyRegistration.constructor): its constructor - or the
3
- factory it names - called with the arguments its decorator was given, once two forms in them are filled in.
3
+ factory it names, a classmethod of it or a module's function - called with the arguments its decorator was given, once
4
+ two forms in them are filled in.
4
5
 
5
6
  In one sentence: a value {"$config": path, "$set": {...}} becomes the YAML or JSON file at `path` - from the folder of
6
7
  the class's file - read where the policy runs, with `$set`'s keys laid over its top level → in every string,
7
8
  "{weights}" becomes the weights directory and "{weights.<part>}" a named part's → `cls.<make>(*args, **kwargs)`, or
9
+ `function(*args, **kwargs)` for make="package.module:function" - the module imported where the policy runs - or
8
10
  `cls(*args, **kwargs)` without a factory.
9
11
 
10
12
  WHY a config file is read where the policy runs: it is part of the project (XPolicyLab's deploy.yml beside its
@@ -14,12 +16,14 @@ worker's directory.
14
16
  """
15
17
  from __future__ import annotations
16
18
 
19
+ import importlib
17
20
  import json
18
21
  import os
19
22
  import re
20
23
  from typing import Any
21
24
 
22
- __all__ = ["CONFIG_KEY", "SET_KEY", "ConstructError", "build", "config", "expand", "is_config", "read_config"]
25
+ __all__ = ["CONFIG_KEY", "SET_KEY", "ConstructError", "build", "config", "expand", "is_config", "maker",
26
+ "read_config"]
23
27
 
24
28
  CONFIG_KEY, SET_KEY = "$config", "$set"
25
29
  _WEIGHTS = re.compile(r"\{weights(?:\.([A-Za-z_][A-Za-z0-9_-]*))?\}")
@@ -96,8 +100,25 @@ def _weights_of(part: str | None, weights: Any) -> str:
96
100
 
97
101
 
98
102
  def build(cls: type, constructor: Any, *, base_dir: str, weights: Any) -> Any:
99
- """The instance `constructor` (a PolicyConstructor) makes of `cls`: `cls.<make>(*args, **kwargs)` or `cls(...)`."""
103
+ """The instance `constructor` (a PolicyConstructor) makes of `cls`: `cls.<make>(*args, **kwargs)`, the function
104
+ make="package.module:function" names called so, or `cls(...)`."""
100
105
  args = expand(list(constructor.args), base_dir=base_dir, weights=weights)
101
106
  kwargs = expand(dict(constructor.kwargs), base_dir=base_dir, weights=weights)
102
- make = getattr(cls, constructor.make) if constructor.make else cls
103
- return make(*args, **kwargs)
107
+ return maker(cls, constructor.make)(*args, **kwargs)
108
+
109
+
110
+ def maker(cls: type, make: str | None) -> Any:
111
+ """What builds the instance: `cls` for no make=, its classmethod or staticmethod `make`, or - make written
112
+ "package.module:function" - that module's function, the module imported (as the policy's code imports it).
113
+ """
114
+ if not make:
115
+ return cls
116
+ module, colon, name = make.rpartition(":")
117
+ if not colon:
118
+ return getattr(cls, make)
119
+ found = getattr(importlib.import_module(module), name, None)
120
+ if found is None:
121
+ raise ConstructError(f"make={make!r}: {module} has no function {name!r}")
122
+ if not callable(found):
123
+ raise ConstructError(f"make={make!r}: {module}.{name} is not callable ({type(found).__name__})")
124
+ return found
@@ -62,3 +62,6 @@ class ConnectResponse(BaseModel):
62
62
 
63
63
  observations: Observations | None = None
64
64
  actions: Actions | None = None
65
+
66
+
67
+ feedback: bool | None = None
@@ -184,6 +184,9 @@ class DeploymentPolicy(BaseModel):
184
184
 
185
185
 
186
186
  streaming: bool = Field(default=False, exclude=True)
187
+
188
+
189
+ feedback: bool | None = None
187
190
  secrets: list[SecretRef] = Field(default_factory=list)
188
191
 
189
192
 
@@ -163,6 +163,9 @@ class Actions(BaseModel):
163
163
  open_loop_horizon: int | None = Field(default=None, ge=1, le=100_000)
164
164
 
165
165
 
166
+ aligned: bool | None = None
167
+
168
+
166
169
  class Tensors(BaseModel):
167
170
  """A policy's inputs reach its code as torch tensors on its device - a batch dimension first when `batch` - and the
168
171
  tensors it answers with go back as arrays; float64 arrays arrive as float32. Converted in the replica, last before
@@ -377,6 +377,10 @@ class PolicySpec(BaseModel):
377
377
 
378
378
  streaming: bool = Field(default=False, exclude=True)
379
379
 
380
+
381
+
382
+ feedback: bool | None = None
383
+
380
384
  action: ActionContract | None = None
381
385
 
382
386
  observation: ObservationContract | None = None
@@ -478,19 +482,26 @@ def check_weights_parts(weights_name: str | None, extra: dict) -> None:
478
482
  raise ValueError(f"at most {MAX_WEIGHTS_PARTS} weights parts")
479
483
 
480
484
 
485
+
486
+ _MAKE = r"^(?:[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*:)?[A-Za-z_][A-Za-z0-9_]*$"
487
+
488
+
481
489
  class PolicyConstructor(BaseModel):
482
490
  """How the worker builds a policy class that names its own methods (`@seq.policy(infer="get_action", ...)`): its
483
- own constructor, or the factory it names (`make`, e.g. LeRobot's `from_pretrained`), called with `args` and
484
- `kwargs` where `@seq.load` would run - the replica's cold start, the weights on disk. The values are the user's own,
485
- as JSON carries them, but for two forms the worker fills in first (sequence_base.construct.expand): a config file's
486
- contents, `{"$config": <its path from the class's file>, "$set": {<keys laid over it>}}`, YAML or JSON, and in any
487
- string `"{weights}"` (the weights directory) or `"{weights.<part>}"` (a named part's).
491
+ own constructor, or the factory it names (`make`: a classmethod of it, e.g. LeRobot's `from_pretrained`, or a
492
+ module's function, "package.module:function", whose return is the instance its methods are called on), called
493
+ with `args` and `kwargs` where `@seq.load` would run - the replica's cold start, the weights on disk. The values are
494
+ the user's own, as JSON carries them, but for two forms the worker fills in first (sequence_base.construct.expand):
495
+ a config file's contents, `{"$config": <its path from the class's file>, "$set": {<keys laid over it>}}`, YAML or
496
+ JSON, and in any string `"{weights}"` (the weights directory) or `"{weights.<part>}"` (a named part's).
497
+
488
498
  """
489
499
 
490
500
  model_config = ConfigDict(extra="forbid")
491
501
 
492
- make: str | None = Field(default=None, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$",
493
- description="a classmethod or staticmethod that returns the instance; None: the class")
502
+ make: str | None = Field(default=None, max_length=512, pattern=_MAKE,
503
+ description="a classmethod or staticmethod that returns the instance, or a module's "
504
+ "function, 'package.module:function' (contract 0.5.27); None: the class")
494
505
  args: list[Any] = Field(default_factory=list, description="positional arguments, in order")
495
506
  kwargs: dict[str, Any] = Field(default_factory=dict, description="keyword arguments")
496
507
 
@@ -46,6 +46,11 @@ class ActRequest(BaseModel):
46
46
  description="The newest observation's number in its episode's stream (POST /observe)")
47
47
 
48
48
 
49
+
50
+ feedback: dict[str, Any] | None = Field(default=None,
51
+ description="What the robot was playing: left, delay, played, last")
52
+
53
+
49
54
  class ActResponse(BaseModel):
50
55
  """200 from /act, version 2. `outputs` is infer()'s return value, unchanged; the rest is platform metadata."""
51
56
 
@@ -0,0 +1,106 @@
1
+ """
2
+ What a policy that asks for execution feedback is handed (PolicySpec.feedback): its infer declares `feedback` and gets,
3
+ with each act, what the robot was playing when it sent that act - the rows it had not played yet, how many control
4
+ steps it expects to play before the answer is back, how many rows of its chunk it had played, and the row it played
5
+ last.
6
+
7
+ In one sentence: the robot's executor (sequence_ai.Player, or an eval's) → puts {left, delay, played, last} in the act
8
+ it sends → the worker checks it by feedback_of and hands infer a Feedback → a policy that inpaints (RTC) freezes the rows
9
+ the delay covers, one trained on its last action reads `last`.
10
+
11
+ WHY the executor fills it, act by act, and the platform keeps none of it: which rows are left and how long a round
12
+ trip takes only the robot knows - a worker times the inference alone.
13
+
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import numbers
18
+ from collections.abc import Mapping
19
+ from typing import Any
20
+
21
+ __all__ = ["Feedback", "feedback_of", "KEYS"]
22
+
23
+
24
+ KEYS = ("left", "delay", "played", "last")
25
+
26
+
27
+ class Feedback:
28
+ """What the robot was playing when it sent this act: `left` - the rows of the chunk it was playing that it had not
29
+ played yet, in order, the ones due first and then the chunk's spare ones (empty on an episode's first act, or when
30
+ it held none); `delay` - how many control steps it expects to play before this answer is back, its longest of its
31
+ last 10 round trips times its control rate, rounded up (0 before it measured one, and in an eval); `played` - how
32
+ many rows of that chunk it had played when it observed; `last` - the row it played last, as the platform clipped it
33
+ (None before it played one). All four are the robot's: they are read, never set."""
34
+
35
+ __slots__ = ("_left", "_delay", "_played", "_last")
36
+
37
+ def __init__(self, left: Any = (), delay: int = 0, played: int = 0, last: Any = None) -> None:
38
+ object.__setattr__(self, "_left", tuple(left))
39
+ object.__setattr__(self, "_delay", int(delay))
40
+ object.__setattr__(self, "_played", int(played))
41
+ object.__setattr__(self, "_last", last)
42
+
43
+ @property
44
+ def left(self) -> tuple:
45
+ return self._left
46
+
47
+ @property
48
+ def delay(self) -> int:
49
+ return self._delay
50
+
51
+ @property
52
+ def played(self) -> int:
53
+ return self._played
54
+
55
+ @property
56
+ def last(self) -> Any:
57
+ return self._last
58
+
59
+ def __setattr__(self, name: str, value: Any) -> None:
60
+ raise AttributeError(f"Feedback.{name} is what the robot was playing: read it, never set it")
61
+
62
+ def __repr__(self) -> str:
63
+ return (f"Feedback(left={len(self._left)} rows, delay={self._delay}, played={self._played}, "
64
+ f"last={self._last!r})")
65
+
66
+
67
+ def feedback_of(value: Any, width: int | None = None) -> Feedback:
68
+ """The Feedback an act's `feedback` says - a dict of `left` (a list of rows, or an array of them), `delay` and
69
+ `played` (counts from 0) and `last` (a row, or None) - checked, the one rule a worker and a robot's SDK read it by:
70
+ with `width` (the robot-arm preset's action_dim) each row is that many numbers; without, a row of a policy's own
71
+ outputs is handed as it came. Raises ValueError saying what is wrong."""
72
+ if not isinstance(value, Mapping):
73
+ raise ValueError(f"feedback is a dict of {', '.join(KEYS)}, not {type(value).__name__}")
74
+ unknown = [k for k in value if k not in KEYS]
75
+ if unknown:
76
+ raise ValueError(f"feedback has no {', '.join(map(repr, unknown))}: it holds {', '.join(KEYS)}")
77
+ left = value.get("left")
78
+ if left is None:
79
+ left = ()
80
+ elif isinstance(left, (str, bytes, Mapping)) or not (isinstance(left, (list, tuple))
81
+ or (getattr(left, "ndim", 0) or 0) >= 1):
82
+ raise ValueError(f"feedback's left is the rows the robot had not played, a list of them, not "
83
+ f"{type(left).__name__}")
84
+ rows = list(left)
85
+ counts = {}
86
+ for key in ("delay", "played"):
87
+ n = value.get(key, 0)
88
+ if isinstance(n, bool) or not isinstance(n, numbers.Integral) or n < 0:
89
+ raise ValueError(f"feedback's {key} is a count of control steps from 0, not {n!r}")
90
+ counts[key] = int(n)
91
+ last = value.get("last")
92
+ if width is not None:
93
+ for where, row in [*((f"left[{i}]", r) for i, r in enumerate(rows)),
94
+ *((("last", last),) if last is not None else ())]:
95
+ _fits(where, row, width)
96
+ return Feedback(rows, counts["delay"], counts["played"], last)
97
+
98
+
99
+ def _fits(where: str, row: Any, width: int) -> None:
100
+ if isinstance(row, (str, bytes, Mapping)) or not (isinstance(row, (list, tuple)) or getattr(row, "ndim", None) == 1):
101
+ raise ValueError(f"feedback's {where} is a row of {width} numbers (the policy's action_dim), not "
102
+ f"{type(row).__name__}")
103
+ if len(row) != width:
104
+ raise ValueError(f"feedback's {where} has {len(row)} values; the policy's action_dim is {width}")
105
+ if not all(isinstance(v, numbers.Real) and not isinstance(v, bool) for v in row):
106
+ raise ValueError(f"feedback's {where} is a row of {width} numbers (the policy's action_dim)")
@@ -129,6 +129,10 @@ class ActionContract(BaseModel):
129
129
  bounds: list[tuple[float, float]] | None = Field(default=None, max_length=256)
130
130
  max_delta: float | list[float] | None = None
131
131
 
132
+
133
+
134
+ aligned: bool | None = None
135
+
132
136
  @model_validator(mode="after")
133
137
  def _limits_fit(self) -> "ActionContract":
134
138
  if self.bounds is not None:
@@ -28,6 +28,20 @@
28
28
  "description": "The episode this call belongs to (per-episode state)",
29
29
  "title": "Episode Id"
30
30
  },
31
+ "feedback": {
32
+ "anyOf": [
33
+ {
34
+ "additionalProperties": true,
35
+ "type": "object"
36
+ },
37
+ {
38
+ "type": "null"
39
+ }
40
+ ],
41
+ "default": null,
42
+ "description": "What the robot was playing: left, delay, played, last",
43
+ "title": "Feedback"
44
+ },
31
45
  "inputs": {
32
46
  "description": "Whatever the policy's infer() takes, exactly as the robot's code built it",
33
47
  "title": "Inputs"
@@ -93,5 +107,5 @@
93
107
  ],
94
108
  "title": "ActRequest",
95
109
  "type": "object",
96
- "x-contract-version": "0.5.25"
110
+ "x-contract-version": "0.5.27"
97
111
  }
@@ -89,5 +89,5 @@
89
89
  ],
90
90
  "title": "ActResponse",
91
91
  "type": "object",
92
- "x-contract-version": "0.5.25"
92
+ "x-contract-version": "0.5.27"
93
93
  }
@@ -99,5 +99,5 @@
99
99
  ],
100
100
  "title": "ActionChunk",
101
101
  "type": "object",
102
- "x-contract-version": "0.5.25"
102
+ "x-contract-version": "0.5.27"
103
103
  }
@@ -23,6 +23,18 @@
23
23
  "action_space": {
24
24
  "$ref": "#/$defs/ActionSpace"
25
25
  },
26
+ "aligned": {
27
+ "anyOf": [
28
+ {
29
+ "type": "boolean"
30
+ },
31
+ {
32
+ "type": "null"
33
+ }
34
+ ],
35
+ "default": null,
36
+ "title": "Aligned"
37
+ },
26
38
  "bounds": {
27
39
  "anyOf": [
28
40
  {
@@ -84,5 +96,5 @@
84
96
  ],
85
97
  "title": "ActionContract",
86
98
  "type": "object",
87
- "x-contract-version": "0.5.25"
99
+ "x-contract-version": "0.5.27"
88
100
  }
@@ -19,5 +19,5 @@
19
19
  ],
20
20
  "title": "ActionStep",
21
21
  "type": "object",
22
- "x-contract-version": "0.5.25"
22
+ "x-contract-version": "0.5.27"
23
23
  }
@@ -2,6 +2,18 @@
2
2
  "additionalProperties": false,
3
3
  "description": "Which rows of a policy's answer play. `key`: the answer's field that holds them (None: the answer itself) - a\nlist of rows or a 2-D array, played in order, `hz` rows a second, `open_loop_horizon` of them before the next act\n(None: all). A shorter answer plays whole and the next act comes sooner; a longer one is cut there. With `hz` a worker\nknows how long one answer plays (open_loop_horizon / hz) and a control period (1 / hz) and times a copy's robots by\nthem, as a robot-arm chunk's covers_seconds and control_frequency_hz do. Undeclared, an answer plays as the default\nsays: a dict or a 1-D list one action, a 2-D one row by row.",
4
4
  "properties": {
5
+ "aligned": {
6
+ "anyOf": [
7
+ {
8
+ "type": "boolean"
9
+ },
10
+ {
11
+ "type": "null"
12
+ }
13
+ ],
14
+ "default": null,
15
+ "title": "Aligned"
16
+ },
5
17
  "hz": {
6
18
  "anyOf": [
7
19
  {
@@ -47,5 +59,5 @@
47
59
  },
48
60
  "title": "Actions",
49
61
  "type": "object",
50
- "x-contract-version": "0.5.25"
62
+ "x-contract-version": "0.5.27"
51
63
  }
@@ -17,5 +17,5 @@
17
17
  ],
18
18
  "title": "AdapterPair",
19
19
  "type": "object",
20
- "x-contract-version": "0.5.25"
20
+ "x-contract-version": "0.5.27"
21
21
  }
@@ -23,5 +23,5 @@
23
23
  ],
24
24
  "title": "AddLocal",
25
25
  "type": "object",
26
- "x-contract-version": "0.5.25"
26
+ "x-contract-version": "0.5.27"
27
27
  }
@@ -57,5 +57,5 @@
57
57
  },
58
58
  "title": "Autoscaler",
59
59
  "type": "object",
60
- "x-contract-version": "0.5.25"
60
+ "x-contract-version": "0.5.27"
61
61
  }
@@ -50,5 +50,5 @@
50
50
  ],
51
51
  "title": "BenchmarkConditionSpec",
52
52
  "type": "object",
53
- "x-contract-version": "0.5.25"
53
+ "x-contract-version": "0.5.27"
54
54
  }
@@ -11,6 +11,18 @@
11
11
  "action_space": {
12
12
  "$ref": "#/$defs/ActionSpace"
13
13
  },
14
+ "aligned": {
15
+ "anyOf": [
16
+ {
17
+ "type": "boolean"
18
+ },
19
+ {
20
+ "type": "null"
21
+ }
22
+ ],
23
+ "default": null,
24
+ "title": "Aligned"
25
+ },
14
26
  "bounds": {
15
27
  "anyOf": [
16
28
  {
@@ -1077,5 +1089,5 @@
1077
1089
  ],
1078
1090
  "title": "BenchmarkRegisterRequest",
1079
1091
  "type": "object",
1080
- "x-contract-version": "0.5.25"
1092
+ "x-contract-version": "0.5.27"
1081
1093
  }
@@ -471,7 +471,7 @@
471
471
  ],
472
472
  "title": "SeqFlags",
473
473
  "type": "integer",
474
- "x-contract-version": "0.5.25",
474
+ "x-contract-version": "0.5.27",
475
475
  "x-flag-names": {
476
476
  "CHECK": 65536,
477
477
  "INFER": 16,
@@ -722,5 +722,5 @@
722
722
  ],
723
723
  "title": "BenchmarkSpec",
724
724
  "type": "object",
725
- "x-contract-version": "0.5.25"
725
+ "x-contract-version": "0.5.27"
726
726
  }
@@ -40,5 +40,5 @@
40
40
  ],
41
41
  "title": "BenchmarkStatusResponse",
42
42
  "type": "object",
43
- "x-contract-version": "0.5.25"
43
+ "x-contract-version": "0.5.27"
44
44
  }
@@ -79,5 +79,5 @@
79
79
  ],
80
80
  "title": "BootstrapResponse",
81
81
  "type": "object",
82
- "x-contract-version": "0.5.25"
82
+ "x-contract-version": "0.5.27"
83
83
  }
@@ -122,5 +122,5 @@
122
122
  ],
123
123
  "title": "Camera",
124
124
  "type": "object",
125
- "x-contract-version": "0.5.25"
125
+ "x-contract-version": "0.5.27"
126
126
  }
@@ -23,5 +23,5 @@
23
23
  ],
24
24
  "title": "CodeBundle",
25
25
  "type": "object",
26
- "x-contract-version": "0.5.25"
26
+ "x-contract-version": "0.5.27"
27
27
  }
@@ -96,5 +96,5 @@
96
96
  },
97
97
  "title": "Concurrency",
98
98
  "type": "object",
99
- "x-contract-version": "0.5.25"
99
+ "x-contract-version": "0.5.27"
100
100
  }
@@ -55,5 +55,5 @@
55
55
  ],
56
56
  "title": "Conditions",
57
57
  "type": "object",
58
- "x-contract-version": "0.5.25"
58
+ "x-contract-version": "0.5.27"
59
59
  }
@@ -25,5 +25,5 @@
25
25
  ],
26
26
  "title": "ConnectRequest",
27
27
  "type": "object",
28
- "x-contract-version": "0.5.25"
28
+ "x-contract-version": "0.5.27"
29
29
  }