Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2672339
fix(portable): recover published Hermes inference
senthilr-nv Aug 27, 2026
aa62e9b
test(portable): harden inference receipt snapshot
senthilr-nv Aug 27, 2026
7cb436b
fix(inference): protect published resume receipts
senthilr-nv Aug 27, 2026
93e8c5b
fix(inference): recover portable registry after session restart
senthilr-nv Aug 27, 2026
045445d
fix(inference): preserve portable recovery failures
senthilr-nv Aug 27, 2026
46b42f3
fix(inference): reconcile timed-out registry starts
senthilr-nv Aug 28, 2026
6054e8f
fix(onboard): reconcile portable registry start
senthilr-nv Aug 28, 2026
ab4d34d
fix(onboard): settle portable registry start
senthilr-nv Aug 28, 2026
122d8e1
fix(onboard): classify portable inference recovery failures
senthilr-nv Aug 28, 2026
67f0982
test(onboard): pin portable registry authority fixture
senthilr-nv Aug 28, 2026
a0799e9
Merge branch 'main' into codex/10423-hermes-inference-session-recovery
cv Aug 28, 2026
150a3f9
Merge remote-tracking branch 'upstream/main' into codex/10423-hermes-…
senthilr-nv Aug 28, 2026
b79c6f0
Merge branch 'main' into codex/10423-hermes-inference-session-recovery
prekshivyas Aug 28, 2026
cb4e799
fix(hermes): requalify published inference runtime
senthilr-nv Aug 28, 2026
9f9513f
Merge remote-tracking branch 'upstream/codex/10423-hermes-inference-s…
senthilr-nv Aug 28, 2026
c68ccda
ci: retrigger PR review advisor
cv Aug 28, 2026
13b057a
fix(hermes): recover portable forwards after session restart
senthilr-nv Aug 28, 2026
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
3 changes: 3 additions & 0 deletions src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ describe("connectSandbox flow", () => {
Array.isArray(args) ? args[0] === "inference" && args[1] === "set" : false,
),
).toBe(false);
expect(harness.recoverHermesPortableOllamaInferenceSpy).toHaveBeenCalledOnce();
expect(harness.recoverPortableDemoLifecycleSpy).toHaveBeenCalled();
});

Expand Down Expand Up @@ -1159,6 +1160,7 @@ describe("connectSandbox flow", () => {
expect(harness.getSandboxDockerRuntimeSpy).not.toHaveBeenCalled();
expect(harness.dockerStartSpy).not.toHaveBeenCalled();
expect(harness.publishLaunchReadinessSpy).not.toHaveBeenCalled();
expect(harness.recoverHermesPortableOllamaInferenceSpy).toHaveBeenCalledOnce();
});

it("rejects accepted probe evidence when schema-5 authority disappears (#9203)", async () => {
Expand Down Expand Up @@ -1228,6 +1230,7 @@ describe("connectSandbox flow", () => {
expect(harness.preflightVllmSpy).not.toHaveBeenCalled();
expect(harness.readSandboxConfigSpy).not.toHaveBeenCalled();
expect(harness.writeSandboxConfigSpy).not.toHaveBeenCalled();
expect(harness.recoverHermesPortableOllamaInferenceSpy).not.toHaveBeenCalled();
expect(sandboxVersion.checkAgentVersion).not.toHaveBeenCalled();
expect(brokerSpy).not.toHaveBeenCalled();
const connectCall = harness.runSandboxExecChildSpy.mock.calls.find(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

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

import {
connectModulePath,
createConnectHarness,
requireDist,
} from "../../../../test/support/connect-flow-test-harness";

describe("Hermes Portable connect recovery errors", () => {
const originalStdoutIsTty = process.stdout.isTTY;

beforeEach(() => {
process.env.NEMOCLAW_TEST_NO_SLEEP = "1";
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true });
vi.spyOn(process, "exit").mockImplementation(((code?: number | string | null) => {
throw new Error(`process.exit(${code ?? 0})`);
}) as never);
});

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
Object.defineProperty(process.stdout, "isTTY", {
configurable: true,
value: originalStdoutIsTty,
});
delete process.env.NEMOCLAW_TEST_NO_SLEEP;
delete require.cache[requireDist.resolve(connectModulePath)];
});

it("retains the route-unavailable retry boundary (#10423)", async () => {
const harness = createConnectHarness({
agentName: "hermes",
sessionAgent: { name: "hermes" },
inferenceProbeResponses: ["BROKEN 503"],
registryEntry: {
openshellDriver: "docker",
gatewayName: "nemoclaw",
lifecycleGeneration: "generation-1",
},
portableReceiptDisposition: { kind: "hermes", phase: "active" },
portableRecoveryResult: { kind: "already-running" },
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);
const output = harness.errorSpy.mock.calls.flat().join("\n");
expect(output).toContain(
"is unreachable. Resume the existing portable onboarding transaction or run `nemoclaw alpha doctor` before retrying",
);
expect(output).not.toContain("Hermes Portable inference recovery for 'alpha' failed");
});

it.each([
[
"authority drift",
"authority-drift",
"Recorded managed inference authority changed during recovery",
],
[
"runtime restoration uncertainty",
"runtime-restoration-unproved",
"could not prove restoration of the exact stopped managed inference runtime",
],
[
"registry restoration uncertainty",
"registry-restoration-unproved",
"could not prove restoration of the exact stopped Portable registry",
],
[
"an unclassified recovery failure",
"recovery-failed",
"Managed inference recovery stopped without a verified result",
],
] as const)(
"preserves the fixed %s class at the connect boundary (#10423)",
async (_label, hermesInferenceRecoveryFailure, expectedDetail) => {
const harness = createConnectHarness({
agentName: "hermes",
sessionAgent: { name: "hermes" },
hermesInferenceRecoveryFailure,
registryEntry: {
openshellDriver: "docker",
gatewayName: "nemoclaw",
lifecycleGeneration: "generation-1",
},
portableReceiptDisposition: { kind: "hermes", phase: "active" },
portableRecoveryResult: { kind: "already-running" },
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);
const output = harness.errorSpy.mock.calls.flat().join("\n");
expect(output).toContain("Hermes Portable inference recovery for 'alpha' failed");
expect(output).toContain(expectedDetail);
expect(output).toContain("Do not run another probe or launch");
expect(output).not.toContain("nested recovery diagnostic canary");
expect(output).not.toContain("before retrying");
expect(harness.recoverHermesPortableOllamaInferenceSpy).toHaveBeenCalledOnce();
},
);

it.each([
"REGISTRY_PREPARATION_AUTHORITY",
"REGISTRY_PREPARATION_START_DISPATCH",
"REGISTRY_PREPARATION_SETTLEMENT_CURRENTNESS",
"REGISTRY_PREPARATION_NETWORK_INSPECTION",
"REGISTRY_PREPARATION_PINNED_REGISTRY_INSPECTION",
"REGISTRY_PREPARATION_PENDING_DEADLINE",
"REGISTRY_PREPARATION_POSTCONDITION",
"RUNTIME_AUTHORITY",
"LIFECYCLE_AUTHORITY",
"PRIVATE_PUBLICATION_AUTHORITY",
"EXACT_RUNTIME_INSPECTION",
] as const)("prints only the fixed %s recovery boundary (#10423)", async (phase) => {
const harness = createConnectHarness({
agentName: "hermes",
sessionAgent: { name: "hermes" },
hermesInferenceRecoveryPhase: phase,
registryEntry: {
openshellDriver: "docker",
gatewayName: "nemoclaw",
lifecycleGeneration: "generation-1",
},
portableReceiptDisposition: { kind: "hermes", phase: "active" },
portableRecoveryResult: { kind: "already-running" },
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);
const output = harness.errorSpy.mock.calls.flat().join("\n");
expect(output).toContain(`Managed inference recovery stopped at boundary ${phase}`);
expect(output).toContain("Do not run another probe or launch");
expect(output).not.toContain("nested recovery diagnostic canary");
expect(output).not.toContain("before retrying");
expect(harness.recoverHermesPortableOllamaInferenceSpy).toHaveBeenCalledOnce();
});
});
Loading
Loading