nullspace-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.
Files changed (72) hide show
  1. nullspace_sdk-0.1.0/.gitignore +61 -0
  2. nullspace_sdk-0.1.0/CHANGELOG.md +72 -0
  3. nullspace_sdk-0.1.0/PKG-INFO +416 -0
  4. nullspace_sdk-0.1.0/README.md +376 -0
  5. nullspace_sdk-0.1.0/pyproject.toml +80 -0
  6. nullspace_sdk-0.1.0/src/nullspace/__init__.py +354 -0
  7. nullspace_sdk-0.1.0/src/nullspace/_cli_schema.py +230 -0
  8. nullspace_sdk-0.1.0/src/nullspace/_config.py +90 -0
  9. nullspace_sdk-0.1.0/src/nullspace/_exit_codes.py +108 -0
  10. nullspace_sdk-0.1.0/src/nullspace/_fields.py +16 -0
  11. nullspace_sdk-0.1.0/src/nullspace/_filesystem_core.py +4553 -0
  12. nullspace_sdk-0.1.0/src/nullspace/_mcp_server.py +215 -0
  13. nullspace_sdk-0.1.0/src/nullspace/cli.py +6339 -0
  14. nullspace_sdk-0.1.0/src/nullspace/client.py +1184 -0
  15. nullspace_sdk-0.1.0/src/nullspace/code_interpreter.py +583 -0
  16. nullspace_sdk-0.1.0/src/nullspace/commands.py +393 -0
  17. nullspace_sdk-0.1.0/src/nullspace/desktop.py +572 -0
  18. nullspace_sdk-0.1.0/src/nullspace/errors.py +300 -0
  19. nullspace_sdk-0.1.0/src/nullspace/files.py +568 -0
  20. nullspace_sdk-0.1.0/src/nullspace/git.py +1034 -0
  21. nullspace_sdk-0.1.0/src/nullspace/lifecycle.py +1048 -0
  22. nullspace_sdk-0.1.0/src/nullspace/monitor.py +268 -0
  23. nullspace_sdk-0.1.0/src/nullspace/process.py +37 -0
  24. nullspace_sdk-0.1.0/src/nullspace/pty.py +483 -0
  25. nullspace_sdk-0.1.0/src/nullspace/sandbox.py +1479 -0
  26. nullspace_sdk-0.1.0/src/nullspace/snapshot.py +109 -0
  27. nullspace_sdk-0.1.0/src/nullspace/template.py +3702 -0
  28. nullspace_sdk-0.1.0/src/nullspace/types.py +1936 -0
  29. nullspace_sdk-0.1.0/src/nullspace/volume.py +419 -0
  30. nullspace_sdk-0.1.0/src/nullspace/volume_files.py +936 -0
  31. nullspace_sdk-0.1.0/tests/__init__.py +0 -0
  32. nullspace_sdk-0.1.0/tests/conftest.py +116 -0
  33. nullspace_sdk-0.1.0/tests/integration_helpers.py +1176 -0
  34. nullspace_sdk-0.1.0/tests/test_chart_extractor.py +46 -0
  35. nullspace_sdk-0.1.0/tests/test_cli.py +5422 -0
  36. nullspace_sdk-0.1.0/tests/test_cli_docs.py +143 -0
  37. nullspace_sdk-0.1.0/tests/test_cli_dx.py +79 -0
  38. nullspace_sdk-0.1.0/tests/test_client.py +245 -0
  39. nullspace_sdk-0.1.0/tests/test_code_interpreter.py +405 -0
  40. nullspace_sdk-0.1.0/tests/test_code_interpreter_sdk_sync_async.py +552 -0
  41. nullspace_sdk-0.1.0/tests/test_commands.py +711 -0
  42. nullspace_sdk-0.1.0/tests/test_connect_bucket.py +426 -0
  43. nullspace_sdk-0.1.0/tests/test_connect_bucket_integration.py +792 -0
  44. nullspace_sdk-0.1.0/tests/test_desktop.py +597 -0
  45. nullspace_sdk-0.1.0/tests/test_desktop_firecracker_integration.py +828 -0
  46. nullspace_sdk-0.1.0/tests/test_files.py +2716 -0
  47. nullspace_sdk-0.1.0/tests/test_firecracker_code_interpreter_integration.py +600 -0
  48. nullspace_sdk-0.1.0/tests/test_firecracker_code_interpreter_sdk_sync_async.py +481 -0
  49. nullspace_sdk-0.1.0/tests/test_firecracker_filesystem_parity.py +814 -0
  50. nullspace_sdk-0.1.0/tests/test_firecracker_sdk_user_journeys.py +2053 -0
  51. nullspace_sdk-0.1.0/tests/test_firecracker_uploads_integration.py +245 -0
  52. nullspace_sdk-0.1.0/tests/test_git.py +870 -0
  53. nullspace_sdk-0.1.0/tests/test_integration.py +1226 -0
  54. nullspace_sdk-0.1.0/tests/test_integration_helpers.py +116 -0
  55. nullspace_sdk-0.1.0/tests/test_jupyter_server.py +98 -0
  56. nullspace_sdk-0.1.0/tests/test_lifecycle.py +998 -0
  57. nullspace_sdk-0.1.0/tests/test_lifecycle_integration.py +1240 -0
  58. nullspace_sdk-0.1.0/tests/test_mcp_server.py +65 -0
  59. nullspace_sdk-0.1.0/tests/test_monitor.py +226 -0
  60. nullspace_sdk-0.1.0/tests/test_path_contract_docs.py +43 -0
  61. nullspace_sdk-0.1.0/tests/test_pty.py +476 -0
  62. nullspace_sdk-0.1.0/tests/test_pty_integration.py +97 -0
  63. nullspace_sdk-0.1.0/tests/test_quickstart_integration.py +52 -0
  64. nullspace_sdk-0.1.0/tests/test_sandbox.py +2549 -0
  65. nullspace_sdk-0.1.0/tests/test_sandbox_integration.py +1734 -0
  66. nullspace_sdk-0.1.0/tests/test_sdk_cli_beta_surface_matrix.py +191 -0
  67. nullspace_sdk-0.1.0/tests/test_snapshot.py +123 -0
  68. nullspace_sdk-0.1.0/tests/test_template.py +3054 -0
  69. nullspace_sdk-0.1.0/tests/test_template_builder_surface.py +649 -0
  70. nullspace_sdk-0.1.0/tests/test_template_docs.py +573 -0
  71. nullspace_sdk-0.1.0/tests/test_volume.py +1227 -0
  72. nullspace_sdk-0.1.0/uv.lock +1045 -0
@@ -0,0 +1,61 @@
1
+ # Build artifacts
2
+ /target/
3
+ /artifacts/
4
+ /data/
5
+ /.local/
6
+
7
+ # Repo-local runtime state now lives under `.local/` by default. Keep the old
8
+ # root-level paths ignored as a safety net for stray local leftovers.
9
+
10
+ # Rootfs images
11
+ *.ext4
12
+ *.img
13
+
14
+ # Firecracker binaries
15
+ firecracker
16
+ vmlinux
17
+ !crates/vmm/src/firecracker/
18
+ !crates/vmm/src/firecracker/**
19
+
20
+ # OS
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # IDE
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+
31
+ # Python
32
+ __pycache__/
33
+ *.pyc
34
+ .venv/
35
+ *.egg-info/
36
+ dist/
37
+
38
+ # Node / TypeScript
39
+ node_modules/
40
+ apps/console/dist/
41
+ apps/console/*.tsbuildinfo
42
+ apps/console/tailwind.config.js
43
+ apps/console/tailwind.config.d.ts
44
+ apps/console/vite.config.js
45
+ apps/console/vite.config.d.ts
46
+ sdks/typescript/dist/
47
+ apps/console/playwright-report/
48
+ apps/console/test-results/
49
+
50
+ # Environment
51
+ .env
52
+ .env.*
53
+ !.env.example
54
+ nullspace-metal-key.pem
55
+
56
+ # discord-bridge cli state
57
+ .state/
58
+
59
+ # Supabase local state
60
+ infra/supabase/.branches/
61
+ infra/supabase/.temp/
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to `nullspace-sdk` are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/) starting at `0.1.0`. The
6
+ import package is `nullspace`; the CLI command is `nullspace`.
7
+
8
+ ## [0.1.0] — 2026-04-30
9
+
10
+ First private-beta SDK package. Native-only surface; no E2B compatibility shim.
11
+
12
+ ### Added
13
+
14
+ - `Sandbox`, `AsyncSandbox`, `SandboxPaginator`, `AsyncSandboxPaginator` —
15
+ on-demand Firecracker microVMs (`create`, `commands.run`, `files.*`,
16
+ `get_url`, `kill`, `pause`, `resume`, `fork`).
17
+ - `Snapshot` / `AsyncSnapshot` — snapshot and resume.
18
+ - `Volume` / `AsyncVolume` plus `volume.files` direct file management
19
+ (Firecracker-only beta surface).
20
+ - `Template` / `TemplateBuild` plus readiness probes
21
+ (`CommandReadinessProbe`, `HttpReadinessProbe`, `PortReadinessProbe`,
22
+ `TimeoutReadinessProbe`) and helpers (`wait_for_file`, `wait_for_port`,
23
+ `wait_for_process`, `wait_for_timeout`, `wait_for_url`).
24
+ - `CodeInterpreter` / `AsyncCodeInterpreter` — Jupyter-style execution.
25
+ - `Monitor` / `AsyncMonitor` — sandbox metrics and process events.
26
+ - `Lifecycle` / `AsyncLifecycle` plus webhook delivery types — lifecycle
27
+ event subscriptions.
28
+ - `nullspace.__version__` — derived from `importlib.metadata`.
29
+ - `nullspace` CLI under the `[cli]` extra: `auth`, `sandbox`, `volume`,
30
+ `template`, `lifecycle` subcommands.
31
+ - Resumable file and directory uploads with progress callbacks; tar-based
32
+ directory uploads honor `.nullspaceignore`.
33
+ - Public exception types: `AuthError`, `BuildError`, `FileUploadError`,
34
+ `UploadChecksumError`, `UploadConflictError`, `UploadExpiredError`,
35
+ `UploadLimitError`, `UploadPathError`, `UploadStateError`,
36
+ `SandboxError`, `TimeoutError`, `NotFoundError`, `NotImplementedError`,
37
+ `GitError`, `BatchWriteError`.
38
+
39
+ ### Distribution
40
+
41
+ - Distribution name: `nullspace-sdk`. Wheel is `py3-none-any`. Supported Python
42
+ versions: 3.11, 3.12, 3.13 on Linux and macOS.
43
+ - Optional extras: `[cli]` adds `click` and `rich`; `[dev]` adds the test
44
+ toolchain.
45
+ - Built as a standard wheel. Operators may publish the same artifact through
46
+ PyPI trusted publishing or provide an immutable private-beta wheel/tag
47
+ through the handout install spec.
48
+
49
+ ### Known beta limitations
50
+
51
+ - **Volumes are a Firecracker-only SDK surface.** Shared mounts use
52
+ close-to-open visibility, atomic rename, and `flock` plus traditional
53
+ `fcntl` record locks.
54
+ - **Template build and logging are Firecracker-only.** Docker is not part
55
+ of the supported long-term contract for this surface.
56
+ - **Path contract.** `/workspace` remains the default mutable work tree;
57
+ filesystem APIs accept other absolute sandbox-scoped paths
58
+ (`/tmp/...`, `/data/...`, `/srv/app/...`); user-controlled `cwd` and
59
+ mount-path inputs reject reserved runtime paths under
60
+ `/workspace/.nullspace`.
61
+ - **No session caps.** Persistence is GA; there are no 24-hour limits,
62
+ unlike alternatives in this category.
63
+ - **Breaking changes are allowed during the `0.x` series** and will be
64
+ recorded here.
65
+
66
+ ### Documentation
67
+
68
+ - README at `sdks/python/README.md`.
69
+ - Hosted private-beta docs: `https://docs.13-215-85-171.sslip.io`.
70
+ - Sprint planning ticket:
71
+ `docs/engineering/sprints/sprint_6_derek/09_python_sdk_pypi_release.md`.
72
+ - Operator runbook: `docs/engineering/runbooks/python-sdk-release.md`.
@@ -0,0 +1,416 @@
1
+ Metadata-Version: 2.4
2
+ Name: nullspace-sdk
3
+ Version: 0.1.0
4
+ Summary: Cloud sandboxes for AI agents
5
+ Project-URL: Homepage, https://docs.13-215-85-171.sslip.io
6
+ Project-URL: Repository, https://github.com/catamaran-research/nullspace
7
+ Project-URL: Documentation, https://docs.13-215-85-171.sslip.io/sdk-reference
8
+ Project-URL: Issues, https://github.com/catamaran-research/nullspace/issues
9
+ Author-email: Nullspace <team@nullspace.dev>
10
+ License-Expression: Apache-2.0
11
+ Keywords: agents,ai,cloud,firecracker,microvm,sandbox
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pathspec>=0.12
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: websockets>=13.0
26
+ Provides-Extra: cli
27
+ Requires-Dist: click>=8.0; extra == 'cli'
28
+ Requires-Dist: rich>=13.0; extra == 'cli'
29
+ Provides-Extra: dev
30
+ Requires-Dist: click>=8.0; extra == 'dev'
31
+ Requires-Dist: mcp[cli]<2,>=1; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
33
+ Requires-Dist: pytest-httpx>=0.34; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: rich>=13.0; extra == 'dev'
36
+ Requires-Dist: zstandard>=0.23; extra == 'dev'
37
+ Provides-Extra: mcp
38
+ Requires-Dist: mcp[cli]<2,>=1; extra == 'mcp'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Nullspace
42
+
43
+ Open-source cloud sandboxes for AI agents. Create isolated Linux environments on demand, run commands, read/write files, and expose ports — all from a few lines of Python.
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/nullspace-sdk)](https://pypi.org/project/nullspace-sdk/)
46
+ [![License](https://img.shields.io/pypi/l/nullspace-sdk)](https://github.com/catamaran-research/nullspace/blob/main/LICENSE)
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ uv pip install "${NULLSPACE_SDK_INSTALL_SPEC:-nullspace-sdk}"
52
+ ```
53
+
54
+ For private beta, `NULLSPACE_SDK_INSTALL_SPEC` may point at a pinned PyPI
55
+ version, a hosted wheel URL, or a private repo tag supplied in the handout.
56
+
57
+ For CLI usage:
58
+
59
+ ```bash
60
+ uv pip install "nullspace-sdk[cli]"
61
+ ```
62
+
63
+ Or install from source:
64
+
65
+ ```bash
66
+ uv pip install -e . # from this directory
67
+ uv pip install -e ./sdks/python # from repo root
68
+ uv pip install -e ".[cli]" # from this directory, with the CLI
69
+ uv pip install -e "./sdks/python[cli]" # from repo root, with the CLI
70
+ ```
71
+
72
+ Docs:
73
+
74
+ - [Hosted endpoints](../../docs/site/reference/hosted-endpoints.mdx)
75
+ - [Python SDK Overview](../../docs/site/guides/python-sdk/overview.mdx)
76
+ - [Python CLI Guide](../../docs/site/guides/python-sdk/cli.mdx)
77
+
78
+ ## Quickstart
79
+
80
+ ### Synchronous
81
+
82
+ ```python
83
+ from nullspace import Sandbox
84
+
85
+ with Sandbox.create() as sandbox:
86
+ # Run a command
87
+ result = sandbox.commands.run("echo 'Hello from Nullspace!'", shell=True)
88
+ print(result.stdout)
89
+
90
+ # Work with files
91
+ sandbox.files.write("/hello.txt", "world")
92
+ print(sandbox.files.read("/hello.txt"))
93
+
94
+ # Expose a port
95
+ server = sandbox.commands.run(
96
+ "python3 -m http.server 8080",
97
+ background=True,
98
+ shell=True,
99
+ )
100
+ print(sandbox.get_url(8080))
101
+ server.kill()
102
+ ```
103
+
104
+ ### Asynchronous
105
+
106
+ ```python
107
+ import asyncio
108
+ from nullspace import AsyncSandbox
109
+
110
+
111
+ async def main() -> None:
112
+ async with await AsyncSandbox.create() as sandbox:
113
+ result = await sandbox.commands.run("echo 'Hello from Nullspace!'", shell=True)
114
+ print(result.stdout)
115
+
116
+ await sandbox.files.write("/hello.txt", "world")
117
+ print(await sandbox.files.read("/hello.txt"))
118
+
119
+
120
+ asyncio.run(main())
121
+ ```
122
+
123
+ Use `shell=True` for normal authored command strings. Use `args` when you want
124
+ exact argument boundaries without shell parsing. The SDK does not infer shell
125
+ mode from a plain string, and `shell=True` cannot be combined with `args`.
126
+
127
+ Path contract note:
128
+
129
+ - No breaking change: `/workspace` remains the default mutable work tree for
130
+ agent and repo-style flows.
131
+ - General filesystem APIs also accept valid sandbox-scoped absolute paths such
132
+ as `/tmp/...`, `/data/...`, and `/srv/app/...`.
133
+ - Endpoint-specific path rules remain explicit: user-controlled `cwd` and
134
+ mount-path inputs still reject reserved runtime paths under
135
+ `/workspace/.nullspace`, and `/context/...` only exists when a source-mount
136
+ capable surface provides it.
137
+
138
+ ## File Uploads
139
+
140
+ Use `write()` for in-memory strings and bytes. Use `upload_file()` or `upload()`
141
+ when the source is a local file path or readable binary stream. Use
142
+ `upload_dir()` or `upload()` when the source is a local directory path.
143
+
144
+ ```python
145
+ from nullspace import Sandbox
146
+
147
+ with Sandbox.create() as sandbox:
148
+ result = sandbox.files.upload_file("./dist/app.tar.gz")
149
+ print(result.transport, result.target_path, result.bytes_uploaded)
150
+ ```
151
+
152
+ Without an explicit destination, `result.target_path` is returned as the
153
+ resolved absolute sandbox path. With the default path contract, that is
154
+ typically `/workspace/app.tar.gz`.
155
+
156
+ The SDK picks direct upload for smaller known-length files and switches to
157
+ resumable upload for larger files automatically. You can force resumable mode
158
+ and wire a progress callback:
159
+
160
+ ```python
161
+ from nullspace import FileUploadError, Sandbox
162
+
163
+
164
+ def on_progress(event) -> None:
165
+ print(event.phase, event.bytes_completed, event.bytes_total, event.transport)
166
+
167
+
168
+ with Sandbox.create() as sandbox:
169
+ try:
170
+ sandbox.files.upload_file(
171
+ "./dist/model.bin",
172
+ "/data/model.bin",
173
+ resumable=True,
174
+ progress=on_progress,
175
+ )
176
+ except FileUploadError as exc:
177
+ if exc.upload_id:
178
+ resumed = sandbox.files.resume_upload(
179
+ exc.upload_id,
180
+ "./dist/model.bin",
181
+ progress=on_progress,
182
+ )
183
+ print(resumed.upload_id, resumed.bytes_uploaded)
184
+ else:
185
+ raise
186
+ ```
187
+
188
+ Local directory paths use resumable tar upload. `upload()` dispatches directory
189
+ paths to `upload_dir()` with the default `merge` conflict policy:
190
+
191
+ ```python
192
+ from nullspace import Sandbox
193
+
194
+ with Sandbox.create() as sandbox:
195
+ result = sandbox.files.upload_dir(
196
+ "./src",
197
+ "/data/src",
198
+ ignore_patterns=["*.pyc", "!pkg/__init__.py"],
199
+ )
200
+ print(result.kind, result.file_count, result.target_path)
201
+ ```
202
+
203
+ Directory uploads honor `.nullspaceignore` from the source root, append any
204
+ explicit `ignore_patterns` after it, preserve symlinks, and do not implicitly
205
+ exclude `.git` or other dot-directories.
206
+
207
+ If a resumable directory upload fails mid-transfer, pass the same source
208
+ directory back to `resume_upload()`:
209
+
210
+ ```python
211
+ from nullspace import FileUploadError, Sandbox
212
+
213
+ with Sandbox.create() as sandbox:
214
+ try:
215
+ sandbox.files.upload_dir("./src", "/data/src")
216
+ except FileUploadError as exc:
217
+ if exc.upload_id:
218
+ resumed = sandbox.files.resume_upload(exc.upload_id, "./src")
219
+ print(resumed.upload_id, resumed.bytes_uploaded)
220
+ else:
221
+ raise
222
+ ```
223
+
224
+ ## CLI Uploads
225
+
226
+ The bundled CLI now exposes the same upload surface under
227
+ `nullspace sandbox upload`:
228
+
229
+ ```bash
230
+ nullspace sandbox upload sb_123 ./dist/app.tar.gz /data/app.tar.gz
231
+ nullspace sandbox upload sb_123 ./src /data/src --exclude '*.pyc'
232
+ nullspace sandbox upload sb_123 - /tmp/stdin.bin
233
+ ```
234
+
235
+ Resumable failures print a concrete next command when the source can be replayed:
236
+
237
+ ```bash
238
+ nullspace sandbox upload sb_123 ./big.iso --resume up_123
239
+ ```
240
+
241
+ Use `--dry-run` to preview local file or directory uploads, and add `--json`
242
+ for machine-readable output.
243
+
244
+ ## Authentication
245
+
246
+ Set your API key as an environment variable:
247
+
248
+ ```bash
249
+ export NULLSPACE_API_KEY=ns_live_...
250
+ ```
251
+
252
+ For the bundled CLI, you can also save an API key with:
253
+
254
+ ```bash
255
+ nullspace auth login
256
+ ```
257
+
258
+ That writes `~/.nullspace/config.json` for backward compatibility. The SDK
259
+ accepts explicit `api_key=` / `base_url=` arguments; without those, the SDK and
260
+ CLI read environment variables, project `.env`,
261
+ `~/.config/nullspace/config.json`, then legacy `~/.nullspace/config.json`.
262
+ The legacy config key `api_url` is accepted as an alias for `base_url`.
263
+
264
+ Or pass it directly:
265
+
266
+ ```python
267
+ from nullspace import Sandbox
268
+
269
+ sandbox = Sandbox.create(api_key="ns_live_...")
270
+ sandbox.kill()
271
+ ```
272
+
273
+ ## Auto-Resume
274
+
275
+ Use `auto_resume=True` with paused timeout behavior when a sandbox should wake
276
+ on its public URL after hibernating:
277
+
278
+ ```python
279
+ from nullspace import Sandbox
280
+
281
+ sandbox = Sandbox.create(on_timeout="pause", auto_resume=True)
282
+ url = sandbox.get_url(8080)
283
+ ```
284
+
285
+ After the sandbox pauses, inbound HTTP or websocket traffic to its public URL
286
+ wakes it and the original request is forwarded once the resumed execution is
287
+ ready. `Sandbox.connect(id)` is still an explicit reconnect operation: it
288
+ resumes a paused sandbox ID by snapshot route and returns the new running
289
+ execution. `Sandbox.get_info_by_id(id)` is read-only and does not wake paused
290
+ sandboxes.
291
+
292
+ ## Features
293
+
294
+ - **On-demand sandboxes** — spin up isolated Linux environments in milliseconds
295
+ - **Command execution** — run shell commands and capture stdout/stderr
296
+ - **File I/O** — read, write, list, and search files inside the sandbox
297
+ - **Persistent volumes** — tenant-scoped shared volume objects, direct `volume.files` management in Python and CLI, by-name lookup, and canonical `volumes=[...]` sandbox mounts
298
+ - **Port exposure** — expose sandbox ports via public URLs
299
+ - **PTY sessions** — interactive terminal sessions over WebSocket
300
+ - **PTY REST session IDs** — canonical session identity is numeric PID
301
+ - **Snapshot & resume** — hibernate sandboxes and resume them later
302
+ - **Fork** — clone a running sandbox (unique to Nullspace)
303
+
304
+ ## Current Limits
305
+
306
+ - **Volumes** are currently a Firecracker-only SDK surface. The Python SDK and
307
+ bundled CLI expose direct volume file management plus create-time mounts.
308
+ - Shared mounts use close-to-open visibility, atomic rename, and `flock` plus
309
+ traditional `fcntl` record locks; snapshot resume and fork remount them with
310
+ fresh internal leases before the new sandbox becomes ready. This remounts
311
+ external shared storage; it does not make Firecracker VM memory or mutable
312
+ rootfs snapshots portable across incompatible runtime hosts.
313
+ - The canonical mount shape is `volumes=[...]` with `ref`, `mount_path`,
314
+ optional `subpath`, and `read_only`.
315
+
316
+ ## Persistent Volumes
317
+
318
+ ```python
319
+ from nullspace import Sandbox, Volume
320
+
321
+ shared = Volume.create("team-data")
322
+ same_shared = Volume.from_name("team-data")
323
+
324
+ with Sandbox.create(volumes=[shared.mount("/workspace/shared")]) as sandbox:
325
+ sandbox.files.write("/workspace/shared/hello.txt", "persistent state")
326
+ print([attachment.mount_path for attachment in sandbox.volumes])
327
+ print(same_shared.id)
328
+ ```
329
+
330
+ Direct volume file management uses the same persistent data without starting a
331
+ sandbox first:
332
+
333
+ ```python
334
+ from pathlib import Path
335
+ import tempfile
336
+
337
+ from nullspace import Volume
338
+
339
+ shared = Volume.from_name("team-data", create_if_missing=True)
340
+ shared.files.make_dir("/datasets")
341
+ shared.files.write("/datasets/hello.txt", "hello from direct volume access\n")
342
+
343
+ with tempfile.TemporaryDirectory() as tmpdir:
344
+ local_file = Path(tmpdir) / "artifact.txt"
345
+ local_file.write_text("uploaded from local disk\n", encoding="utf-8")
346
+ shared.files.upload_file(local_file, "/datasets/artifact.txt")
347
+ shared.files.download_file("/datasets/artifact.txt", Path(tmpdir) / "artifact-copy.txt")
348
+ shared.files.download_dir("/datasets", Path(tmpdir) / "datasets-copy")
349
+
350
+ print(shared.files.read("/datasets/hello.txt").strip())
351
+ print(shared.files.download_url("/datasets/artifact.txt"))
352
+ ```
353
+
354
+ ```bash
355
+ nullspace volume ls-files team-data /
356
+ nullspace volume upload team-data ./dist/model.bin /models/model.bin
357
+ nullspace volume download team-data /models/model.bin ./model.bin
358
+ nullspace volume download team-data /datasets/frontend ./frontend-copy
359
+ nullspace volume download team-data /datasets/frontend ./frontend-copy.tar --archive
360
+ ```
361
+
362
+ See the full guides:
363
+
364
+ - [`docs/site/guides/python-sdk/volumes.mdx`](../../docs/site/guides/python-sdk/volumes.mdx)
365
+
366
+ ## Templates
367
+
368
+ Template build and logging are Firecracker-only. Docker is not part of the supported long-term contract for this feature.
369
+
370
+ ```python
371
+ from nullspace import Sandbox, Template, default_build_logger, wait_for_timeout
372
+
373
+ builder = (
374
+ Template()
375
+ .from_ubuntu_image("22.04")
376
+ .set_runtime_envs({"HELLO": "Hello from Nullspace!"})
377
+ .set_start_cmd(
378
+ "echo $HELLO > /tmp/boot.log",
379
+ readiness=wait_for_timeout(5_000),
380
+ )
381
+ )
382
+
383
+ build = Template.build(
384
+ builder,
385
+ name="hello-template",
386
+ on_log_entry=default_build_logger(),
387
+ )
388
+
389
+ # Equivalent shorthand:
390
+ build = Template.build(builder, "hello-template:stable")
391
+
392
+ with Sandbox.create(template=build.name) as sandbox:
393
+ print(sandbox.files.read("/tmp/boot.log").strip())
394
+ ```
395
+
396
+ Use `Template.build_in_background(...)` plus `build.get_status(...)` for background builds, and `TemplateBuild.connect(...)` to reconnect to an existing build.
397
+ Ref-based management helpers include `Template.get_tags(...)`, `Template.assign_tags(...)`, and `Template.remove_tag(...)`.
398
+
399
+ For the full template guide and migration notes, see:
400
+
401
+ - [`docs/site/guides/python-sdk/templates.mdx`](../../docs/site/guides/python-sdk/templates.mdx)
402
+ - [`docs/site/guides/python-sdk/template-build-logging-migration.mdx`](../../docs/site/guides/python-sdk/template-build-logging-migration.mdx)
403
+
404
+ ## Links
405
+
406
+ - [Hosted endpoints](../../docs/site/reference/hosted-endpoints.mdx)
407
+ - [GitHub](https://github.com/catamaran-research/nullspace)
408
+ - [Python SDK Overview](../../docs/site/guides/python-sdk/overview.mdx)
409
+ - [Python Template Guide](../../docs/site/guides/python-sdk/templates.mdx)
410
+ - [Python Template Build Migration Guide](../../docs/site/guides/python-sdk/template-build-logging-migration.mdx)
411
+ - [Documentation source](../../docs/site)
412
+ - [Changelog](./CHANGELOG.md)
413
+
414
+ ## License
415
+
416
+ Apache-2.0