@delta-agentcloud/cli 1.1.34 → 1.1.38

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.
package/README.md CHANGED
@@ -187,6 +187,9 @@ You can also provide credentials via environment variables: `DELTA_INFRA_API_KEY
187
187
  # multiprocessing, Chromium-based kernels etc. fail with "No space left on device"/"Bus error" when shared memory is insufficient; raise it then.
188
188
  # The unit is mandatory (4G/512M are normalized to 4Gi/512Mi; a bare number is rejected by the server) and must not exceed --memory.
189
189
  # `--devices` requests host devices (comma-separated, e.g. kvm → /dev/kvm inside the container, for nested virtualization): a privilege escalation,
190
+ # `--ready-timeout` caps how long the CREATE step waits for the sandbox to become ready (seconds, default 120, range 10-1800):
191
+ # a cold node fetching a large image layer (e.g. 2.9GB) often takes longer than 60s, so raising it avoids error_code=SandboxReadyTimeout.
192
+ # Applies to private compute only; third-party compute uses its own internal create timeout and ignores it. Distinct from --max-life (post-create TTL).
190
193
  # rejected by the server when the allowlist is disabled (never silently ignored). Both apply to private compute only; third-party compute ignores them.
191
194
  delta-cli sandbox create \
192
195
  --image-name image.yangtzeailab.com/opensandbox/pytorch-cuda13:latest \
@@ -281,19 +284,19 @@ delta-cli sandbox operation <request_id>
281
284
  | `delta-cli sandbox run-bg <id> --command "..." [--timeout <sec>] [--wait] [--cancel-on-timeout] [--format sse\|json] [--json-result] [--summary/--no-summary] [--artifacts]` | Run a command in the background; without `--wait` it returns `{execution_id, sandbox_id}` immediately (with `--format json`, the unified result object with `finished=false`); with `--wait` it follows via SSE (raw `data:` frames to stdout, no trailing envelope); `--format json` prints exactly one envelope with the same structure as `sandbox run --format json`. **On a wait timeout** → `error.type=timeout` and a **non-zero** process exit code, with `error.details` keeping `sandbox_id`/`execution_id`/`remote_state` (`not_cancelled`/`cancelled`/`cancel_failed`) so the command can be queried or cancelled afterwards; `remote_state=not_cancelled` means only the local wait stopped and **the remote command may still be running**. The remote command is left running by default — add `--cancel-on-timeout` to cancel it (a failed cancel is reported as `cancel_failed`) |
282
285
  | `delta-cli sandbox logs <id> --execution-id <eid> [--tail N --grep <pattern> --context N --max-bytes N]` | Show background logs; returns `stderr_size + stderr_tail` by default (avoids context blow-up); filter with `--tail/--grep` |
283
286
  | `delta-cli sandbox cancel <id> --execution-id <eid>` | Interrupt a background command |
284
- | `delta-cli sandbox read <id> --path <path> [--output <local path>] [--tail N] [--grep <pattern>] [--offset N] [--limit N] [--context N] [--max-bytes N] [--parse-json]` | Read a file in the container; `--output <path>` saves it locally; `--tail`/`--grep` filter the output. **A full copy (no `--tail`/`--grep`/`--offset`/`--limit`/`--max-bytes`) is byte-exact**: it goes through the chunked download path (binary-safe, sha256-verified — the envelope reports `byte_exact:true`, `integrity:"sha256"`, `digest_verified`). With a range filter the filter is applied to the **exact bytes**, so the copy is a byte-exact slice of the original (`byte_exact:true`, `integrity:"sha1"`). Printed inline, a text file gives `content` (strictly decoded, `byte_exact:true`) while a **non-UTF-8 file gives no `content` at all** — the exact original bytes are in `content_base64` (base64-decode it) and the envelope is marked `byte_exact:true`. Only a server that does not support that negotiation returns the lossy text (`integrity:"text_decoded"`, with a warning on stderr). |
285
- | `delta-cli sandbox pull <id> --source <sandbox path> --target <local path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/dirs from the sandbox to local (mirror of `upload`; flag direction reversed: source=remote, target=local). **Directories are always pulled recursively** (`--recursive` is deprecated and no longer needed); the transfer uses the **chunked download** path: binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. **The payload moves as raw bytes** (the server's binary endpoint, so it is not inflated by base64; older servers fall back automatically), and **the chunks must tile the whole file**: a gap or an interrupted transfer is reported as a failure, never as a successful download |
287
+ | `delta-cli sandbox read <id> --path <path> [--output <local path>] [--tail N] [--grep <pattern>] [--offset N] [--limit N] [--context N] [--max-bytes N] [--parse-json]` | Read a file in the container; `--output <path>` saves it locally; `--tail`/`--grep` filter the output. **A full copy (no `--tail`/`--grep`/`--offset`/`--limit`/`--max-bytes`) is byte-exact**: it goes through the chunked download path (binary-safe, sha256-verified — the envelope reports `byte_exact:true`, `integrity:"sha256"`, `digest_verified`). With a range filter the filter is applied to the **exact bytes**, so the copy is a byte-exact slice of the original (`byte_exact:true`, `integrity:"sha1"`). Printed inline, a text file gives `content` (strictly decoded, `byte_exact:true`) while a **non-UTF-8 file gives no `content` at all** — the exact original bytes are in `content_base64` (base64-decode it) and the envelope is marked `byte_exact:true`. Only a server that does not support that negotiation returns the lossy text (`integrity:"text_decoded"`, with a warning on stderr). An `--output` that names an existing local directory receives the file inside it, under its own name (the way `cp file dir/` works). |
288
+ | `delta-cli sandbox pull <id> --source <sandbox path> --target <local path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/dirs from the sandbox to local (mirror of `upload`; flag direction reversed: source=remote, target=local). **Directories are always pulled recursively** (`--recursive` is deprecated and no longer needed); the transfer uses the **chunked download** path: binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. **The payload moves as raw bytes** (the server's binary endpoint, so it is not inflated by base64; older servers fall back automatically), and **the chunks must tile the whole file**: a gap or an interrupted transfer is reported as a failure, never as a successful download. When `--target` names an existing local directory, the file is written inside it under its own name (the way `cp file dir/` works) |
286
289
  | `delta-cli sandbox write <id> --path <path> --source <local path>` | Write a file. **Binary-safe**: the content travels as raw bytes (the server's binary endpoint, with a base64 fallback for older servers) and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected); the response carries `server_sha256`/`sha256`/`size`/`size_verified`. A `--source` above 32 MiB goes through the streaming upload path instead of being read into memory |
287
290
  | `delta-cli sandbox write-multiple <id> --entry <remote path>=<local path>` | Write multiple files |
288
- | `delta-cli sandbox ls <id> --path <path>` | List a directory |
291
+ | `delta-cli sandbox ls <id> --path <path>` | List a directory (a path that is a file, or is missing, is rejected with an explicit error instead of a generic provider failure) |
289
292
  | `delta-cli sandbox stat <id> --path <path>` | File metadata |
290
293
  | `delta-cli sandbox mv <id> --entry <src=dest>` | Move/rename |
291
294
  | `delta-cli sandbox replace <id> --path <path> --old <text> --new <text>` | Replace content |
292
295
  | `delta-cli sandbox chmod <id> --path <path> --mode <octal>` | Change permissions |
293
- | `delta-cli sandbox rm <id> --path <path>` | Delete a file |
294
- | `delta-cli sandbox rmdir <id> --path <dir>` | Recursively delete a directory |
296
+ | `delta-cli sandbox rm <id> --path <path>` | Delete a file. **Files only**: a directory path is rejected (use `rmdir`), and a path that does not exist is an error rather than a silent success |
297
+ | `delta-cli sandbox rmdir <id> --path <dir>` | Recursively delete a directory (cannot be undone). **Directories only**: a file path is rejected and the file is left untouched, and a path that does not exist is an error |
295
298
  | `delta-cli sandbox mkdir <id> --path <dir>` | Create a directory |
296
- | `delta-cli sandbox search <id> --path <root> --pattern <glob>` | Search files |
299
+ | `delta-cli sandbox search <id> --path <root> --pattern <glob>` | Search files (an unterminated glob such as `[` is rejected instead of silently matching nothing) |
297
300
  | `delta-cli sandbox working-directory <id>` | Get the sandbox working directory path |
298
301
  | `delta-cli sandbox upload <id> --source <local dir> --target <sandbox path>` | Upload a directory (tar.gz + auto-extract) |
299
302
  | `delta-cli sandbox expose <id> [--port N]` | Expose a sandbox service port to the public internet (frp tunnel), default port 8700 |
@@ -346,7 +349,7 @@ Error types and exit codes:
346
349
 
347
350
  | Type | Exit Code | Scenario |
348
351
  |------|-----------|----------|
349
- | validation | 2 | Argument error |
352
+ | validation | 2 | Argument error — includes a file path used where a directory is required (or the reverse) and an invalid glob |
350
353
  | auth | 3 | Not authenticated |
351
354
  | permission | 4 | Insufficient permission |
352
355
  | not_found | 5 | Resource does not exist |
package/checksums.txt CHANGED
@@ -1,5 +1,5 @@
1
- 492db2e329de38d28486a7df310bd0223c6bbcd465423d823b945b046a3a9173 delta-cli-darwin-amd64.tar.gz
2
- f0000cae8dfdeafcb33aa6716c38fcaa38219883a2439d0ee27a9425dd81651d delta-cli-darwin-arm64.tar.gz
3
- 7fff0a38b42c2694a37bb9415ebed791f70e3f41d8fde24670824ad5b7f56182 delta-cli-linux-amd64.tar.gz
4
- 1ef7e3c95d6c44bb59e0d8b7c66258686fa85426e6f5fc298d406b1b4b9bbee6 delta-cli-linux-arm64.tar.gz
5
- 68db2c20c6bb2127a2c6c8beecb383f362c5fbb7ca577df62392aea69c62f039 delta-cli-windows-amd64.zip
1
+ 648c7b5a995d70cd94dcab7e933a0ba6e188d5af55cef9e3ae0b5bc54ea3fb92 delta-cli-darwin-amd64.tar.gz
2
+ d94a8d0379546a6f2b5df998542ba46cd08c5d7836a19c53b384a8dee8927cac delta-cli-darwin-arm64.tar.gz
3
+ 4f691da0b1a9e99bfb73cdba1b2e5ce6ace6b407fd4ebf31f0ae662133ab6b87 delta-cli-linux-amd64.tar.gz
4
+ a0bcb29452a24afe3502f8c1b7e7e6ad65c05a91c767cbe9600d577143e3e001 delta-cli-linux-arm64.tar.gz
5
+ 4cd8f9ef14bc84e1917f2ef0969f4e7d693b45d9a5db24337ef8b4deac4dad13 delta-cli-windows-amd64.zip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delta-agentcloud/cli",
3
- "version": "1.1.34",
3
+ "version": "1.1.38",
4
4
  "description": "Delta Agent Cloud CLI for sandbox compute and science tools.",
5
5
  "bin": {
6
6
  "delta-cli": "scripts/run.js"
@@ -156,9 +156,11 @@ The reason is that, after running a command, this skill reads `log_file` from th
156
156
  | Get a resource recommendation | `sandbox recommend --cpu N --memory XGi [--gpu N] [--gpu-mem N]` (`--memory`/`--gpu-mem` accept g/m/G/M formats such as `1G`/`512M`; the CLI converts these automatically to Gi/Mi) |
157
157
  | List the current user's sandboxes | `sandbox list [--status <running/finished/killed/error>] [--start-time <ISO8601>] [--end-time <ISO8601>] [--sandbox-id <id>] [--days N]` |
158
158
  | **Lifecycle** | |
159
- | Create a sandbox | `sandbox create --image-name <image-name> [--cpu N --memory XGi --gpu N --gpu-mem N --gpu-type <model> --shm-size XGi --devices kvm --max-life M]`; `--memory`/`--gpu-mem` accept g/m/G/M formats (for example `1G`/`512M`, including lower-case forms), which the CLI converts to the server's `Gi`/`Mi` units; `--image-name` takes an image name (the `image_name` field returned by `sandbox images`, for example `"PyTorch CUDA13 (GPU)"`); the underlying image identifier is hidden from users. `--gpu-type` selects a GPU model (for example `"RTX 4090"` or `H100`); see `gpu_types[].gpu_name` in the output of `sandbox resources` for available models. If omitted, scheduling is automatic. `--shm-size` increases the container's `/dev/shm` (default is only 64Mi; essential for multiprocessing/DataLoader workloads; **must include Gi/Mi units** and must not exceed `--memory`; only applied on private compute). `--devices kvm` requests host-device passthrough (the container receives `/dev/kvm` for nested virtualisation; an operator must enable it on the allowlist; only applied on private compute). Add `--no-auto-cleanup` if you do not want automatic cleanup (only an explicit kill/finish destroys it). `--shared-workspace` defaults to true and enables a shared workspace; `supports_shared_workspace` in `sandbox images` indicates whether that compute source supports one. For quick tasks that do not rely on shared data, use `--shared-workspace=false` to skip workspace seeding/write-back. |
159
+ | Create a sandbox | `sandbox create --image-name <image-name> [--cpu N --memory XGi --gpu N --gpu-mem N --gpu-type <model> --shm-size XGi --devices kvm --max-life M]`; `--memory`/`--gpu-mem` accept g/m/G/M formats (for example `1G`/`512M`, including lower-case forms), which the CLI converts to the server's `Gi`/`Mi` units; `--image-name` takes an image name (the `image_name` field returned by `sandbox images`, for example `"PyTorch CUDA13 (GPU)"`); the underlying image identifier is hidden from users. `--gpu-type` selects a GPU model (for example `"RTX 4090"` or `H100`); see `gpu_types[].gpu_name` in the output of `sandbox resources` for available models. If omitted, scheduling is automatic. `--shm-size` increases the container's `/dev/shm` (default is only 64Mi; essential for multiprocessing/DataLoader workloads; **must include Gi/Mi units** and must not exceed `--memory`; only applied on private compute). `--devices kvm` requests host-device passthrough (the container receives `/dev/kvm` for nested virtualisation; an operator must enable it on the allowlist; only applied on private compute). Add `--no-auto-cleanup` if you do not want automatic cleanup (only an explicit kill/finish destroys it). `--shared-workspace` defaults to true and enables a shared workspace; `supports_shared_workspace` in `sandbox images` indicates whether that compute source supports one. For quick tasks that do not rely on shared data, use `--shared-workspace=false` to skip workspace seeding/write-back. `--ready-timeout <seconds>` sets how long the server waits for the sandbox to become **ready during creation** (distinct from `--max-life`, which controls how long it lives afterwards). The default is 120s. It is applied **only on private compute (third-party compute ignores it** and uses its own internal creation timeout), and the accepted range is 10-1800s. Raise it when a cold node must pull a large image for the first time; otherwise creation can fail with `error_code=SandboxReadyTimeout`. |
160
160
  | Shared workspace (persistence across sandboxes) | `--shared-workspace=true` (the default) enables a shared workspace: when creating a sandbox, the platform **seeds** the user's workspace data into `/workspace/{user_id}`; on `kill`/`finish` it **writes the data back**, and the next sandbox receives it automatically, allowing files to be retrieved across sandboxes. **The only persistent directory is `/workspace/{user_id}`**. Files written elsewhere (`/tmp`, `/root`, custom directories, etc.) are lost when the sandbox is destroyed, so put any results you need to keep in this directory. ⚠️ Whether data persists across sandboxes depends on whether the platform detects a pre-installed `rclone` in the image at creation time: with rclone, seeding/write-back works; without it, those steps are skipped (data is not persisted across sandboxes, but creation still succeeds). If you are unsure whether an image includes rclone, write to `/workspace/{user_id}`, destroy the sandbox and create another to check. For quick tasks that do not rely on shared data, use `--shared-workspace=false` to skip seeding/write-back. |
161
161
  **Per-sandbox resource limits**: cpu ≤ 512 / memory ≤ 1024Gi / gpu ≤ 64 / gpuMem ≤ 1024Gi / shmSize ≤ memory. Requests above these limits fail quickly rather than hanging.
162
+
163
+ **Creation timeout**: `--ready-timeout` (default 120s, range 10-1800s) bounds how long the server waits for a sandbox to become ready *during creation*, and is honoured **only on private compute**; AutoDL/RunPod use their own internal creation timeouts and ignore it. A sandbox that is created but never becomes ready fails with `error_code=SandboxReadyTimeout`, which usually means the image was not yet cached on the target node and the first pull exceeded the timeout. `--max-life` is unrelated: it caps the sandbox's lifetime after creation.
162
164
  | Extend the lifecycle | `sandbox extend <sandbox_id> --max-life N` — **dynamically extend** a running sandbox's max-life (increase or decrease it; N must exceed the elapsed minutes and be ≤10080; use `kill`/`finish` for early cleanup; sandboxes with `--no-auto-cleanup` do not need renewal). Without `--max-life`, this command reports the current lifecycle (elapsed time, time remaining and expiry); `status` output includes a `data.lifecycle` summary (elapsed time, time remaining and expiry; omitted if the lifecycle endpoint is unavailable). |
163
165
  | Connect to a sandbox | `sandbox connect <id>` |
164
166
  | View status | `sandbox status <id>` |
@@ -175,7 +177,7 @@ The reason is that, after running a command, this skill reads `log_file` from th
175
177
  | Reclaim a tunnel | `sandbox dispose <id> --port N` — explicitly reclaim a single tunnel (stop frpc and release the public port); killing the sandbox also reclaims all its tunnels |
176
178
  | **File operations** | |
177
179
  | Read a file | `sandbox read <id> --path <path> [--output <local-path> --tail N --grep <pattern> --offset N --limit N --context N --max-bytes N --parse-json]` — **byte-exact**: a full copy goes through the chunked download (sha256-verified) and a filtered copy is a byte-exact slice of the original. Printed inline, a text file gives `content`; a **non-UTF-8 file gives no `content`** — the exact bytes are in `content_base64` |
178
- | Pull a file/directory | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` — pull a single file or **recursively** pull a directory (directories are always recursive), over the **chunked** path: bytes move verbatim (no base64 inflation), binary-safe, sha256-verified, resumable and parallel (the reverse of upload: source=remote, target=local) |
180
+ | Pull a file/directory | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` — pull a single file or **recursively** pull a directory (directories are always recursive), over the **chunked** path: bytes move verbatim (no base64 inflation), binary-safe, sha256-verified, resumable and parallel (the reverse of upload: source=remote, target=local). A `--target` that names an existing local directory receives the file inside it, under its own name |
179
181
  | View the working directory | `sandbox working-directory <id>` (returns `data.path`, such as `/workspace/{user_id}/{sandbox_id}`) |
180
182
  | Write a file | `sandbox write <id> --source <filename>` (without `--path`, writes to the working directory by default); **every sandbox-side path** — `--path`, the remote part of `write-multiple --entry`, `upload --target`, `pull --source`, `run`/`run-bg --log-file` — is prefixed with the working directory when relative, while an absolute path is used as given. The content is **binary-safe** (raw bytes, with a base64 fallback for older servers) and the server verifies the declared `size`/`sha256` before writing, so the response's `server_sha256`/`size_verified` can be compared with the local file |
181
183
  | Write multiple files | `sandbox write-multiple <id> --entry <remote-path>=<local-path> [--entry ...]` |
@@ -185,10 +187,10 @@ The reason is that, after running a command, this skill reads `log_file` from th
185
187
  | Move/rename | `sandbox mv <id> --entry <src=dest> [--entry ...]` |
186
188
  | Replace content | `sandbox replace <id> --path <path> --old <text> --new <text>` |
187
189
  | Change permissions | `sandbox chmod <id> --path <path> --mode <octal>` |
188
- | Delete files | `sandbox rm <id> --path <path> [--path ...]` |
189
- | Delete directories | `sandbox rmdir <id> --path <path> [--path ...]` |
190
+ | Delete files | `sandbox rm <id> --path <path> [--path ...]` — files only; a directory path is rejected, a missing path is an error |
191
+ | Delete directories | `sandbox rmdir <id> --path <path> [--path ...]` — directories only; a file path is rejected and the file is left untouched, a missing path is an error |
190
192
  | Create directories | `sandbox mkdir <id> --path <path> [--path ...]` |
191
- | Search files | `sandbox search <id> --path <root-directory> --pattern <glob>` |
193
+ | Search files | `sandbox search <id> --path <root-directory> --pattern <glob>` — an unterminated glob such as `[` is rejected, not silently empty |
192
194
 
193
195
  ## Full lifecycle
194
196
 
@@ -392,7 +394,7 @@ This skill is designed to make the conclusion self-contained: when execution end
392
394
  |-------------|----------|
393
395
  | `error.type: not_found` | Check whether `sandbox_id` is correct and whether the sandbox has been killed |
394
396
  | `error.type: network` | Check `base_url` and network connectivity |
395
- | `error.type: validation` | Check parameter formats and required flags |
397
+ | `error.type: validation` | Check parameter formats and required flags. This also covers a file path used where a directory is required (or the reverse) and an invalid glob; `error.hint` names what to change |
396
398
  | `error.type: auth / permission` | Follow the permission-denied guidance in delta-shared |
397
399
  | `error.type: api` (5xx) | Server error; check the API service status |
398
400
  | `sandbox create` failed | Read `error.hint` — it states the cause and the next step (for example, the image was not cached on the target node, so the initial pull exceeded the readiness timeout: retry once, or pick a GPU type whose node already has the image). A failed create leaves no sandbox behind, so retrying is safe. `error.request_id` identifies the failure in the server log. |
@@ -17,7 +17,7 @@
17
17
 
18
18
  | Command | Description |
19
19
  |------|------|
20
- | `sandbox create --image-name <image-name> [--idempotency-key <key>] [--cpu N --memory XGi --gpu N --gpu-mem N --gpu-type <model> --shm-size XGi --devices kvm --max-life M --no-auto-cleanup]` | Create a sandbox container (`--image-name` takes an image name, such as `"PyTorch CUDA13 (GPU)"`; the underlying image identifier is hidden from users. **`--idempotency-key`**: a retry with the same key replays the original sandbox instead of creating a duplicate, and the same key with different parameters is rejected; the response includes `request_id`/`operation_status`/`replayed`. `--gpu-type` selects a GPU model, such as `"RTX 4090"` or `H100`; the server maps this to the HAMi use-gputype annotation for model-based scheduling. To see available models, check `sandbox resources` (the `third_party` group's `gpu_types[].gpu_name`); if omitted, scheduling is automatic. `--no-auto-cleanup` prevents automatic cleanup; the sandbox can then be destroyed only with an explicit kill/finish); `--memory`/`--gpu-mem` accept g/m/G/M formats (for example, `1G`/`512M`; lowercase is also accepted), which the CLI converts to the `Gi`/`Mi` units accepted by the server; `--shm-size` increases the container's `/dev/shm` (the **default is only 64Mi**; it must be increased if PyTorch `DataLoader(num_workers>0)`/`multiprocessing`, Chromium-based engines or similar report `No space left on device`/`Bus error` because shared memory is insufficient; **a Gi/Mi unit is required** (`4G`/`512M` are normalised by the CLI; the server rejects a bare number); it **must not exceed `--memory`** (it is a memory filesystem and filling it causes OOMKill; `--memory` defaults to only `1Gi`, so increasing shm usually requires increasing memory too); validation is backend-independent and rejects invalid values before creation, but this setting is applied only on private compute (third-party compute ignores it); `--devices` requests host-device passthrough using comma-separated tokens (for example, `kvm` makes `/dev/kvm` available inside the container for nested virtualisation; this raises privileges, and if the server allowlist is not enabled, creation is **explicitly rejected** rather than silently ignored; this setting is applied only on private compute); **resource limits per sandbox**: `--cpu` ≤ 512, `--memory` ≤ 1024Gi, `--gpu` ≤ 64, `--gpu-mem` ≤ 1024Gi, and `--shm-size` ≤ `--memory`. Exceeding a limit returns a **validation error promptly at request time** (it does not hang); normal tasks are well below these limits. The response echoes the requested image name/resource (request values fill fields not returned by the server; server values take precedence); compute backend is an internal detail and is selected automatically, so `--provider` is no longer supported. If creation fails, `error.hint` states the cause and the next step (for example, the image was not cached on the target node: retry, or pick a GPU type whose node already has the image), and `error.request_id` identifies the failure in the server log. |
20
+ | `sandbox create --image-name <image-name> [--idempotency-key <key>] [--cpu N --memory XGi --gpu N --gpu-mem N --gpu-type <model> --shm-size XGi --devices kvm --max-life M --no-auto-cleanup]` | Create a sandbox container (`--image-name` takes an image name, such as `"PyTorch CUDA13 (GPU)"`; the underlying image identifier is hidden from users. **`--idempotency-key`**: a retry with the same key replays the original sandbox instead of creating a duplicate, and the same key with different parameters is rejected; the response includes `request_id`/`operation_status`/`replayed`. `--gpu-type` selects a GPU model, such as `"RTX 4090"` or `H100`; the server maps this to the HAMi use-gputype annotation for model-based scheduling. To see available models, check `sandbox resources` (the `third_party` group's `gpu_types[].gpu_name`); if omitted, scheduling is automatic. `--no-auto-cleanup` prevents automatic cleanup; the sandbox can then be destroyed only with an explicit kill/finish); `--memory`/`--gpu-mem` accept g/m/G/M formats (for example, `1G`/`512M`; lowercase is also accepted), which the CLI converts to the `Gi`/`Mi` units accepted by the server; `--shm-size` increases the container's `/dev/shm` (the **default is only 64Mi**; it must be increased if PyTorch `DataLoader(num_workers>0)`/`multiprocessing`, Chromium-based engines or similar report `No space left on device`/`Bus error` because shared memory is insufficient; **a Gi/Mi unit is required** (`4G`/`512M` are normalised by the CLI; the server rejects a bare number); it **must not exceed `--memory`** (it is a memory filesystem and filling it causes OOMKill; `--memory` defaults to only `1Gi`, so increasing shm usually requires increasing memory too); validation is backend-independent and rejects invalid values before creation, but this setting is applied only on private compute (third-party compute ignores it); `--devices` requests host-device passthrough using comma-separated tokens (for example, `kvm` makes `/dev/kvm` available inside the container for nested virtualisation; this raises privileges, and if the server allowlist is not enabled, creation is **explicitly rejected** rather than silently ignored; this setting is applied only on private compute); **resource limits per sandbox**: `--cpu` ≤ 512, `--memory` ≤ 1024Gi, `--gpu` ≤ 64, `--gpu-mem` ≤ 1024Gi, and `--shm-size` ≤ `--memory`. Exceeding a limit returns a **validation error promptly at request time** (it does not hang); normal tasks are well below these limits. The response echoes the requested image name/resource (request values fill fields not returned by the server; server values take precedence); compute backend is an internal detail and is selected automatically, so `--provider` is no longer supported. `--ready-timeout <seconds>` (optional, default 120, range 10-1800) bounds the wait for the sandbox to become readable during creation; it is applied **only on private compute** (AutoDL/RunPod use their own internal creation timeouts and ignore it). Raise it for cold-node first pulls of large images; otherwise creation can fail with `error_code=SandboxReadyTimeout`. If creation fails, `error.hint` states the cause and the next step (for example, the image was not cached on the target node: retry, or pick a GPU type whose node already has the image), and `error.request_id` identifies the failure in the server log. |
21
21
  | `sandbox connect <id>` | Connect to an existing sandbox. |
22
22
  | `sandbox status <id>` | Check sandbox status (running / done). |
23
23
  | `sandbox finish <id> [--results '{...}'] [--no-wait]` | Save results and destroy the sandbox automatically. Destruction is a server-side operation: the response includes `request_id`/`operation_status`; `--no-wait` returns immediately with `pending`. |
@@ -43,17 +43,17 @@
43
43
  | `sandbox write <id> [--path <path>] --source <filename>` | Write a file (recommended). If `--path` is omitted, write to the working directory (`<working-directory>/<filename>`); a relative `--path` is also resolved under the working directory. An absolute `--path` is used as-is (but the `/workspace/<filename>` root path is outside the OSS sync scope and is not recommended). **Binary-safe**: the content travels as raw bytes (the server's binary endpoint, with a base64 fallback for older servers) and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected). The response carries `server_sha256`/`sha256`/`size`/`size_verified`. |
44
44
  | `sandbox write <id> --path <absolute-path> --data "..."` | Write a small amount of inline content (`--data` has no source filename, so `--path` must be specified explicitly). |
45
45
  | `sandbox write-multiple <id> --entry <remote-path>=<local-path> [--entry ...]` | Write multiple files in one operation (a relative target `path` is also resolved under the working directory). |
46
- | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/directories to the local machine (the reverse of `upload`; flag directions are reversed: `--source`=remote sandbox path, `--target`=local path). Single files are detected automatically; **directories are always traversed recursively** (`--recursive` is deprecated and no longer needed). The transfer uses the **chunked download** path: the bytes move verbatim (no base64 inflation) when the server offers the binary endpoint, binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. |
46
+ | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/directories to the local machine (the reverse of `upload`; flag directions are reversed: `--source`=remote sandbox path, `--target`=local path). Single files are detected automatically; **directories are always traversed recursively** (`--recursive` is deprecated and no longer needed). The transfer uses the **chunked download** path: the bytes move verbatim (no base64 inflation) when the server offers the binary endpoint, binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. When `--target` names an existing local directory, the file is written inside it under its own name (the way `cp file dir/` works). |
47
47
  | `sandbox upload <id> --source <local-directory> --target <sandbox-path>` | Upload a local directory (packaged as tar.gz, extracted server-side) and verify the resulting file list against the local manifest. `--source` is local, `--target` is inside the sandbox. |
48
48
  | `sandbox ls <id> --path <path>` | List directory contents (defaults to `.`). |
49
49
  | `sandbox stat <id> --path <path>` | Get file metadata (size / mode / owner / group). |
50
50
  | `sandbox mv <id> --entry <source=dest> [--entry ...]` | Move or rename files. |
51
51
  | `sandbox replace <id> --path <path> --old <text> --new <text> [--regex] [--ignore-case]` | Replace file contents. |
52
52
  | `sandbox chmod <id> --path <path> --mode <octal>` | Change file permissions. |
53
- | `sandbox rm <id> --path <path> [--path ...]` | Delete files. |
54
- | `sandbox rmdir <id> --path <path> [--path ...]` | Delete directories recursively (cannot be undone). |
53
+ | `sandbox rm <id> --path <path> [--path ...]` | Delete files. Files only: a directory path is rejected (use `rmdir`), and a missing path is an error, not a silent success. |
54
+ | `sandbox rmdir <id> --path <path> [--path ...]` | Delete directories recursively (cannot be undone). Directories only: a file path is rejected and the file is left untouched; a missing path is an error. |
55
55
  | `sandbox mkdir <id> --path <path> [--path ...]` | Create directories (`mkdir -p`). |
56
- | `sandbox search <id> --path <root-directory> --pattern <glob>` | Search for files. |
56
+ | `sandbox search <id> --path <root-directory> --pattern <glob>` | Search for files. An unterminated glob such as `[` is rejected instead of silently matching nothing. |
57
57
 
58
58
  ## Tunnelling (port exposure)
59
59
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  1. **Check available images** `delta-cli sandbox images` — list the images supported by the server. The image determines which language runtimes are available in the container (Python / Node.js / Go / Java / Rust, etc.); choose one that fits the user's technology stack and GPU/CPU requirements.
8
8
  2. **List existing sandboxes (optional)** `delta-cli sandbox list` — check the active sandboxes already created by the current user to avoid creating duplicates.
9
- 3. **Create** `delta-cli sandbox create --image-name <image-name> --cpu 4 --memory 16Gi --gpu 1 --gpu-mem 8000 --max-life 120 [--gpu-type <model>] [--shm-size XGi] [--devices kvm] [--idempotency-key <key>]` (the sandbox is ready to use immediately; no connection step is needed. `--idempotency-key` (optional) makes network retries safe: the same key replays the original sandbox instead of creating a duplicate, and the response includes `request_id`/`operation_status`/`replayed`; `--memory`/`--gpu-mem` accept g/m/G/M formats (for example, 1G/512M; lowercase is also accepted), which the CLI converts to Gi/Mi. `--max-life` sets the sandbox's maximum lifetime in minutes; the default is 30. `--no-auto-cleanup` prevents automatic cleanup; the sandbox can then be destroyed only with an explicit kill/finish. `--image-name` takes an image name, such as `"PyTorch CUDA13 (GPU)"`; compute backend selection is automatic and does not need to be specified. `--gpu-type` (optional) selects a GPU model, such as `"RTX 4090"` or `H100`; the server maps this to the HAMi use-gputype annotation for model-based scheduling. To see available models, check `sandbox resources` (the `third_party` group's `gpu_types[].gpu_name`); if omitted, scheduling is automatic. `--shm-size` (optional) increases the container's `/dev/shm` (the **default is only 64Mi**)—it must be increased when PyTorch `DataLoader(num_workers>0)`/`multiprocessing`, Chromium-based engines or similar report `No space left on device`/`Bus error`; **a Gi/Mi unit is required** (`4G`/`512M` are normalised by the CLI; the server rejects a bare number), and it **must not exceed `--memory`** (it is a memory filesystem and filling it causes OOMKill; `--memory` defaults to only 1Gi, so increasing shm usually requires increasing memory too, for example, `--memory 8Gi --shm-size 4Gi`). Only private compute (`private`) applies this setting (third-party compute ignores it), but unit/limit validation is backend-independent and rejects invalid values before creation. `--devices` (optional) requests host-device passthrough using comma-separated tokens (for example, `--devices kvm` makes `/dev/kvm` available inside the container for nested virtualisation); this raises privileges, and if the server allowlist is not enabled, creation is **explicitly rejected** (not silently ignored; validation is backend-independent). If this error occurs, do not retry; use an approach that does not depend on that device. Only private compute applies this setting. **Resource limits per sandbox**: cpu ≤ 512, memory ≤ 1024Gi, gpu ≤ 64, gpuMem ≤ 1024Gi, shmSize ≤ memory. Exceeding a limit returns a validation error promptly at request time rather than hanging.)
9
+ 3. **Create** `delta-cli sandbox create --image-name <image-name> --cpu 4 --memory 16Gi --gpu 1 --gpu-mem 8000 --max-life 120 [--gpu-type <model>] [--shm-size XGi] [--devices kvm] [--idempotency-key <key>]` (the sandbox is ready to use immediately; no connection step is needed. `--idempotency-key` (optional) makes network retries safe: the same key replays the original sandbox instead of creating a duplicate, and the response includes `request_id`/`operation_status`/`replayed`; `--memory`/`--gpu-mem` accept g/m/G/M formats (for example, 1G/512M; lowercase is also accepted), which the CLI converts to Gi/Mi. `--max-life` sets the sandbox's maximum lifetime in minutes; the default is 30. `--no-auto-cleanup` prevents automatic cleanup; the sandbox can then be destroyed only with an explicit kill/finish. `--image-name` takes an image name, such as `"PyTorch CUDA13 (GPU)"`; compute backend selection is automatic and does not need to be specified. `--gpu-type` (optional) selects a GPU model, such as `"RTX 4090"` or `H100`; the server maps this to the HAMi use-gputype annotation for model-based scheduling. To see available models, check `sandbox resources` (the `third_party` group's `gpu_types[].gpu_name`); if omitted, scheduling is automatic. `--shm-size` (optional) increases the container's `/dev/shm` (the **default is only 64Mi**)—it must be increased when PyTorch `DataLoader(num_workers>0)`/`multiprocessing`, Chromium-based engines or similar report `No space left on device`/`Bus error`; **a Gi/Mi unit is required** (`4G`/`512M` are normalised by the CLI; the server rejects a bare number), and it **must not exceed `--memory`** (it is a memory filesystem and filling it causes OOMKill; `--memory` defaults to only 1Gi, so increasing shm usually requires increasing memory too, for example, `--memory 8Gi --shm-size 4Gi`). Only private compute (`private`) applies this setting (third-party compute ignores it), but unit/limit validation is backend-independent and rejects invalid values before creation. `--devices` (optional) requests host-device passthrough using comma-separated tokens (for example, `--devices kvm` makes `/dev/kvm` available inside the container for nested virtualisation); this raises privileges, and if the server allowlist is not enabled, creation is **explicitly rejected** (not silently ignored; validation is backend-independent). If this error occurs, do not retry; use an approach that does not depend on that device. Only private compute applies this setting. **Resource limits per sandbox**: cpu ≤ 512, memory ≤ 1024Gi, gpu ≤ 64, gpuMem ≤ 1024Gi, shmSize ≤ memory. `--ready-timeout <seconds>` (default 120, range 10-1800) bounds the wait for readiness during creation and applies **only on private compute** (third-party compute uses its own internal creation timeout); raise it when a cold node pulls a large image for the first time, otherwise creation can fail with `error_code=SandboxReadyTimeout`. Exceeding a limit returns a validation error promptly at request time rather than hanging.)
10
10
  - **If `create` fails**: the error envelope's `hint` states the cause and the next step. A common one is "the image may not be cached on the target node" — the initial pull exceeded the readiness timeout, so retry once (the pull continues in the background and later creates succeed) or pick a GPU type whose node already has the image. A failed create leaves no sandbox behind, so retrying is safe; `error.request_id` identifies the failure in the server log.
11
11
  4. **Write code/data** `delta-cli sandbox write <id> --source <filename>` — write a local file directly to the sandbox. If `--path` is omitted, the file is written to that sandbox's working directory by default (`/workspace/{user_id}/{sandbox_id}/<filename>`, which is within the OSS sync scope, so the file persists after kill). Before writing, use `delta-cli sandbox working-directory <id>` to confirm the path. **You must** use a relative source path (for example, `--source infer.py`); do not use `"$WORKSPACE_ROOT/<filename>"` or `$(pwd)/...`. A relative `--path` is also resolved under the working directory; an absolute `--path` is used as-is (the same rule applies to every sandbox-side path: the remote part of `write-multiple --entry`, `upload --target`, `pull --source`, and `run`/`run-bg --log-file`) (`/workspace/<filename>` is outside the OSS sync scope and is not recommended). You can also use `--data "..."` to write a small amount of inline content (requires an explicit `--path`), and `--mode 755` to set file permissions. The returned `size` field is the actual on-disk byte count verified with stat, so it can confirm that the write completed. Use `write-multiple` to write several files at once. File paths and extensions depend on the runtimes in the image.
12
12
  5. **Run a command** — first use `delta-cli sandbox working-directory <id>` to get `<wd>`, then construct a command for a runtime in the image. Common examples: `python <wd>/train.py` (Python), `node <wd>/app.js` (Node.js), `go run <wd>/main.go` (Go), `bash <wd>/run.sh` (Shell).
@@ -121,6 +121,8 @@ and minimal scenarios. Do not read documentation for other tools. Split cross-to
121
121
  this Skill's routing rules and read only the references for tools actually involved. Do not rely
122
122
  on separate routing, service, workflow or catalogue documents.
123
123
 
124
+ Science-service failures carry a service-specific `error.hint` rather than a sandbox one: an unknown tool points at `delta-cli science list`, a missing endpoint at `delta-cli science endpoints list <tool>`, a missing task or artifact at the `science task` commands. Follow `error.hint` instead of re-deriving the next step.
125
+
124
126
  This table lists research tools with maintained references; it is not a static server catalogue.
125
127
  If the user names a new tool absent from the table, or a reference is missing, outdated or
126
128
  inconsistent with CLI output, run `delta-cli science list`, then run