Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc
| Container runtime | Supported runtime installed and running |
| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed |

#### OpenShell Compatibility

NemoClaw 0.1.0 was validated with OpenShell 0.0.7.
During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects.

| NemoClaw | OpenShell | Notes |
|----------|-----------|-------|
| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. |

#### Container Runtimes

| Platform | Supported runtimes | Notes |
Expand Down
64 changes: 63 additions & 1 deletion bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const os = require("os");
const path = require("path");
const { spawn, spawnSync } = require("child_process");
const pRetry = require("p-retry");
const { version: NEMOCLAW_VERSION } = require("../../package.json");

/** Parse a numeric env var, returning `fallback` when unset or non-finite. */
function envInt(name, fallback) {
Expand Down Expand Up @@ -85,6 +86,46 @@ function cleanupTempDir(filePath, expectedPrefix) {
}

const EXPERIMENTAL = process.env.NEMOCLAW_EXPERIMENTAL === "1";
const OPENSHELL_COMPATIBILITY = Object.freeze({
"0.1.0": Object.freeze(["0.0.7"]),
});

function parseSemver(version) {
const match = String(version || "")
.trim()
.match(/^(\d+)\.(\d+)\.(\d+)$/);
if (!match) return null;
return match.slice(1).map((part) => Number(part));
}

function isExactSemverMatch(version, supportedVersion) {
const actual = parseSemver(version);
const supported = parseSemver(supportedVersion);
return (
Array.isArray(actual) &&
Array.isArray(supported) &&
actual[0] === supported[0] &&
actual[1] === supported[1] &&
actual[2] === supported[2]
);
}

function getSupportedOpenshellVersions(nemoclawVersion = NEMOCLAW_VERSION) {
return OPENSHELL_COMPATIBILITY[nemoclawVersion] || [];
}

function isSupportedOpenshellVersion(version, supportedVersions = getSupportedOpenshellVersions()) {
if (!version || supportedVersions.length === 0) return false;
return supportedVersions.some((supportedVersion) =>
isExactSemverMatch(version, supportedVersion),
);
}

function formatSupportedOpenshellVersions(supportedVersions) {
if (supportedVersions.length === 0) return null;
if (supportedVersions.length === 1) return supportedVersions[0];
return `${supportedVersions.slice(0, -1).join(", ")}, or ${supportedVersions.at(-1)}`;
}
const USE_COLOR = !process.env.NO_COLOR && !!process.stdout.isTTY;
const DIM = USE_COLOR ? "\x1b[2m" : "";
const RESET = USE_COLOR ? "\x1b[0m" : "";
Expand Down Expand Up @@ -443,6 +484,23 @@ function getStableGatewayImageRef(versionOutput = null) {
return `ghcr.io/nvidia/openshell/cluster:${version}`;
}

function getOpenshellCompatibilityNotice(version = null) {
const supportedVersions = getSupportedOpenshellVersions();
const supportedLabel = formatSupportedOpenshellVersions(supportedVersions);
if (version && supportedLabel && !isSupportedOpenshellVersion(version, supportedVersions)) {
return [
` OpenShell compatibility warning: NemoClaw ${NEMOCLAW_VERSION} was validated with OpenShell ${supportedLabel}, but found ${version}.`,
` Install OpenShell ${supportedLabel}, then rerun \`nemoclaw onboard\` before creating or reusing NemoClaw-managed sandboxes.`,
" Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.",
];
}
const suffix = version ? ` (${version})` : "";
return [
` OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI${suffix}.`,
" Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.",
];
}

function getOpenshellBinary() {
if (OPENSHELL_BIN) return OPENSHELL_BIN;
const resolved = resolveOpenshell();
Expand Down Expand Up @@ -1892,6 +1950,10 @@ async function preflight() {

async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) {
step(2, 7, "Starting OpenShell gateway");
const gatewayEnv = getGatewayStartEnv();
for (const line of getOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null)) {
console.log(line);
}

const gatewayStatus = runCaptureOpenshell(["status"], { ignoreError: true });
const gwInfo = runCaptureOpenshell(["gateway", "info", "-g", GATEWAY_NAME], {
Expand All @@ -1915,7 +1977,6 @@ async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) {
// sandbox itself does not need direct GPU access. Passing --gpu causes
// FailedPrecondition errors when the gateway's k3s device plugin cannot
// allocate GPUs. See: https://build.nvidia.com/spark/nemoclaw/instructions
const gatewayEnv = getGatewayStartEnv();
if (gatewayEnv.OPENSHELL_CLUSTER_IMAGE) {
console.log(` Using pinned OpenShell gateway image: ${gatewayEnv.OPENSHELL_CLUSTER_IMAGE}`);
}
Expand Down Expand Up @@ -3746,6 +3807,7 @@ module.exports = {
getGatewayStartEnv,
getGatewayReuseState,
getSandboxInferenceConfig,
getOpenshellCompatibilityNotice,
getInstalledOpenshellVersion,
getRequestedModelHint,
getRequestedProviderHint,
Expand Down
6 changes: 3 additions & 3 deletions docs/about/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ Respect CLI boundaries
Supply chain safety
: Blueprint artifacts are immutable, versioned, and digest-verified before execution.

OpenShell-native for new installs
: For users without an existing OpenClaw installation, NemoClaw recommends `openshell sandbox create` directly
rather than forcing a plugin-driven bootstrap.
OpenShell-backed lifecycle
: NemoClaw orchestrates OpenShell resources under the hood, but `nemoclaw onboard`
is the supported operator entry point for creating or recreating NemoClaw-managed sandboxes.

Reproducible setup
: Running setup again recreates the sandbox from the same blueprint and policy definitions.
Expand Down
9 changes: 9 additions & 0 deletions docs/get-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc
| Container runtime | Supported runtime installed and running |
| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed |

:::{warning} OpenShell compatibility
NemoClaw 0.1.0 was validated with OpenShell 0.0.7.
During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects.
:::

| NemoClaw | OpenShell | Notes |
|----------|-----------|-------|
| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. |

### Container Runtimes

| Platform | Supported runtimes | Notes |
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ Use this command for new installs and for recreating a sandbox after changes to
$ nemoclaw onboard
```

:::{warning}
For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox.
NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 was validated against.
:::

The wizard prompts for a provider first, then collects the provider credential if needed.
Supported non-experimental choices include NVIDIA Endpoints, OpenAI, Anthropic, Google Gemini, and compatible OpenAI or Anthropic endpoints.
Credentials are stored in `~/.nemoclaw/credentials.json`.
Expand Down
34 changes: 34 additions & 0 deletions test/onboard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getGatewayReuseState,
getPortConflictServiceHints,
getFutureShellPathHint,
getOpenshellCompatibilityNotice,
getSandboxInferenceConfig,
getInstalledOpenshellVersion,
getRequestedModelHint,
Expand All @@ -34,6 +35,8 @@ import {
writeSandboxConfigSyncFile,
} from "../bin/lib/onboard";

const ROOT = path.resolve(import.meta.dirname, "..");

describe("onboard helpers", () => {
it("classifies sandbox create timeout failures and tracks upload progress", () => {
expect(
Expand Down Expand Up @@ -254,6 +257,37 @@ describe("onboard helpers", () => {
expect(getStableGatewayImageRef("bogus")).toBe(null);
});

it("prints a compatibility note when the gateway version is pinned", () => {
expect(getOpenshellCompatibilityNotice("0.0.7")).toEqual([
" OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI (0.0.7).",
" Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.",
]);
expect(getOpenshellCompatibilityNotice()).toEqual([
" OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI.",
" Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.",
]);
});

it("warns when the installed OpenShell version falls outside the supported baseline", () => {
expect(getOpenshellCompatibilityNotice("0.0.8")).toEqual([
" OpenShell compatibility warning: NemoClaw 0.1.0 was validated with OpenShell 0.0.7, but found 0.0.8.",
" Install OpenShell 0.0.7, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.",
" Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.",
]);
});

it("prints the compatibility notice before the healthy gateway reuse early return", () => {
const content = fs.readFileSync(path.join(ROOT, "bin/lib/onboard.js"), "utf-8");
const startGwBlock = content.match(/async function startGatewayWithOptions[\s\S]*?^}/m);
expect(startGwBlock).toBeTruthy();

const noticeIndex = startGwBlock[0].indexOf("getOpenshellCompatibilityNotice(");
const healthyCheckIndex = startGwBlock[0].indexOf("if (isGatewayHealthy(");
expect(noticeIndex).toBeGreaterThanOrEqual(0);
expect(healthyCheckIndex).toBeGreaterThanOrEqual(0);
expect(noticeIndex).toBeLessThan(healthyCheckIndex);
});

it("treats the gateway as healthy only when nemoclaw is running and connected", () => {
expect(
isGatewayHealthy(
Expand Down