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
2 changes: 1 addition & 1 deletion docs/reference/commands-nemohermes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ The exit code is the remote command's exit code.

### `nemohermes <name> agent`

The `agent` wrapper is an OpenClaw passthrough and rejects Hermes sandboxes with guidance for the Hermes HTTP API.
The `agent` wrapper rejects Hermes sandboxes with guidance for the Hermes HTTP API.
Hermes sandboxes expose an OpenAI-compatible API on port `8642` inside the sandbox, so non-interactive use does not need a wrapper command.

Forward the port and POST chat completions directly:
Expand Down
23 changes: 15 additions & 8 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -546,34 +546,41 @@ The exit code is the remote command's exit code.

<AgentOnly variant="openclaw">

Run one OpenClaw agent turn non-interactively in a running sandbox.
This command forwards every argument verbatim to `openclaw agent ...` inside the sandbox via `openshell sandbox exec`, with `HOME=/sandbox` so the addressed agent profile resolves the same way as `connect`.
Run one agent turn non-interactively in a running sandbox.
For OpenClaw sandboxes, this command forwards arguments to `openclaw agent ...` inside the sandbox via `openshell sandbox exec`, with `HOME=/sandbox` so the addressed agent profile resolves the same way as `connect`.
For terminal-runtime sandboxes, NemoClaw forwards arguments to the manifest-declared interactive command; LangChain Deep Agents Code sandboxes run `dcode ...`.
Use this when driving the sandbox programmatically from another process (CI job, multi-agent platform, evaluation harness) rather than from an interactive terminal.

All flags accepted by the in-sandbox OpenClaw CLI are forwarded verbatim, so the upstream surface stays the single source of truth.
All flags accepted by the selected in-sandbox agent CLI are forwarded verbatim, so the upstream surface stays the single source of truth.

Every invocation must include at least one target selector — `--agent`, `--session-id`, `--session-key`, or `--to` — so the wrapper never falls back to the unspecified default-session behaviour. Conflict resolution between multiple selectors is delegated to the in-sandbox `openclaw agent` argv contract; the host-side guard only checks presence.
OpenClaw invocations must include at least one target selector — `--agent`, `--session-id`, `--session-key`, or `--to` — so the wrapper never falls back to the unspecified default-session behaviour. Conflict resolution between multiple selectors is delegated to the in-sandbox `openclaw agent` argv contract; the host-side guard only checks presence.

```bash
$$nemoclaw my-assistant agent --agent work -m "Summarise README.md"
$$nemoclaw my-assistant agent --agent work -m "Status update?"
$$nemoclaw my-assistant agent --session-id review-42 -m "Any new findings?"
$$nemoclaw my-assistant agent --session-key intake-42 --json -m 'ping'
$$nemoclaw dcode-sandbox agent --help
$$nemoclaw dcode-sandbox agent -n "Summarize this repository"
```

The wrapper inherits the remote command's exit code, so host-side pipelines can branch on it. Streaming forwards whatever `openclaw agent` already emits on `stdout`; the wrapper adds no buffering.
The wrapper inherits the remote command's exit code, so host-side pipelines can branch on it.
Streaming forwards whatever the in-sandbox agent command emits on `stdout`; the wrapper adds no buffering.

Common upstream flags include `-m <text>`, `--session-id <id>`, `--agent <id>`, `--model <id>`, `--thinking <level>`, `--json`, `--deliver`, `--reply-channel <channel>`, and `--timeout <seconds>`. Run `$$nemoclaw <name> agent --help` for the wrapper-level summary, or invoke `$$nemoclaw <name> exec -- openclaw agent --help` to view the upstream OpenClaw help text directly.
Common OpenClaw flags include `-m <text>`, `--session-id <id>`, `--agent <id>`, `--model <id>`, `--thinking <level>`, `--json`, `--deliver`, `--reply-channel <channel>`, and `--timeout <seconds>`.
For OpenClaw sandboxes and registry fallbacks, `$$nemoclaw <name> agent --help` prints the wrapper-level summary locally.
Invoke `$$nemoclaw <name> exec -- openclaw agent --help` to view the upstream OpenClaw help text directly.
For registered terminal-runtime sandboxes, bare invocations and `--help` are forwarded to the terminal command, so a LangChain Deep Agents Code sandbox receives `dcode` for `$$nemoclaw <name> agent` and `dcode --help` for `$$nemoclaw <name> agent --help`.

Host-side validation runs before the sandbox dispatch:

- At least one target selector flag — `--agent`, `--session-id`, `--session-key`, or `--to` (in either `--flag value` or `--flag=value` form) — must be present. Invocations without a selector exit `2` and print `No target session selected` locally, without paying the in-sandbox dispatch cost.
- OpenClaw sandboxes and registry fallbacks must include at least one target selector flag — `--agent`, `--session-id`, `--session-key`, or `--to` (in either `--flag value` or `--flag=value` form). OpenClaw invocations without a selector exit `2` and print `No target session selected` locally, without paying the in-sandbox dispatch cost. Registered terminal-runtime sandboxes delegate bare invocations and help flags to the manifest command instead.
- If the sandbox is registered but not in a `Ready` or `Running` phase, the wrapper exits `1` and prints the documented recovery commands (`$$nemoclaw <name> recover`, `$$nemoclaw <name> rebuild --yes`, `$$nemoclaw onboard --resume`) rather than deferring the readiness rejection to `openshell sandbox exec`.

</AgentOnly>
<AgentOnly variant="hermes">

The `agent` wrapper is an OpenClaw passthrough and rejects Hermes sandboxes with guidance for the Hermes HTTP API.
The `agent` wrapper rejects Hermes sandboxes with guidance for the Hermes HTTP API.
Hermes sandboxes expose an OpenAI-compatible API on port `8642` inside the sandbox, so non-interactive use does not need a wrapper command.

Forward the port and POST chat completions directly:
Expand Down
17 changes: 6 additions & 11 deletions src/commands/sandbox/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ describe("SandboxAgentCommand oclif parse path", () => {
});
});

it("does not call runAgentPassthrough when --help follows the sandbox name", async () => {
it("passes --help after the sandbox name to agent-aware dispatch (#5790)", async () => {
await SandboxAgentCommand.run(["alpha", "--help"], rootDir);
expect(runAgentPassthroughMock).not.toHaveBeenCalled();
const help = logSpy.mock.calls.map((c: unknown[]) => String(c[0])).join("\n");
expect(help).toMatch(/openclaw agent/);
expect(runAgentPassthroughMock).toHaveBeenCalledWith("alpha", {
extraArgs: ["--help"],
});
});

it("does not call runAgentPassthrough when no sandbox name is supplied", async () => {
Expand All @@ -50,13 +50,8 @@ describe("SandboxAgentCommand oclif parse path", () => {
expect(runAgentPassthroughMock).not.toHaveBeenCalled();
});

it("prints wrapper help and does not dispatch on a bare no-args invocation (#5658)", async () => {
// `nemoclaw <name> agent` with no further args cannot succeed in-sandbox
// (openclaw agent requires -m), so short-circuit to wrapper help locally
// instead of paying sandbox-exec latency to surface an upstream error.
it("passes a bare sandbox invocation to agent-aware dispatch (#5790)", async () => {
await SandboxAgentCommand.run(["alpha"], rootDir);
expect(runAgentPassthroughMock).not.toHaveBeenCalled();
const help = logSpy.mock.calls.map((c: unknown[]) => String(c[0])).join("\n");
expect(help).toMatch(/openclaw agent/);
expect(runAgentPassthroughMock).toHaveBeenCalledWith("alpha", { extraArgs: [] });
});
});
19 changes: 4 additions & 15 deletions src/commands/sandbox/agent.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import {
hasAgentPassthroughHelpToken,
printAgentPassthroughHelp,
} from "../../lib/actions/sandbox/agent/passthrough-help";
import { printAgentPassthroughHelp } from "../../lib/actions/sandbox/agent/passthrough-help";
import { runAgentPassthrough } from "../../lib/actions/sandbox/agent/passthrough";
import { NemoClawCommand } from "../../lib/cli/nemoclaw-oclif-command";

export default class SandboxAgentCommand extends NemoClawCommand {
static id = "sandbox:agent";
static strict = false;
static summary = "Run one OpenClaw agent turn non-interactively in a sandbox";
static summary = "Run one agent turn non-interactively in a sandbox";
static description =
"Pass through to `openclaw agent` inside the sandbox via `openshell sandbox exec`. Stream the agent's response back to stdout without owning a TTY; useful for driving the sandbox from another process (CI job, multi-agent platform, evaluation harness). All flags accepted by the in-sandbox OpenClaw CLI are forwarded verbatim, including `-m <text>`, `--session-id <id>`, `--agent <id>`, `--json`, `--thinking <level>`, `--deliver`, and `--reply-channel`. Currently supported on OpenClaw sandboxes only; Hermes sandboxes exit non-zero with a redirect to the OpenAI-compatible API on port 8642 inside the sandbox.";
static usage = ["<name> [openclaw-agent-flags...]"];
"Pass through to the sandbox's registered agent command via `openshell sandbox exec`. OpenClaw sandboxes run `openclaw agent`; terminal-runtime sandboxes run their manifest-declared interactive command, such as `dcode` for LangChain Deep Agents Code. Stream the agent's response back to stdout without owning a TTY; useful for driving the sandbox from another process (CI job, multi-agent platform, evaluation harness). Hermes sandboxes exit non-zero with a redirect to the OpenAI-compatible API on port 8642 inside the sandbox.";
static usage = ["<name> [agent-flags...]"];
static examples = [
'<%= config.bin %> sandbox agent alpha --agent work -m "Summarise README.md"',
'<%= config.bin %> sandbox agent alpha --agent work -m "Status update?"',
Expand All @@ -34,14 +31,6 @@ export default class SandboxAgentCommand extends NemoClawCommand {
printAgentPassthroughHelp();
return;
}
// A bare `<name> agent` with no further args cannot succeed in-sandbox
// (`openclaw agent` requires `-m`), so treat it like a help request and
// print the wrapper summary locally instead of paying sandbox-exec latency
// only to surface an upstream "Missing required option -m" error (#5658).
if (extraArgs.length === 0 || hasAgentPassthroughHelpToken(extraArgs)) {
printAgentPassthroughHelp();
return;
}
await runAgentPassthrough(sandboxName, { extraArgs });
}
}
24 changes: 22 additions & 2 deletions src/lib/actions/sandbox/agent/passthrough-help.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

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

import { hasAgentPassthroughHelpToken } from "./passthrough-help";
import { hasAgentPassthroughHelpToken, printAgentPassthroughHelp } from "./passthrough-help";

describe("hasAgentPassthroughHelpToken", () => {
it("returns true for --help before the OpenClaw argv separator", () => {
Expand All @@ -20,3 +20,23 @@ describe("hasAgentPassthroughHelpToken", () => {
expect(hasAgentPassthroughHelpToken([])).toBe(false);
});
});

describe("printAgentPassthroughHelp", () => {
it("describes both OpenClaw and terminal-runtime passthroughs (#5790)", () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
let output = "";
try {
printAgentPassthroughHelp();
output = logSpy.mock.calls.map((call) => String(call[0])).join("\n");
} finally {
logSpy.mockRestore();
}

expect(output).toContain("[agent-flags...]");
expect(output).toContain("registered agent command");
expect(output).toContain("OpenClaw sandboxes run `openclaw agent ...`");
expect(output).toContain("terminal-runtime sandboxes run");
expect(output).toContain("`dcode ...`");
expect(output).not.toContain("OpenClaw sandboxes only");
});
});
29 changes: 14 additions & 15 deletions src/lib/actions/sandbox/agent/passthrough-help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,31 @@ export function hasAgentPassthroughHelpToken(args: readonly string[]): boolean {

export function printAgentPassthroughHelp(): void {
console.log("");
console.log(` Usage: ${CLI_NAME} <name> agent [openclaw-agent-flags...]`);
console.log(` Usage: ${CLI_NAME} <name> agent [agent-flags...]`);
console.log("");
console.log(
" Pass-through to `openclaw agent ...` inside the sandbox via `openshell sandbox exec`.",
" Pass-through to the sandbox's registered agent command via `openshell sandbox exec`.",
);
console.log(" All flags accepted by the in-sandbox OpenClaw CLI are forwarded verbatim.");
console.log(" OpenClaw sandboxes run `openclaw agent ...`; terminal-runtime sandboxes run");
console.log(
" Common flags: -m <text>, --session-id <id>, --agent <id>, --json, --thinking <level>.",
);
console.log("");
console.log(
" Every invocation must include at least one target selector — --agent, --session-id,",
);
console.log(
" --session-key, or --to. On Ready/Running sandboxes, invocations without a selector",
" their manifest-declared interactive command, such as `dcode ...` for Deep Agents Code.",
);
console.log(" All flags accepted by the selected in-sandbox agent CLI are forwarded verbatim.");
console.log(
" exit 2 with `No target session selected` before any in-sandbox dispatch runs; on a",
" Common OpenClaw flags: -m <text>, --session-id <id>, --agent <id>, --json, --thinking <level>.",
);
console.log("");
console.log(" OpenClaw invocations must include at least one target selector — --agent,");
console.log(" --session-id, --session-key, or --to. On Ready/Running OpenClaw sandboxes,");
console.log(" invocations without a selector exit 2 with `No target session selected`; on a");
console.log(
" non-Ready sandbox the phase guard fires first and exits 1 with recovery commands.",
);
console.log("");
console.log(
" Currently supported on OpenClaw sandboxes only; Hermes sandboxes are rejected with a",
);
console.log(` For terminal-runtime help, run \`${CLI_NAME} <name> agent --help\` to view the`);
console.log(" upstream command help from inside the sandbox.");
console.log("");
console.log(" Hermes sandboxes are rejected with a");
console.log(" redirect to the OpenAI-compatible API on port 8642 inside the sandbox.");
console.log("");
}
Loading
Loading