Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d0f21ef
perf(cli): add secure launch readiness leases
senthilr-nv Aug 13, 2026
7ceac57
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
2bba8cc
Merge branch 'main' into codex/8942-launch-readiness-lease
cv Aug 13, 2026
d0c657b
perf(cli): add secure launch readiness leases
senthilr-nv Aug 13, 2026
2a4cad5
fix(cli): harden launch readiness evidence
senthilr-nv Aug 13, 2026
760c10e
fix(cli): disable readiness leases on unsupported hosts
senthilr-nv Aug 13, 2026
51cc49f
merge(cli): preserve protected PR ancestry
senthilr-nv Aug 13, 2026
648e273
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
9b49429
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
31df5ed
fix(ci): register launch readiness E2E parity
prekshivyas Aug 13, 2026
cd9af73
test(launch): keep readiness cases linear
prekshivyas Aug 13, 2026
bf0bfe7
fix(ci): satisfy launch readiness guardrails
prekshivyas Aug 13, 2026
d1cf319
test(ci): provision launch readiness authority
prekshivyas Aug 13, 2026
34a8956
Merge branch 'main' into codex/8942-launch-readiness-lease
senthilr-nv Aug 13, 2026
bda6b08
merge: refresh PR branch with upstream main
senthilr-nv Aug 13, 2026
4c9994c
fix(cli): complete launch readiness review fixes
senthilr-nv Aug 13, 2026
ed4bdd5
merge(main): refresh pull request base
senthilr-nv Aug 13, 2026
928ea7f
test(cli): keep readiness process cases linear
senthilr-nv Aug 13, 2026
574b655
test(launch): fix Linux readiness CI fixtures
senthilr-nv Aug 13, 2026
f4da8df
test(connect): mock owner-scoped readiness probes
senthilr-nv Aug 13, 2026
a454ac8
test(cli): scope launch readiness fixtures
senthilr-nv Aug 13, 2026
db825a3
perf(cli): reconcile launch readiness with main
senthilr-nv Aug 13, 2026
26f562e
merge(main): preserve reviewed launch readiness ancestry
senthilr-nv Aug 13, 2026
cc40d1d
test(cli): stabilize readiness authority fixtures
senthilr-nv Aug 13, 2026
4a54379
merge(main): reconcile PR 8951 with current main
senthilr-nv Aug 13, 2026
58e1c7b
Merge branch 'main' into codex/8942-launch-readiness-lease
rsliter Aug 13, 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
22 changes: 11 additions & 11 deletions ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"defaultMax": 20,
"maxByFile": {
"src/lib/actions/sandbox/mcp-bridge-contracts.ts": 26,
"src/lib/actions/sandbox/process-recovery.ts": 26,
"src/lib/actions/sandbox/process-recovery.ts": 27,
"src/lib/adapters/docker/index.ts": 43,
"src/lib/adapters/openshell/client.ts": 23,
"src/lib/adapters/openshell/resolve.ts": 27,
"src/lib/adapters/openshell/runtime.ts": 52,
"src/lib/adapters/openshell/timeouts.ts": 37,
"src/lib/agent/defs.ts": 32,
"src/lib/adapters/openshell/runtime.ts": 53,
"src/lib/adapters/openshell/timeouts.ts": 38,
"src/lib/agent/defs.ts": 33,
"src/lib/cli/branding.ts": 86,
"src/lib/cli/nemoclaw-oclif-command.ts": 106,
"src/lib/cli/terminal-style.ts": 43,
Expand All @@ -20,15 +20,15 @@
"src/lib/core/url-utils.ts": 28,
"src/lib/core/wait.ts": 35,
"src/lib/credentials/store.ts": 46,
"src/lib/inference/config.ts": 29,
"src/lib/inference/config.ts": 30,
"src/lib/inference/web-search.ts": 21,
"src/lib/messaging/channels/index.ts": 25,
"src/lib/onboard/gateway-binding.ts": 49,
"src/lib/onboard/gateway-binding.ts": 50,
"src/lib/runner.ts": 88,
"src/lib/security/redact.ts": 52,
"src/lib/state/onboard-session.ts": 36,
"src/lib/state/registry.ts": 97,
"src/lib/state/state-root.ts": 21,
"src/lib/state/registry.ts": 98,
"src/lib/state/state-root.ts": 22,
"src/lib/subprocess-env.ts": 24,
"src/lib/validation.ts": 25
}
Expand All @@ -37,7 +37,7 @@
"defaultMax": 20,
"maxByFile": {
"src/lib/actions/inference-set.ts": 32,
"src/lib/actions/sandbox/connect.ts": 38,
"src/lib/actions/sandbox/connect.ts": 39,
"src/lib/actions/sandbox/destroy.ts": 29,
"src/lib/actions/sandbox/doctor.ts": 30,
"src/lib/actions/sandbox/status-snapshot.ts": 20,
Expand All @@ -58,8 +58,8 @@
"maxRootFiles": {
"src/lib/onboard": 307,
"src/lib/actions": 19,
"src/lib/actions/sandbox": 182,
"src/lib/state": 37,
"src/lib/actions/sandbox": 183,
"src/lib/state": 38,
"src/lib/inference": 63,
"scripts": 47
}
Expand Down
3 changes: 2 additions & 1 deletion docs/get-started/quickstart-hermes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ Review the [Prerequisites](prerequisites) before you begin.
nemohermes launch my-hermes
```

`nemohermes launch` runs the same preflight as `connect`, then starts `hermes` in your terminal.
`nemohermes launch` runs the complete preflight or validates an existing fixed 24-hour launch-readiness lease, then starts `hermes` in your terminal.
If the recorded configuration or live runtime has changed, NemoClaw runs the complete preflight automatically.
To open a sandbox shell first and start Hermes yourself, run these commands instead.

```bash
Expand Down
3 changes: 2 additions & 1 deletion docs/get-started/quickstart-langchain-deepagents-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ Review the [Prerequisites](prerequisites) before you begin.
nemo-deepagents launch my-deepagents
```

`nemo-deepagents launch` runs the same preflight as `connect`, then starts `dcode` in your terminal.
`nemo-deepagents launch` runs the complete preflight or validates an existing fixed 24-hour launch-readiness lease, then starts `dcode` in your terminal.
If the recorded configuration or live runtime has changed, NemoClaw runs the complete preflight automatically.
To open a sandbox shell first and start `dcode` yourself, run these commands instead.

```bash
Expand Down
3 changes: 2 additions & 1 deletion docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ Review the [Prerequisites](prerequisites) before you begin.
nemoclaw launch my-assistant
```

`nemoclaw launch` runs the same preflight as `connect`, then starts `openclaw tui` in your terminal.
`nemoclaw launch` runs the complete preflight or validates an existing fixed 24-hour launch-readiness lease, then starts `openclaw tui` in your terminal.
If the recorded configuration or live runtime has changed, NemoClaw runs the complete preflight automatically.
To open a sandbox shell first and start the TUI yourself, run these commands instead.

```bash
Expand Down
20 changes: 20 additions & 0 deletions docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,26 @@ Use `nemo-deepagents <sandbox-name> status`, `logs`, `connect`, and `rebuild` fo
If the terminal runtime reports degraded health, rebuild the sandbox instead of using `recover` or `gateway restart`.
</AgentOnly>

### Understand Launch Readiness Leases

A successful complete preflight for `$$nemoclaw launch <sandbox-name>` can publish a credential-free launch-readiness lease.
Infrastructure can publish the same evidence with `$$nemoclaw <sandbox-name> connect --probe-only`.
The lease has a fixed 24-hour lifetime that repeated launches do not extend.
Leaving the agent with `/exit` does not revoke it, and users do not refresh it manually.

During the lease, `launch` still verifies the owning OpenShell gateway, exact live sandbox identity, registry and agent configuration, effective policy, inference route, required forwards, and semantic runtime health.
When those checks pass, it can skip duplicate recovery, readiness polling, and inference-route repair.
The lease is not a health guarantee or repair authority.
Comment thread
senthilr-nv marked this conversation as resolved.

Missing, unsafe, malformed, expired, mismatched, changed, or unhealthy evidence makes `launch` run the complete preflight automatically.
If that preflight succeeds before the lease expires, replacement evidence keeps the original start and expiry time.
After expiry, a successful complete preflight starts a new 24-hour lease only when publication succeeds.

Infrastructure must run `connect --probe-only` as the same final numeric desktop user that later runs `launch`.
Run it after the final durable home and state volume is mounted and after policy and network provisioning is complete.
NemoClaw rejects evidence after a bound sandbox, configuration, policy, or network identity changes.
Deployment ordering remains responsible for external changes that OpenShell and NemoClaw cannot observe.

<AgentOnly variant="openclaw,hermes">
### Host OpenShell Gateway Versus In-Sandbox Agent Recovery

Expand Down
39 changes: 33 additions & 6 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1012,9 +1012,29 @@ $$nemoclaw use <name> --json
Connect to a sandbox and start its agent in one host-side command.
Use it instead of running `$$nemoclaw <name> connect` and then typing the agent command inside the sandbox.

`launch` runs the same preflight as [`$$nemoclaw <name> connect`](#$$nemoclaw-name-connect), including the readiness wait, in-sandbox agent process recovery, and inference-route reconciliation.
It runs every check in that shared preflight.
After the preflight, `launch` starts the sandbox's agent in your terminal instead of opening a sandbox shell.
`launch` runs the complete preflight from [`$$nemoclaw <name> connect`](#$$nemoclaw-name-connect) when no launch-readiness lease is usable.
That path includes the readiness wait, in-sandbox agent process recovery, and inference-route reconciliation.
A successful complete preflight can publish a credential-free launch-readiness lease with a fixed 24-hour lifetime.

During that lease, another `launch` still verifies these conditions:

- The owning OpenShell gateway reports the exact sandbox identity in the `Ready` or `Running` state.
- The sandbox registry, agent manifest, interactive command, policy intent, and effective parsed OpenShell network policy match the recorded identity.
- The recorded inference selection matches the live route, and `inference.local` passes its semantic probe when inference is configured.
- The agent runtime and its required host-side forwards pass their semantic health checks.

After these checks pass, `launch` can skip duplicate recovery, readiness polling, and inference-route repair.
The lease does not replace a health check or authorize repair.
Missing, expired, malformed, inaccessible, mismatched, or unhealthy evidence makes `launch` run the complete preflight automatically.
A failed live check never becomes a successful launch because a lease exists.

The 24-hour lifetime does not extend when you launch repeatedly.
Exiting the agent with `/exit` does not revoke the lease.
If state changes before expiry, NemoClaw fences the old evidence and runs the complete preflight.
A successful preflight in that interval keeps the original start and expiry time.
After expiry, a successful complete preflight starts a new 24-hour lease only when publication succeeds.
You do not create or refresh this lease manually, and `launch` has no lease-control flags.
After lease validation or the automatic fallback that runs the complete preflight, `launch` starts the sandbox's agent in your terminal instead of opening a sandbox shell.

The agent command comes from the sandbox's agent manifest.
If the sandbox registry names a non-OpenClaw agent without a readable local agent manifest, `launch` exits before starting an in-sandbox command.
Expand Down Expand Up @@ -1117,11 +1137,18 @@ $$nemoclaw my-assistant connect [--probe-only]
$$nemoclaw connect
```

The `--probe-only` flag waits for the sandbox to become ready, verifies or repairs its in-sandbox agent process and host-side forward, and exits without opening a shell.
The `--probe-only` flag is the infrastructure producer for launch-readiness evidence.
It validates a usable lease and exits without duplicate recovery.
Otherwise, it fences prior evidence, waits for the sandbox, verifies or repairs its in-sandbox agent process and host-side forwards, and publishes evidence only after every probe succeeds.
It rechecks the sandbox on its recorded OpenShell gateway after the readiness wait and never restarts the shared host gateway.
Use it for health checks and scripted readiness probes.
A runtime failure and an evidence fencing or publication failure both exit nonzero with different diagnostics.

Use [`$$nemoclaw launch <name>`](#$$nemoclaw-launch-name) when you want the same preflight followed by the agent instead of a sandbox shell.
Infrastructure must run the command as the same final numeric desktop user that later runs `launch`.
Run it only after the final durable home and state volume is mounted and after policy and network provisioning is complete.
Do not use a graphical or login-session identifier as the deployment ordering boundary.
Run it for health checks and scripted readiness probes; users continue to run only `$$nemoclaw launch <name>`.

Use [`$$nemoclaw launch <name>`](#$$nemoclaw-launch-name) when you want launch-readiness validation, an automatic fallback that runs the complete preflight, and then the agent instead of a sandbox shell.

### `$$nemoclaw <name> exec`

Expand Down
25 changes: 25 additions & 0 deletions scripts/test-launch-readiness-lease.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

if [[ "$(uname -s)" != "Linux" ]]; then
echo "Launch-readiness lease acceptance requires Linux and the util-linux PTY driver." >&2
exit 2
fi

if [[ $# -ne 1 || -z "$1" ]]; then
echo "Usage: scripts/test-launch-readiness-lease.sh <openclaw-sandbox>" >&2
exit 2
fi

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"

npm run clean:cli
npm run build:cli
NEMOCLAW_RUN_LIVE_E2E=1 \
NEMOCLAW_ACCEPTANCE_SANDBOX="$1" \
npx vitest run --project e2e-live \
test/e2e/live/launch-readiness-lease-acceptance.test.ts
77 changes: 77 additions & 0 deletions src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,83 @@ describe("connectSandbox flow", () => {
);
});

it("probe-only accepts healthy launch evidence without duplicate recovery or publication (#8942)", async () => {
const sb = { name: "alpha", agent: "openclaw", provider: null, model: null, policies: [] };
const harness = createConnectHarness({
readinessDecision: {
kind: "accepted",
category: "accepted",
agent: { name: "openclaw" },
sb,
},
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

expect(harness.checkAndRecoverSpy).not.toHaveBeenCalled();
expect(harness.ensureLiveSandboxSpy).not.toHaveBeenCalled();
expect(harness.publishLaunchReadinessSpy).not.toHaveBeenCalled();
expect(harness.logSpy.mock.calls.flat().join("\n")).toContain(
"Probe complete: launch readiness is healthy for 'alpha'.",
);
});

it("probe-only refuses runtime recovery when prior evidence cannot be fenced (#8942)", async () => {
const harness = createConnectHarness({
readinessDecision: {
kind: "fallback",
category: "unsafe",
fence: null,
gatewayName: "nemoclaw",
gatewayPort: 8080,
fenceFailed: true,
},
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);

expect(harness.checkAndRecoverSpy).not.toHaveBeenCalled();
expect(harness.errorSpy.mock.calls.flat().join("\n")).toContain(
"complete probe and recovery did not run because prior launch-readiness evidence could not be fenced",
);
});

it("probe-only distinguishes completed recovery from final evidence failure (#8942)", async () => {
const harness = createConnectHarness({
readinessPublicationResult: { kind: "evidence-failed" },
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);

expect(harness.checkAndRecoverSpy).toHaveBeenCalled();
expect(harness.publishLaunchReadinessSpy).toHaveBeenCalled();
expect(harness.errorSpy.mock.calls.flat().join("\n")).toContain(
"complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published",
);
});

it("probe-only reports final semantic validation failure as a runtime failure (#8942)", async () => {
const harness = createConnectHarness({
readinessPublicationResult: { kind: "validation-failed", category: "health" },
});

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);

expect(harness.checkAndRecoverSpy).toHaveBeenCalled();
expect(harness.errorSpy.mock.calls.flat().join("\n")).toContain(
"final launch-readiness validation failed due to health",
);
expect(harness.errorSpy.mock.calls.flat().join("\n")).not.toContain(
"complete probe and recovery succeeded",
);
});

it("probe-only mode exits before reporting success when inference.local returns no trusted result (#8502)", async () => {
const harness = createConnectHarness({
registryEntry: {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/connect-route-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ describe("connectSandbox route lifecycle", () => {
});

it("repairs a WSL Ollama route without requiring an auth proxy token", async () => {
vi.stubEnv("WSL_DISTRO_NAME", "Ubuntu");
const harness = createConnectHarness({
inferenceGetOutput: "Gateway inference:\n Provider: ollama-local\n Model: qwen3:0.6b\n",
inferenceProbeResponses: ["BROKEN 503", "BROKEN 503", "OK 200", "OK 200"],
isWsl: true,
registryEntry: {
model: "qwen3:0.6b",
provider: "ollama-local",
Expand Down
Loading
Loading