Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions docs/inference/set-up-vllm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,22 @@ Accepting the preview prompt sets explicit managed-vLLM intent for the narrow pe
Declining the prompt or setting only `NEMOCLAW_NO_EXPRESS=1` stops the installer before onboarding; neither path opens interactive provider selection.
Set `NEMOCLAW_PROVIDER=install-vllm` when you need to bypass the prompt with the same explicit preview intent.

On a host that still meets the N1x identity checks, `rebuild` reuses the recorded route as explicit preview intent only when all of these conditions hold:

- The sandbox registry records provider `vllm-local`.
- The registry records model `nvidia/Qwen3.6-35B-A3B-NVFP4`.
- If the registry records `endpointUrl` as `null`, NemoClaw derives `http://host.openshell.internal:8000/v1`; otherwise, `endpointUrl` must equal that URL.
- The registry records `endpointSource` as `onboard` and `openshellDriver` as `docker`.
- The replacement keeps the recorded provider and model, then re-derives the canonical local endpoint.
- `hostLocalInferenceReceipt` is absent or `null`, or contains a canonical vLLM receipt for `host.openshell.internal:8000` and the same model.

NemoClaw v0.0.109 can omit `hostLocalInferenceReceipt` or record it as `null`.
Matching sandboxes from that release remain eligible only when every condition above passes.
You do not need to set `NEMOCLAW_PROVIDER` again for a matching rebuild.
Any unmet condition stops readiness preflight before NemoClaw deletes the existing sandbox.
A malformed, non-vLLM, or conflicting receipt also stops readiness preflight before deletion.
This reuse waives only the pending physical-validation finding and does not change the Deferred status.

<Warning title="N1x Express Qualification Pending">
Physical CUDA and CDI checks passed on one N1x host.
The full NemoClaw Express E2E test is still pending, so the N1x platform status remains Deferred.
Expand Down
2 changes: 2 additions & 0 deletions src/lib/actions/sandbox/rebuild-gpu-opt-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export type RebuildRecreateOnboardOpts = {
rebuildRegistryInferenceRoute?: RebuildRouteHandoff;
rebuildProviderReconfigure?: RebuildProviderReconfigureHandoff;
providerRecoveryReceipt?: ProviderRecoveryReceipt;
/** Recorded managed-vLLM intent admitted only by the N1x readiness exception. */
allowDeferredN1xManagedVllm?: true;
/** Target-scoped authority admitted by the authoritative rebuild preflight. */
rebuildGatewayAuthority?: CheckpointGatewayAuthority;
preparedImageRebuild?: PreparedImageRebuildHandoff;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

const mocks = vi.hoisted(() => ({
bail: vi.fn(),
preflightAuthoritativeOnboardRuntime: vi.fn(async (..._args: unknown[]) => false),
prepareManagedWorkloadRebuildHandoff: vi.fn(),
prepareSandboxWorkloadSourceFromRebuildHandoff: vi.fn(),
prepareRebuildTargetConfig: vi.fn(),
Expand Down Expand Up @@ -33,7 +34,7 @@ vi.mock("../../onboard/workload/runtime", () => ({
vi.mock("./rebuild-target-preflight", async (importOriginal) => ({
...(await importOriginal<typeof import("./rebuild-target-preflight")>()),
hydrateMessagingConfigForRebuild: vi.fn(),
preflightAuthoritativeOnboardRuntime: vi.fn(async () => false),
preflightAuthoritativeOnboardRuntime: mocks.preflightAuthoritativeOnboardRuntime,
prepareRebuildRecreateOptions: mocks.prepareRebuildRecreateOptions,
prepareRebuildTargetConfig: mocks.prepareRebuildTargetConfig,
stageRebuildHermesDashboardConfig: vi.fn(() => true),
Expand All @@ -50,7 +51,64 @@ vi.mock("./rebuild-messaging-conflict-preflight", () => ({
import { managedRebuildProfileDependencies } from "./agents/managed-workload-rebuild-profile";
import { prepareRebuildTargetPreflights } from "./rebuild-preflight-target-phase";

describe("managed workload rebuild context-window preflight", () => {
describe("prepareRebuildTargetPreflights", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.prepareManagedWorkloadRebuildHandoff.mockResolvedValue(null);
mocks.preflightAuthoritativeOnboardRuntime.mockResolvedValue(false);
});

async function prepareN1xTarget(endpointSource: "onboard" | "inference-set") {
const resumeConfig = {
provider: "vllm-local",
model: "nvidia/Qwen3.6-35B-A3B-NVFP4",
preferredInferenceApi: "openai-completions",
pinEndpoint: true,
endpointUrl: null,
compatibleEndpointReasoning: null,
compatibleEndpointReasoningEffort: null,
registryInferenceRoute: null,
};
mocks.prepareRebuildTargetConfig.mockReturnValue({
agentDefinition: {},
resumeConfig,
durableConfig: {
toolDisclosure: "progressive",
dcodeAutoApprovalMode: "disabled",
webSearchConfig: null,
},
credentialEnv: null,
fromDockerfile: false,
hermesToolGateways: [],
});
mocks.prepareRebuildRecreateOptions.mockReturnValue({
controlUiPort: 18_789,
targetGatewayName: "nemoclaw",
toolDisclosure: "progressive",
dcodeAutoApprovalMode: "disabled",
observabilityEnabled: false,
});

await prepareRebuildTargetPreflights({
sandboxName: "my-assistant",
sandboxEntry: {
name: "my-assistant",
agent: "openclaw",
gatewayName: "nemoclaw",
openshellDriver: "docker",
provider: resumeConfig.provider,
model: resumeConfig.model,
endpointUrl: "http://host.openshell.internal:8000/v1",
endpointSource,
} as never,
rebuildAgent: "openclaw",
autoYes: true,
log: vi.fn(),
bail: mocks.bail as never,
});
return mocks.preflightAuthoritativeOnboardRuntime.mock.calls[0]?.[2];
}

it("resolves the Ollama context window through target preparation", async () => {
const catalogHandoff = {
agent: "openclaw",
Expand Down Expand Up @@ -123,4 +181,18 @@ describe("managed workload rebuild context-window preflight", () => {
).resolves.toBeNull();
expect(mocks.resolveContextWindowForModel).toHaveBeenCalledWith("ollama-local", "qwen3.5:9b");
});

it("passes exact legacy N1x intent into authoritative readiness (#9292)", async () => {
const readinessOptions = await prepareN1xTarget("onboard");

expect(readinessOptions).toEqual(
expect.objectContaining({ allowDeferredN1xManagedVllm: true }),
);
});

it("withholds N1x intent for a mismatched endpoint source (#9292)", async () => {
const readinessOptions = await prepareN1xTarget("inference-set");

expect(readinessOptions).not.toHaveProperty("allowDeferredN1xManagedVllm");
});
});
2 changes: 2 additions & 0 deletions src/lib/actions/sandbox/rebuild-preflight-target-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
prepareRebuildTargetConfig,
type RebuildTargetConfig,
stageRebuildHermesDashboardConfig,
stageRecordedManagedVllmIntent,
} from "./rebuild-target-preflight";

/** Upper bound on how long a minted provider-recovery receipt stays valid. */
Expand Down Expand Up @@ -233,6 +234,7 @@ export async function prepareRebuildTargetPreflights(args: {
recreateOptions.observabilityEnabled =
requestedObservabilityEnabled ?? recreateOptions.observabilityEnabled;
recreateOptions.observabilityRequestedExplicitly = requestedObservabilityEnabled !== undefined;
stageRecordedManagedVllmIntent(recreateOptions, sandboxEntry, resumeConfig);
if (
!stageRebuildHermesDashboardConfig(
rebuildAgent,
Expand Down
1 change: 1 addition & 0 deletions src/lib/actions/sandbox/rebuild-target-preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export {
hydrateMessagingConfigForRebuild,
prepareRebuildRecreateOptions,
stageRebuildHermesDashboardConfig,
stageRecordedManagedVllmIntent,
} from "./rebuild-target-staging";
42 changes: 41 additions & 1 deletion src/lib/actions/sandbox/rebuild-target-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({
ensureValidatedWebSearchCredential: vi.fn(),
isDockerDesktopWslRuntime: vi.fn(),
isLinuxDockerDriverGatewayEnabled: vi.fn(),
preflightAuthoritativeRebuildTarget: vi.fn(),
preflightRebuildCredentials: vi.fn(),
readGatewayProviderMetadata: vi.fn(),
runOpenshell: vi.fn(),
Expand All @@ -30,6 +31,7 @@ vi.mock("../../onboard/gateway-provider-metadata", async (importOriginal) => {
vi.mock("./rebuild-onboard-dependencies", () => ({
rebuildOnboardDependencies: {
ensureValidatedWebSearchCredential: mocks.ensureValidatedWebSearchCredential,
preflightAuthoritativeRebuildTarget: mocks.preflightAuthoritativeRebuildTarget,
},
}));

Expand All @@ -52,8 +54,12 @@ vi.mock("./rebuild-credential-preflight", () => ({
import * as rebuildImagePreflight from "./rebuild-custom-image-preflight";
import type { RebuildSandboxEntry } from "./rebuild-flow-helpers";
import type { RebuildRecreateOnboardOpts } from "./rebuild-gpu-opt-out";
import type { RebuildResumeConfig } from "./rebuild-resume-config";
import type { RebuildTargetConfig } from "./rebuild-target-config";
import { preflightRebuildTargetRuntime } from "./rebuild-target-runtime";
import {
preflightAuthoritativeOnboardRuntime,
preflightRebuildTargetRuntime,
} from "./rebuild-target-runtime";

const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!;
const TARGET = {
Expand Down Expand Up @@ -189,6 +195,40 @@ describe("preflightRebuildTargetRuntime GPU route", () => {
});
});

describe("authoritative rebuild readiness", () => {
it("passes recorded managed-vLLM intent to the pre-delete readiness gate (#9292)", async () => {
const authority = { checkpoint: "gateway-authority" };
mocks.preflightAuthoritativeRebuildTarget.mockResolvedValue(authority);
const recreateOptions = {
...RECREATE_OPTIONS,
allowDeferredN1xManagedVllm: true,
} as RebuildRecreateOnboardOpts;
const bail = vi.fn((message: string): never => {
throw new Error(message);
});

await expect(
preflightAuthoritativeOnboardRuntime(
"alpha",
{ provider: "vllm-local", model: "test-model" } as RebuildResumeConfig,
recreateOptions,
bail,
),
).resolves.toBe(true);

expect(mocks.preflightAuthoritativeRebuildTarget).toHaveBeenCalledWith(
expect.objectContaining({
allowDeferredN1xManagedVllm: true,
provider: "vllm-local",
model: "test-model",
sandboxName: "alpha",
}),
);
expect(recreateOptions.rebuildGatewayAuthority).toBe(authority);
expect(bail).not.toHaveBeenCalled();
});
});

describe("preflightRebuildTargetRuntime web search credential", () => {
const WEB_SEARCH_TARGET = {
...TARGET,
Expand Down
32 changes: 32 additions & 0 deletions src/lib/actions/sandbox/rebuild-target-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

import { hydrateMessagingChannelConfig } from "../../messaging-channel-config";
import { isRecordedN1xManagedVllmRebuildEligible } from "../../domain/sandbox/n1x-managed-vllm-rebuild";
import { getStoredMessagingChannelConfig } from "../../onboard/messaging-config";
import {
createRebuildRouteHandoff,
type RegistryInferenceRoute,
} from "../../onboard/rebuild-route-handoff";
import { parseHostLocalInferenceReceipt } from "../../onboard/runtime-provider/host-local-inference";
import type { SandboxBaseImageResolutionMetadata } from "../../sandbox-base-image";
import * as onboardSession from "../../state/onboard-session";
import type { RebuildBail } from "./rebuild-credential-preflight";
Expand Down Expand Up @@ -84,6 +86,36 @@ export function stageRebuildHermesDashboardConfig(
return true;
}

/** Stage the recorded N1x decision without moving action state into the domain layer. */
export function stageRecordedManagedVllmIntent(
recreateOptions: Pick<RebuildRecreateOnboardOpts, "allowDeferredN1xManagedVllm">,
sandboxEntry: Pick<
RebuildSandboxEntry,
| "provider"
| "model"
| "endpointUrl"
| "endpointSource"
| "openshellDriver"
| "hostLocalInferenceReceipt"
>,
rebuildSelection: {
provider: string;
model: string;
pinEndpoint: boolean;
endpointUrl: string | null;
},
): void {
if (
isRecordedN1xManagedVllmRebuildEligible(
sandboxEntry,
rebuildSelection,
parseHostLocalInferenceReceipt,
)
) {
recreateOptions.allowDeferredN1xManagedVllm = true;
}
}

export function hydrateMessagingConfigForRebuild(
sandboxName: string,
log: (msg: string) => void,
Expand Down
Loading
Loading