mru-sdk 0.1.0a1__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.
- mru_sdk-0.1.0a1/LICENSE +202 -0
- mru_sdk-0.1.0a1/PKG-INFO +376 -0
- mru_sdk-0.1.0a1/README.md +354 -0
- mru_sdk-0.1.0a1/pyproject.toml +43 -0
- mru_sdk-0.1.0a1/setup.cfg +4 -0
- mru_sdk-0.1.0a1/src/mru/__init__.py +21 -0
- mru_sdk-0.1.0a1/src/mru/auth.py +39 -0
- mru_sdk-0.1.0a1/src/mru/client.py +281 -0
- mru_sdk-0.1.0a1/src/mru/errors.py +53 -0
- mru_sdk-0.1.0a1/src/mru/job.py +119 -0
- mru_sdk-0.1.0a1/src/mru/models.py +136 -0
- mru_sdk-0.1.0a1/src/mru/py.typed +0 -0
- mru_sdk-0.1.0a1/src/mru/session.py +197 -0
- mru_sdk-0.1.0a1/src/mru/source.py +61 -0
- mru_sdk-0.1.0a1/src/mru/testing.py +233 -0
- mru_sdk-0.1.0a1/src/mru/transport.py +52 -0
- mru_sdk-0.1.0a1/src/mru_sdk.egg-info/PKG-INFO +376 -0
- mru_sdk-0.1.0a1/src/mru_sdk.egg-info/SOURCES.txt +23 -0
- mru_sdk-0.1.0a1/src/mru_sdk.egg-info/dependency_links.txt +1 -0
- mru_sdk-0.1.0a1/src/mru_sdk.egg-info/top_level.txt +1 -0
- mru_sdk-0.1.0a1/tests/test_auth.py +124 -0
- mru_sdk-0.1.0a1/tests/test_contracts.py +105 -0
- mru_sdk-0.1.0a1/tests/test_examples.py +92 -0
- mru_sdk-0.1.0a1/tests/test_launch.py +260 -0
- mru_sdk-0.1.0a1/tests/test_sdk.py +278 -0
mru_sdk-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
mru_sdk-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mru-sdk
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Python job launch and hardware-independent robot sessions
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Repository, https://github.com/Druzrka/mru-python-sdk
|
|
7
|
+
Project-URL: Changelog, https://github.com/Druzrka/mru-python-sdk/blob/main/CHANGELOG.md
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# MRU Python SDK
|
|
24
|
+
|
|
25
|
+
Write robot jobs using observations and named actuators. The job owns its policy,
|
|
26
|
+
models, and checkpoints; the robot agent owns physical execution and hardware
|
|
27
|
+
drivers. Python 3.11+; no runtime dependencies.
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Python job → MRU SDK → job session protocol → robot agent → drivers → robot
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This repository implements a Python launch client, the runtime SDK, a Unix-socket transport, and an in-process
|
|
34
|
+
fake agent. It does **not** implement the platform scheduler, a production robot
|
|
35
|
+
agent, or hardware drivers. Observation and command traffic uses the job session
|
|
36
|
+
connection without going through a web backend.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
The package is published on PyPI as `mru-sdk` and imported as `mru`. Current
|
|
41
|
+
releases are pre-releases, so allow them explicitly:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
uv add mru-sdk --prerelease=allow
|
|
45
|
+
# or
|
|
46
|
+
pip install --pre mru-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`mru-sdk` is separate from the `mru-cli` command-line tool; installing both is fine.
|
|
50
|
+
|
|
51
|
+
## Launch from Python
|
|
52
|
+
|
|
53
|
+
**The launch client targets a proposed API that still needs backend implementation.**
|
|
54
|
+
It is tested against a local HTTP fixture. It does not use the existing MRU
|
|
55
|
+
deployment API or CLI configuration, and it has no default production URL.
|
|
56
|
+
The concrete backend handoff is in [docs/launch-api.md](docs/launch-api.md).
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from mru import Client
|
|
60
|
+
|
|
61
|
+
client = Client.from_env() # MRU_API_URL and MRU_API_TOKEN
|
|
62
|
+
job = client.launch(
|
|
63
|
+
"job.py",
|
|
64
|
+
robot_id="robot_123",
|
|
65
|
+
dependencies=["numpy==2.1.0"],
|
|
66
|
+
params={"target": 0.2},
|
|
67
|
+
)
|
|
68
|
+
print(job.id)
|
|
69
|
+
job.wait(timeout=300)
|
|
70
|
+
print(job.status)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`job.py` is uploaded and executed on MRU. It can use the `Job`/`run` interface
|
|
74
|
+
shown below. Only that file is included by default; add explicit supporting
|
|
75
|
+
files with `files=["policy.py", "assets/settings.json"]`. Paths are relative to
|
|
76
|
+
`source_dir`, which defaults to the entry point's parent. Model weights and large
|
|
77
|
+
datasets are separate from this source-only upload (8 MiB, 128 files maximum).
|
|
78
|
+
|
|
79
|
+
The launcher does not import your code or install dependencies locally. It returns
|
|
80
|
+
after the backend accepts the job; the remote job can continue after the submitting
|
|
81
|
+
process exits. `client.get_job(job_id)` reconnects, `job.refresh()` retrieves status,
|
|
82
|
+
`job.logs(after=cursor)` fetches a `LogPage`, and `job.cancel()` requests cancellation.
|
|
83
|
+
`job.status` is cached until refreshed; `job.details` holds the full job record from
|
|
84
|
+
the last response. `wait()` returns for completed, failed, or cancelled jobs; callers
|
|
85
|
+
inspect `status` and `details["error"]`. Timing out while waiting leaves the remote
|
|
86
|
+
job running.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
cursor = 0
|
|
90
|
+
while True:
|
|
91
|
+
page = job.logs(after=cursor)
|
|
92
|
+
for entry in page.entries:
|
|
93
|
+
print(entry.stream, entry.message)
|
|
94
|
+
cursor = page.cursor
|
|
95
|
+
if not page.has_more:
|
|
96
|
+
break
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`Client(api_url=..., token=...)` accepts explicit account credentials. `client.me()`
|
|
100
|
+
checks them with the backend. Runtime credentials (`Auth` / `MRU_JOB_TOKEN`) are
|
|
101
|
+
separate and are supplied by the platform to the launched job. Nothing reads or
|
|
102
|
+
writes a saved CLI login.
|
|
103
|
+
|
|
104
|
+
Launch requests carry an idempotency key. Pass `request_id` to choose it; inspect
|
|
105
|
+
`job.request_id` after success or `LaunchError.request_id` if the outcome is unknown.
|
|
106
|
+
There is no automatic launch retry. Reuse the same key and unchanged files/options
|
|
107
|
+
after a lost response so the backend can return the original job.
|
|
108
|
+
|
|
109
|
+
See [examples/launch_job.py](examples/launch_job.py) for a submission script and
|
|
110
|
+
[examples/manage_job.py](examples/manage_job.py) to reconnect by job ID, follow
|
|
111
|
+
logs, and cancel.
|
|
112
|
+
|
|
113
|
+
## Run locally
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
python3 -m venv .venv
|
|
117
|
+
source .venv/bin/activate
|
|
118
|
+
python -m pip install -e .
|
|
119
|
+
python examples/local_job.py
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Alternatively, without installing: `PYTHONPATH=src python3 examples/local_job.py`.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from mru import ActuatorRequirement, Job, Requirements, run
|
|
126
|
+
from mru.testing import FakeRobot
|
|
127
|
+
|
|
128
|
+
async def main(job: Job):
|
|
129
|
+
async for observation in job.observations():
|
|
130
|
+
# Your model can compute these positions. The SDK does not load models.
|
|
131
|
+
await job.actuators["left_arm"].move(
|
|
132
|
+
positions=(0.2, -0.2),
|
|
133
|
+
duration_ms=100,
|
|
134
|
+
observation_id=observation.id,
|
|
135
|
+
)
|
|
136
|
+
await job.complete()
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
requirements = Requirements(
|
|
140
|
+
sensors={"left_arm": "joint_state"},
|
|
141
|
+
actuators={"left_arm": ActuatorRequirement(
|
|
142
|
+
unit="rad", joints=("shoulder", "elbow"),
|
|
143
|
+
)},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
robot = FakeRobot(params={"task": "reach"})
|
|
147
|
+
run(main, session=robot.session(), requirements=requirements)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For a platform-managed job, use `run(main, requirements=requirements)`. The
|
|
151
|
+
platform supplies `MRU_SESSION_SOCKET`, a Unix socket connected to the assigned
|
|
152
|
+
agent or a runtime-side relay, and `MRU_JOB_TOKEN`, a job credential. Socket access control and the robot assignment
|
|
153
|
+
belong to the platform. A remote deployment needs a relay or an implementation
|
|
154
|
+
of `mru.transport.Transport` with authenticated, encrypted networking; no remote transport
|
|
155
|
+
is included. In an existing event loop, use
|
|
156
|
+
`await run_async(...)`.
|
|
157
|
+
|
|
158
|
+
## Runtime session authentication
|
|
159
|
+
|
|
160
|
+
Supply a platform-issued job token explicitly:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from mru import Auth, run
|
|
164
|
+
|
|
165
|
+
auth = Auth.from_token(job_token) # Obtained from the platform, not a password
|
|
166
|
+
run(main, auth=auth)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Or read the credential supplied to the runtime:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
run(main, auth=Auth.from_env()) # Reads MRU_JOB_TOKEN
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When using the built-in platform connection, `run(main)` and `run_async(main)`
|
|
176
|
+
load `MRU_JOB_TOKEN` automatically and fail before connecting if it is missing or
|
|
177
|
+
malformed. An explicit `auth` argument takes precedence over the environment.
|
|
178
|
+
With a supplied session, credentials are passed explicitly; no environment token
|
|
179
|
+
is read. Lower-level callers use `await session.open(requirements, auth=auth)`.
|
|
180
|
+
|
|
181
|
+
The SDK sends the bearer credential only in the session handshake, separately
|
|
182
|
+
from job parameters and actuator commands. `Auth` hides the token in its ordinary
|
|
183
|
+
string representation; it is still sensitive data in memory and on the wire.
|
|
184
|
+
Never log or record handshake payloads. The SDK does not persist credentials.
|
|
185
|
+
|
|
186
|
+
`Auth.from_token()` constructs a credential; successful authentication occurs
|
|
187
|
+
when the agent accepts the handshake. The agent must validate the token, its
|
|
188
|
+
expiry/revocation, and its job/robot/action permissions before exposing data or
|
|
189
|
+
granting a lease. Missing or invalid credentials produce `AuthenticationError`;
|
|
190
|
+
insufficient permissions produce `AuthorizationError`. These are also subclasses
|
|
191
|
+
of `CommandRejected`.
|
|
192
|
+
|
|
193
|
+
**User registration, interactive login, token issuance/refresh, and production
|
|
194
|
+
agent verification are not implemented in this repository.** These require the
|
|
195
|
+
MRU platform service. Supplying a token does not by itself establish that a user
|
|
196
|
+
is registered or authorized. The SDK transports the credential; the agent must
|
|
197
|
+
enforce access even for callers that bypass the SDK.
|
|
198
|
+
|
|
199
|
+
For a local authentication test:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
robot = FakeRobot(required_token="local-test-token")
|
|
203
|
+
run(main, session=robot.session(), auth=Auth.from_token("local-test-token"))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The fake compares the supplied token before granting access; it does not implement
|
|
207
|
+
identity, scopes, expiration, or token issuance. `FakeRobot()` without
|
|
208
|
+
`required_token` remains available without login for local simulation.
|
|
209
|
+
|
|
210
|
+
## Author interface
|
|
211
|
+
|
|
212
|
+
| Piece | Interface |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| Launch client | `Client.from_env()`, `client.launch("job.py", robot_id=...)` |
|
|
215
|
+
| Remote job | `job.refresh()`, `job.wait()`, `job.logs()` → `LogPage`, `job.cancel()` |
|
|
216
|
+
| Job | `job.params`, `await job.complete()`, `await job.fail(reason)` |
|
|
217
|
+
| Observations | `async for observation in job.observations()` |
|
|
218
|
+
| Actuators | `job.actuators[name].spec`, `await handle.move(...)` |
|
|
219
|
+
| Session | `Session(transport, timeout=10, observation_buffer=16)` |
|
|
220
|
+
| Authentication | `Auth.from_token(token)`, `Auth.from_env()`, `run(main, auth=auth)` |
|
|
221
|
+
| Testing | `FakeRobot`, `load_recording`, `save_recording` |
|
|
222
|
+
|
|
223
|
+
Observations have a session-unique ID, an agent capture timestamp in Unix epoch
|
|
224
|
+
nanoseconds, named joint states, encoded images, and optional scalar readings.
|
|
225
|
+
Joint states declare joint order and units. Images contain encoded bytes and a
|
|
226
|
+
MIME encoding such as `image/jpeg`; the job handles decoding and preprocessing.
|
|
227
|
+
An observation timestamp describes the frame; this version does not represent
|
|
228
|
+
separate capture times for asynchronously sampled sensors. Agents must group
|
|
229
|
+
samples accordingly.
|
|
230
|
+
|
|
231
|
+
`move()` supports absolute joint positions only. Values follow `handle.spec.joints`
|
|
232
|
+
and use `handle.spec.unit` (for example, `rad` or `m`), without implicit unit
|
|
233
|
+
conversion. The message includes the command type and unit. The SDK validates
|
|
234
|
+
shape, finite values, and advertised limits; the agent must validate them again.
|
|
235
|
+
`duration_ms` is a positive integer. Every move needs an observation ID issued
|
|
236
|
+
in the same session. The agent decides whether that observation is fresh enough.
|
|
237
|
+
|
|
238
|
+
`move()` returns an `ExecutionAck` only when the agent reports execution finished,
|
|
239
|
+
not when the command is merely received or queued. Rejections raise
|
|
240
|
+
`CommandRejected` with a machine-readable `.code`. A timeout covers sending and
|
|
241
|
+
waiting for the acknowledgement, defaults to the session timeout, and can be
|
|
242
|
+
overridden per move. Allow enough time for execution and transport latency.
|
|
243
|
+
`DeadlineExceeded` means the outcome is uncertain: the SDK ends the session and
|
|
244
|
+
requests a stop. It never automatically retries physical commands.
|
|
245
|
+
|
|
246
|
+
There is one observation consumer per session. The bounded buffer drops the
|
|
247
|
+
oldest frames if the job falls behind, keeping recent data. This stream is not
|
|
248
|
+
a lossless recording facility. Finite recording exhaustion ends iteration;
|
|
249
|
+
returning from the job completes it.
|
|
250
|
+
|
|
251
|
+
## Lifecycle and cancellation
|
|
252
|
+
|
|
253
|
+
The session handshake checks requirements before the job callback runs. The
|
|
254
|
+
platform should also perform the same check before launching the runtime.
|
|
255
|
+
The agent grants at most one active job per robot and holds its lease until
|
|
256
|
+
connection teardown. Completion and failure stop execution. Returning normally
|
|
257
|
+
from `main` completes the job; an exception reports failure and propagates.
|
|
258
|
+
|
|
259
|
+
Platform cancellation interrupts SDK waits with `JobCancelled`, requests a stop,
|
|
260
|
+
and makes `run_async` cancel the job callback. Cancelling a command or observation
|
|
261
|
+
wait also ends the session and requests a stop. Teardown waits at most one second
|
|
262
|
+
(or the shorter session timeout) to send the best-effort stop. The SDK cannot
|
|
263
|
+
guarantee delivery over a broken connection or establish a physical safety state.
|
|
264
|
+
The agent must independently stop on connection loss, enforce its own watchdogs
|
|
265
|
+
and limits, and reject commands from revoked sessions. Cooperative asyncio
|
|
266
|
+
cancellation requires the job to yield; blocking inference should run outside
|
|
267
|
+
the event loop.
|
|
268
|
+
|
|
269
|
+
`job.logger` is Python's `mru.job` logger. Configure standard Python logging in
|
|
270
|
+
the job (for example, `logging.basicConfig(level=logging.INFO)`). The platform
|
|
271
|
+
collects process stdout/stderr and session lifecycle events. Logs need not travel
|
|
272
|
+
through the robot data connection.
|
|
273
|
+
|
|
274
|
+
## Keep configuration ownership separate
|
|
275
|
+
|
|
276
|
+
**Robot operator configuration** defines sensor names/types, actuator groups,
|
|
277
|
+
joint order, units, supported commands, limits, hardware mappings, and freshness
|
|
278
|
+
and watchdog policies. The handshake advertises capabilities, not driver details.
|
|
279
|
+
`RobotCapabilities` describes this advertised subset; it is not a hardware
|
|
280
|
+
configuration loader.
|
|
281
|
+
|
|
282
|
+
**Job author configuration** defines the Python entry point, dependencies,
|
|
283
|
+
`Requirements`, and task parameters. The platform installs dependencies and runs
|
|
284
|
+
the entry point, then supplies assigned parameters through the handshake as
|
|
285
|
+
`job.params`. Model settings and checkpoint locations are ordinary job-owned
|
|
286
|
+
parameters; the SDK neither interprets nor downloads them. This repository
|
|
287
|
+
uses the proposed launch manifest described in [the launch API contract](docs/launch-api.md).
|
|
288
|
+
|
|
289
|
+
## Fake robot and recordings
|
|
290
|
+
|
|
291
|
+
`FakeRobot()` emits joint feedback for a two-joint `left_arm`. It simulates
|
|
292
|
+
command duration, execution acknowledgements, capability checks, stale-data
|
|
293
|
+
rejection, exclusive access, and stop behavior. It is a protocol test double,
|
|
294
|
+
not a physics simulator or proof of hardware safety. Only one command per
|
|
295
|
+
actuator may execute at a time; concurrent commands to that actuator are rejected.
|
|
296
|
+
|
|
297
|
+
Use `FakeRobot(capabilities=..., observations=..., params=...)` for another setup.
|
|
298
|
+
Recorded frames are emitted every `interval` seconds (default 0.02), preserving
|
|
299
|
+
their IDs and timestamps. IDs must be unique. Replay freshness uses local emission
|
|
300
|
+
time, allowing old recordings to be used; it does not compare recorded wall-clock
|
|
301
|
+
timestamps to the current clock. The fake retains the latest 256 observation IDs
|
|
302
|
+
and defaults to a 0.5-second freshness window. Generated feedback updates after
|
|
303
|
+
execution; replayed observations remain as recorded.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from mru.testing import FakeRobot, load_recording, save_recording
|
|
307
|
+
|
|
308
|
+
save_recording("observations.jsonl", observations)
|
|
309
|
+
robot = FakeRobot(observations=load_recording("observations.jsonl"))
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`await robot.cancel(reason)` simulates platform cancellation, and
|
|
313
|
+
`await robot.disconnect()` simulates connection loss. Inspect `robot.commands`
|
|
314
|
+
for completed executions and `robot.status` for lifecycle state.
|
|
315
|
+
|
|
316
|
+
## Examples
|
|
317
|
+
|
|
318
|
+
| Example | Shows | Needs a backend |
|
|
319
|
+
| --- | --- | --- |
|
|
320
|
+
| [local_job.py](examples/local_job.py) | A minimal job against the fake robot | No |
|
|
321
|
+
| [replay_recording.py](examples/replay_recording.py) | Saving observations and replaying them through `FakeRobot` | No |
|
|
322
|
+
| [local_cancellation.py](examples/local_cancellation.py) | How a job sees platform cancellation (`JobCancelled`) | No |
|
|
323
|
+
| [launch_job.py](examples/launch_job.py) | Uploading source and launching a job | Yes |
|
|
324
|
+
| [manage_job.py](examples/manage_job.py) | Reconnecting by job ID, following logs, cancelling | Yes |
|
|
325
|
+
|
|
326
|
+
The test suite runs every example; the backend-dependent ones run against a
|
|
327
|
+
local HTTP fixture.
|
|
328
|
+
|
|
329
|
+
## Known V1 limits
|
|
330
|
+
|
|
331
|
+
- **One active job per robot.** The agent grants one lease at a time; a second
|
|
332
|
+
job waits in the platform queue or is rejected with `robot_busy`.
|
|
333
|
+
- **Absolute joint positions only.** `move()` sends `joint_position` commands in
|
|
334
|
+
the actuator's declared joint order and unit. No velocity, torque, Cartesian,
|
|
335
|
+
gripper-specific, or trajectory commands; no unit conversion.
|
|
336
|
+
- **Joint states, encoded images, and scalar readings only.** Images arrive as
|
|
337
|
+
encoded bytes (for example JPEG); the job decodes them. Each observation has one
|
|
338
|
+
capture timestamp; there are no per-sensor timestamps.
|
|
339
|
+
- **One observation consumer per session**, with a bounded, lossy buffer that keeps
|
|
340
|
+
the most recent frames. It is not a recording facility.
|
|
341
|
+
- **One command at a time per actuator.** Concurrent moves on the same actuator are
|
|
342
|
+
rejected, not queued.
|
|
343
|
+
- **No automatic retries.** Neither launches nor physical commands are retried
|
|
344
|
+
when the outcome is uncertain.
|
|
345
|
+
- **Unix-socket transport only.** Remote connections need a platform relay or a
|
|
346
|
+
custom `Transport` with authenticated, encrypted networking.
|
|
347
|
+
- **Source-only uploads:** at most 128 files and 8 MiB. Model weights and datasets
|
|
348
|
+
are fetched by the job itself.
|
|
349
|
+
- **The launch client needs a backend** implementing [mru.python-job/v1](docs/launch-api.md);
|
|
350
|
+
none is deployed yet.
|
|
351
|
+
- **`FakeRobot` is a behavioral test double**, not a physics or safety simulator.
|
|
352
|
+
|
|
353
|
+
## Versioning
|
|
354
|
+
|
|
355
|
+
The package uses semantic versioning; the launch API and job session protocol
|
|
356
|
+
are versioned separately. See [compatibility and versioning](docs/compatibility.md)
|
|
357
|
+
and the [changelog](CHANGELOG.md). The package ships type information (`py.typed`).
|
|
358
|
+
Shared launch-contract fixtures for backend tests are in [contracts/launch-v1](contracts/launch-v1/README.md).
|
|
359
|
+
Maintainers: see [releasing](docs/releasing.md).
|
|
360
|
+
|
|
361
|
+
## Development
|
|
362
|
+
|
|
363
|
+
Use Python 3.11 or newer:
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
PYTHONPATH=src python3.12 -m unittest discover -s tests -v
|
|
367
|
+
uvx mypy # or: pip install mypy && mypy; strict settings are in pyproject.toml
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The suite includes a real local Unix-socket round trip and requires permission
|
|
371
|
+
to bind a temporary socket. See [the protocol contract](docs/protocol.md) to
|
|
372
|
+
implement an agent or a transport.
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|