akernel-sdk 0.1.0__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.
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.4
2
+ Name: akernel-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK and CLI for AKernel remote sandboxes
5
+ Author: AKernel Authors
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/akernel-dev/akernel
8
+ Project-URL: Documentation, https://github.com/akernel-dev/akernel/tree/main/sdk/python
9
+ Project-URL: Repository, https://github.com/akernel-dev/akernel.git
10
+ Project-URL: Issues, https://github.com/akernel-dev/akernel/issues
11
+ Keywords: ai-agents,sandbox,gvisor,remote-execution
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Distributed Computing
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: openyuanrong-sdk==0.7.51
26
+ Requires-Dist: websockets>=10.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build<2,>=1.2; extra == "dev"
29
+ Requires-Dist: mypy<2,>=1.10; extra == "dev"
30
+ Requires-Dist: ruff<1,>=0.11; extra == "dev"
31
+
32
+ # AKernel Python SDK
33
+
34
+ `akernel-sdk` is the Python interface for creating and managing remote AKernel sandboxes. Applications use one stable API for commands, files, interactive PTYs, port forwarding, and reverse tunnels. The current implementation uses `openYuanrong` as its backend adapter; backend-specific handles and namespaces are not part of the public API.
35
+
36
+ ## Navigation
37
+
38
+ - [Install and configure](#install-and-configure)
39
+ - [Create a sandbox](#create-a-sandbox)
40
+ - [Commands](#commands)
41
+ - [Filesystem](#filesystem)
42
+ - [Interactive PTYs](#interactive-ptys)
43
+ - [Port forwarding](#port-forwarding)
44
+ - [Reverse tunnels](#reverse-tunnels)
45
+ - [Rootfs and mounts](#rootfs-and-mounts)
46
+ - [Resources and lifecycle](#resources-and-lifecycle)
47
+ - [CLI](#cli)
48
+ - [Examples and tests](#examples-and-tests)
49
+
50
+ ## Install and configure
51
+
52
+ AKernel SDK 0.1.0 requires Python 3.10 or newer.
53
+
54
+ ```bash
55
+ pip install akernel-sdk
56
+ ```
57
+
58
+ To install from a source checkout:
59
+
60
+ ```bash
61
+ python -m pip install ./sdk/python
62
+ ```
63
+
64
+ Configure the public AKernel entrypoint and a signed JWT token:
65
+
66
+ ```bash
67
+ export AKERNEL_SERVER_ADDRESS="akernel.example.com"
68
+ export AKERNEL_TOKEN="<token>"
69
+ ```
70
+
71
+ Address behavior is deterministic:
72
+
73
+ - A host or IP without a port uses HTTPS/WSS on 443 for the frontend and HTTP
74
+ on 80 for public sandbox port URLs.
75
+ - `host:port` uses that port as a shared HTTPS/WSS endpoint.
76
+ - `AKERNEL_GATEWAY_ADDRESS` overrides the port-forwarding and exec gateway for
77
+ standalone or custom topologies. An override without a scheme uses HTTP/WS.
78
+
79
+ The standalone launcher prints the Traefik container IP to use as
80
+ `AKERNEL_SERVER_ADDRESS`.
81
+
82
+ ## Create a sandbox
83
+
84
+ ```python
85
+ from akernel_sdk import Sandbox
86
+
87
+ with Sandbox(cpu=1000, memory=2048) as sandbox:
88
+ result = sandbox.commands.run("printf hello")
89
+ print(result.stdout)
90
+ ```
91
+
92
+ The constructor accepts:
93
+
94
+ ```python
95
+ Sandbox(
96
+ image: str | None = None,
97
+ rootfs: S3Config | None = None,
98
+ runtime: str = "runsc",
99
+ cpu: int = 1000,
100
+ memory: int = 4096,
101
+ cpu_limit: int = 0,
102
+ mem_limit: int = 0,
103
+ idle_timeout: int = 300,
104
+ schedule_timeout: int = 30,
105
+ env: dict[str, str] | None = None,
106
+ name: str | None = None,
107
+ cwd: str | None = None,
108
+ port_forwardings: list[int] | None = None,
109
+ mounts: list[Mount] | None = None,
110
+ reverse_tunnel: HttpReverseTunnel | None = None,
111
+ detached: bool = False,
112
+ node_id: str | None = None,
113
+ )
114
+ ```
115
+
116
+ `cpu` is measured in millicores and `memory` in MiB. A zero CPU or memory
117
+ limit means the limit follows the corresponding request. A positive limit
118
+ must not be smaller than its request.
119
+
120
+ AKernel 0.1.0 supports the gVisor `runsc` runtime. The independent `runtime`
121
+ parameter leaves room for future Kata support without changing the rootfs API.
122
+
123
+ ## Commands
124
+
125
+ Run a foreground command:
126
+
127
+ ```python
128
+ result = sandbox.commands.run(
129
+ "printf $GREETING",
130
+ envs={"GREETING": "hello"},
131
+ cwd="/tmp",
132
+ timeout=60,
133
+ )
134
+ print(result.stdout, result.stderr, result.exit_code)
135
+ ```
136
+
137
+ Run and control a background command:
138
+
139
+ ```python
140
+ handle = sandbox.commands.run("sleep 30", background=True)
141
+ print(handle.pid)
142
+
143
+ for process in sandbox.commands.list():
144
+ print(process.pid, process.command, process.running)
145
+
146
+ handle.kill()
147
+ ```
148
+
149
+ Enable stdin only when it is needed:
150
+
151
+ ```python
152
+ handle = sandbox.commands.run("wc -l", background=True, stdin=True)
153
+ handle.send_stdin("one\ntwo\n")
154
+ handle.close_stdin()
155
+ result = handle.wait(timeout=15)
156
+ ```
157
+
158
+ Foreground commands use one actor RPC with the configured timeout. Background commands return a handle whose `wait()` method also performs one actor RPC.
159
+
160
+ ## Filesystem
161
+
162
+ ```python
163
+ sandbox.files.write("/tmp/message.txt", "hello")
164
+ print(sandbox.files.read("/tmp/message.txt"))
165
+
166
+ sandbox.files.write("/tmp/data.bin", b"\x00\x01")
167
+ print(sandbox.files.read("/tmp/data.bin", format="bytes"))
168
+
169
+ for entry in sandbox.files.list("/tmp"):
170
+ print(entry.path, entry.type, entry.size)
171
+
172
+ sandbox.files.make_dir("/workspace")
173
+ sandbox.files.rename("/tmp/message.txt", "/workspace/message.txt")
174
+ sandbox.files.remove("/workspace/message.txt")
175
+ ```
176
+
177
+ Copy local files or directories through the frontend exec WebSocket:
178
+
179
+ ```python
180
+ sandbox.files.copy_from_local("./project", "/workspace/project")
181
+ sandbox.files.copy_to_local("/workspace/result.json", "./result.json")
182
+ ```
183
+
184
+ ## Interactive PTYs
185
+
186
+ Use `sandbox.pty` for an interactive byte stream with stdin, streaming output, terminal resizing, and an exit status:
187
+
188
+ ```python
189
+ import sys
190
+
191
+ from akernel_sdk import Sandbox
192
+
193
+
194
+ def write_output(data: bytes) -> None:
195
+ sys.stdout.buffer.write(data)
196
+ sys.stdout.buffer.flush()
197
+
198
+
199
+ with Sandbox() as sandbox:
200
+ with sandbox.pty.create(on_data=write_output) as session:
201
+ session.send_stdin(b"echo hello from PTY\n")
202
+ session.resize(rows=40, cols=120)
203
+ session.send_stdin(b"exit 7\n")
204
+ print(session.wait())
205
+ ```
206
+
207
+ PTY output remains bytes so the SDK does not guess the terminal encoding. Use `session.close_stdin()` to signal end-of-input while continuing to receive output. A session belongs to its WebSocket connection: closing it terminates the remote interactive process, and reconnecting to an existing session is not supported.
208
+
209
+ Use `sandbox.commands` instead when the caller needs separate stdout and stderr, a complete `CommandResult`, or a controllable background process. The former `Shell` API and its actor `bash_*` methods were removed before the v0.1.0 public API was released.
210
+
211
+ ## Port forwarding
212
+
213
+ Declare each sandbox port at creation time:
214
+
215
+ ```python
216
+ from akernel_sdk import Sandbox
217
+
218
+ with Sandbox(port_forwardings=[8080]) as sandbox:
219
+ server = sandbox.commands.run(
220
+ "python3 -m http.server 8080 --bind 0.0.0.0",
221
+ background=True,
222
+ )
223
+ print(sandbox.get_port_url(8080))
224
+ server.kill()
225
+ ```
226
+
227
+ `get_port_url()` rejects undeclared ports. Pass `internal=True` only when a
228
+ deployment operator explicitly wants the direct Traefik address instead of the
229
+ public gateway.
230
+
231
+ ## Reverse tunnels
232
+
233
+ A reverse tunnel lets sandbox code call an HTTP or HTTPS service reachable
234
+ from the machine running the SDK:
235
+
236
+ ```python
237
+ from akernel_sdk import HttpReverseTunnel, Sandbox
238
+
239
+ tunnel = HttpReverseTunnel(
240
+ target="https://service.example.com",
241
+ reverse_port=8765,
242
+ listen_port=8766,
243
+ connect_timeout=60,
244
+ )
245
+
246
+ with Sandbox(reverse_tunnel=tunnel) as sandbox:
247
+ result = sandbox.commands.run(
248
+ f"curl {sandbox.reverse_tunnel.url}/health"
249
+ )
250
+ ```
251
+
252
+ `reverse_port` carries the WebSocket tunnel through Traefik. `listen_port` is
253
+ the loopback HTTP listener used inside the sandbox. Consequently,
254
+ `sandbox.reverse_tunnel.url` is always
255
+ `http://127.0.0.1:<listen_port>`, even when `target` uses HTTPS.
256
+
257
+ For an HTTPS target, the SDK-side tunnel client performs the TLS handshake and
258
+ certificate verification. The sandbox application talks only to its loopback
259
+ HTTP listener. AKernel 0.1.0 supports one HTTP/WebSocket reverse tunnel per
260
+ sandbox; it does not expose a general TCP tunnel.
261
+
262
+ ## Rootfs and mounts
263
+
264
+ Use a public OCI image:
265
+
266
+ ```python
267
+ with Sandbox(image="ubuntu:24.04") as sandbox:
268
+ print(sandbox.commands.run("cat /etc/os-release").stdout)
269
+ ```
270
+
271
+ Or use an object in S3-compatible storage as the rootfs:
272
+
273
+ ```python
274
+ from akernel_sdk import S3Config, Sandbox
275
+
276
+ rootfs = S3Config(
277
+ endpoint="https://s3.example.com",
278
+ bucket="akernel-rootfs",
279
+ object="ubuntu-24.04/rootfs.img",
280
+ access_key="<optional>",
281
+ secret_key="<optional>",
282
+ )
283
+
284
+ with Sandbox(rootfs=rootfs) as sandbox:
285
+ print(sandbox.commands.run("cat /etc/os-release").stdout)
286
+ ```
287
+
288
+ `image` and `rootfs` are mutually exclusive. The SDK generates the backend
289
+ wire representation; callers do not pass raw rootfs JSON or override the
290
+ runtime inside an S3 object.
291
+
292
+ The same `S3Config` type can be used as a read-only mount source:
293
+
294
+ ```python
295
+ from akernel_sdk import Mount
296
+
297
+ mount = Mount(target="/models", type="erofs", s3_config=rootfs)
298
+ with Sandbox(mounts=[mount]) as sandbox:
299
+ print(sandbox.commands.run("ls /models").stdout)
300
+ ```
301
+
302
+ OCI images can also be mounted read-only:
303
+
304
+ ```python
305
+ mount = Mount(target="/opt/tools", image_url="ubuntu:24.04")
306
+ ```
307
+
308
+ ## Resources and lifecycle
309
+
310
+ `resources()` returns stable `NodeInfo` values rather than backend objects:
311
+
312
+ ```python
313
+ from akernel_sdk import resources
314
+
315
+ for node in resources():
316
+ print(node.id, node.status, node.capacity, node.allocatable, node.labels)
317
+ ```
318
+
319
+ Use the context manager for ordinary sandboxes. For a named detached sandbox,
320
+ explicitly delete it when it is no longer needed:
321
+
322
+ ```python
323
+ sandbox = Sandbox(name="worker", detached=True)
324
+ sandbox.kill() # closes local clients; remote sandbox remains
325
+ Sandbox.delete("worker") # terminates the named remote sandbox
326
+ ```
327
+
328
+ `sandbox.id` is the physical ID shown by `ak list`. `get_info()` returns a
329
+ `SandboxInfo` containing `id`, state, requested CPU and memory, and the OCI
330
+ image when one was configured.
331
+
332
+ ## CLI
333
+
334
+ The `ak` CLI is installed with the SDK package:
335
+
336
+ ```bash
337
+ ak resources
338
+ ak list
339
+ ak list --quiet
340
+ ak exec <sandbox-id>
341
+ ak exec <sandbox-id> -- /bin/sh
342
+ ak delete <sandbox-id> [<sandbox-id> ...]
343
+ ```
344
+
345
+ It uses the same `AKERNEL_SERVER_ADDRESS` and `AKERNEL_TOKEN` environment as
346
+ the Python API.
347
+
348
+ ## Examples and tests
349
+
350
+ Maintained examples are under [`examples/`](./examples):
351
+
352
+ - `basic_usage.py`
353
+ - `command_stdin.py`
354
+ - `custom_image.py`
355
+ - `named_sandbox.py`
356
+ - `pty.py`
357
+ - `port_forwarding.py`
358
+ - `reverse_tunnel.py`
359
+ - `s3_rootfs_and_mounts.py`
360
+
361
+ Run unit tests without a deployment:
362
+
363
+ ```bash
364
+ PYTHONPATH=sdk/python \
365
+ python -m unittest discover -s sdk/python/tests/unit -t sdk/python -v
366
+ ```
367
+
368
+ Run the integration suite against a configured deployment:
369
+
370
+ ```bash
371
+ export AKERNEL_RUN_INTEGRATION=1
372
+ PYTHONPATH=sdk/python \
373
+ python -m unittest discover -s sdk/python/tests/integration -t sdk/python -v
374
+ ```
375
+
376
+ Load and transfer benchmarks live under [`benchmarks/`](./benchmarks) and are
377
+ not part of the default test suite.
378
+
379
+ ## Public value types
380
+
381
+ | Type | Fields |
382
+ |---|---|
383
+ | `CommandResult` | `stdout`, `stderr`, `exit_code` |
384
+ | `CommandInfo` | `pid`, `command`, `running` |
385
+ | `EntryInfo` | `name`, `path`, `type`, `size`, `permissions`, `modified_time` |
386
+ | `SandboxInfo` | `id`, `state`, `cpu`, `memory`, `image` |
387
+ | `NodeInfo` | `id`, `status`, `capacity`, `allocatable`, `labels` |
388
+ | `S3Config` | `endpoint`, `bucket`, `object`, optional credentials |
389
+ | `Mount` | `target`, one source, and `type` |
390
+ | `HttpReverseTunnel` | `target`, `reverse_port`, `listen_port`, `connect_timeout` |