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
57 changes: 57 additions & 0 deletions docs/network-policy/customize-network-policy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,63 @@ $$nemoclaw my-assistant policy-remove my-internal-api --yes

`policy-remove` accepts both built-in and custom preset names. Run `$$nemoclaw <name> policy-list` to see every preset currently applied to the sandbox.

## Agent Policy Context

When an agent runs in the sandbox, it needs a compact view of the active policy so it can decide whether a host or integration is allowed and what to suggest when something fails.
`$$nemoclaw <name> policy-explain` prints that view as a redacted summary: the recorded tier, the applied presets and their allowed host categories, the known presets that are not applied, the inspect/add/remove commands that change policy, and the support boundaries between NemoClaw, OpenShell, and the agent.

```bash
$$nemoclaw my-assistant policy-explain
```

Pass `--json` to emit the same context as a structured object the agent can read:

```bash
$$nemoclaw my-assistant policy-explain --json
```

NemoClaw also seeds the rendered context inside the sandbox at `/sandbox/.openclaw/workspace/POLICY.md` once during onboarding and refreshes it on every `policy-add` or `policy-remove`, so the in-sandbox agent picks it up when it scans the workspace.
Pass `--write` to refresh that file on demand without changing the policy:

```bash
$$nemoclaw my-assistant policy-explain --write
```

The output is intentionally redacted.
Network policy rule bodies, credential metadata, and binary allowlists are not included; only host stems and category-level summaries appear.
Host stems that resolve to RFC 1918 ranges (10/8, 172.16/12, 192.168/16), loopback (127/8, `::1`), link-local (169.254/16, `fe80::/10`), cloud metadata (`169.254.169.254`), unique-local IPv6 (`fc00::/7`), reserved zero (0.0.0.0/8), CGNAT (100.64/10), benchmarking (198.18/15), `localhost`, and the internal DNS suffixes `.local`, `.internal`, `.lan`, `.home`, `.home.arpa`, `.corp`, `.intra`, `.intranet`, `.localdomain` are dropped from `allowedHostCategories` and surface as a `redactedHostCount`.

Each active preset also carries a `verification` field that tells the agent whether the OpenShell gateway actually enforces it:

| Status | Meaning |
|--------|---------|
| `verified` | Registry lists the preset and the gateway confirms it is enforced. Safe to treat the host stems as allowed. |
| `registry-only` | Registry lists the preset but the gateway does not enforce it (drift). Treat allowed hosts as unverified; the agent should not assume the traffic will reach the host. |
| `gateway-only` | Gateway enforces a preset the registry does not list. Reported as active so the agent does not misclassify allowed hosts as blocked. |
| `gateway-unavailable` | Could not probe the gateway (no live snapshot). The whole report is advisory; rely on `nemoclaw <sandbox> policy-list` once the gateway is reachable. |

The context also documents how the agent should classify a failed host or integration attempt.
The rules are evaluated in order so HTTP 403 has a single interpretation per call: when the host matches an applied preset the request is treated as an authentication failure, otherwise as a policy denial.

1. `unsupported` — the caller asserts the capability is not offered for this sandbox (for example, a messaging channel that the active agent does not support). The agent should surface the limitation without retrying.
2. `missing-approval` — the host **is** allowed by an applied preset and the request was refused with HTTP 401. The network path is open; credentials are missing or invalid.
3. `missing-approval` (low confidence) — the host **is** allowed by an applied preset and the request was refused with HTTP 403. Ambiguous: OpenShell policies enforce by method, path, protocol, and binary, so a 403 on an allowed host can still be a finer-grained policy denial rather than missing credentials. Confirm credentials first, then run `openshell policy get` to check whether the specific method or path is blocked.
4. `blocked-by-policy` — either the host is **not** allowed by any applied preset and either an existing built-in or custom preset declares it (apply that preset), or the request is refused with a network-block error code (`EHOSTUNREACH`, `ENETUNREACH`, `ENOTFOUND`, `ECONNREFUSED`, `ETIMEDOUT`, `EAI_AGAIN`) or HTTP 403. The same network-block codes also surface as `blocked-by-policy` (low confidence) when the host is on an applied but **unverified** preset (`registry-only` or `gateway-unavailable`), because a block code on a host the registry says should be allowed is the strongest signal that the gateway is not enforcing the preset.
5. `unknown` — none of the above apply; the agent should surface the underlying error. A network-block code on a host that matches a **verified** preset stays `unknown` because the gateway has confirmed enforcement, so the block must be an upstream connectivity failure rather than a policy denial.

Each classification also carries a `confidence` field set to `high` or `low`. Low-confidence verdicts mean the agent should report multiple possibilities to the user instead of treating the next-step recommendation as authoritative. Common low-confidence triggers are:

- HTTP 403 on an active host (ambiguous between missing credentials and a finer-grained OpenShell denial by method, path, protocol, or binary).
- The matched preset is `registry-only` (the registry lists it but the gateway does not enforce it) — the agent must not assume the host is reachable.
- The matched preset is `gateway-unavailable` (no live gateway snapshot was available) — the verdict is registry-derived and advisory.

Callers that already hold a verified gateway snapshot can pass it to the classifier so verdicts about hosts on verified presets stay high-confidence.

Use the classification to pick the next step.
For `blocked-by-policy`, run `$$nemoclaw <name> policy-add <preset>` or author a [custom preset](#custom-preset-files).
For `missing-approval`, confirm the API token and scopes for the integration.
For `unsupported`, surface the limitation to the user without retrying.

## Related Topics

- [Approve or Deny Agent Network Requests](approve-network-requests) for real-time operator approval.
Expand Down
31 changes: 31 additions & 0 deletions docs/reference/commands-nemohermes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,37 @@ If the preset is unknown or not currently applied, the command exits non-zero wi

Unchecking a preset in the onboard TUI checkbox also removes it from the sandbox.

### `nemohermes <name> policy-explain`

Print a redacted summary of the active policy context for a sandbox so an agent or operator can reason about what is allowed, what is blocked, and how to request a change.
The output covers the recorded tier, the applied presets (built-in and custom) with their allowed host categories, the known presets that are not applied, the inspect/add/remove commands that change policy, and the support boundaries between NemoClaw, OpenShell, and the agent.
Raw policy YAML, rule bodies, and credential metadata are deliberately not included.

```bash
nemohermes my-assistant policy-explain
```

Pass `--json` to emit the same context as a structured object for agent consumption:

```bash
nemohermes my-assistant policy-explain --json
```

NemoClaw refreshes the rendered context inside the sandbox at `/sandbox/.openclaw/workspace/POLICY.md` whenever a preset is added or removed, and once at the end of the onboarding policy step.
Pass `--write` to refresh that file on demand without changing the policy:

```bash
nemohermes my-assistant policy-explain --write
```

The context also documents how a failed host or integration attempt should be classified.
The classifications are `blocked-by-policy`, `missing-approval`, `unsupported`, and `unknown`, so the agent can pick a remediation step instead of surfacing a lower-level network error.

| Flag | Description |
|------|-------------|
| `--json` | Emit the policy context as a structured JSON object for agent consumption |
| `--write` | Refresh `/sandbox/.openclaw/workspace/POLICY.md` inside the sandbox in addition to printing |

### `nemohermes <name> hosts-add`

Add a host alias to the sandbox pod template.
Expand Down
31 changes: 31 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,37 @@ If the preset is unknown or not currently applied, the command exits non-zero wi

Unchecking a preset in the onboard TUI checkbox also removes it from the sandbox.

### `$$nemoclaw <name> policy-explain`
Comment thread
laitingsheng marked this conversation as resolved.

Print a redacted summary of the active policy context for a sandbox so an agent or operator can reason about what is allowed, what is blocked, and how to request a change.
The output covers the recorded tier, the applied presets (built-in and custom) with their allowed host categories, the known presets that are not applied, the inspect/add/remove commands that change policy, and the support boundaries between NemoClaw, OpenShell, and the agent.
Raw policy YAML, rule bodies, and credential metadata are deliberately not included.

```bash
$$nemoclaw my-assistant policy-explain
```

Pass `--json` to emit the same context as a structured object for agent consumption:

```bash
$$nemoclaw my-assistant policy-explain --json
```

NemoClaw refreshes the rendered context inside the sandbox at `/sandbox/.openclaw/workspace/POLICY.md` whenever a preset is added or removed, and once at the end of the onboarding policy step.
Pass `--write` to refresh that file on demand without changing the policy:

```bash
$$nemoclaw my-assistant policy-explain --write
```

The context also documents how a failed host or integration attempt should be classified.
The classifications are `blocked-by-policy`, `missing-approval`, `unsupported`, and `unknown`, so the agent can pick a remediation step instead of surfacing a lower-level network error.

| Flag | Description |
|------|-------------|
| `--json` | Emit the policy context as a structured JSON object for agent consumption |
| `--write` | Refresh `/sandbox/.openclaw/workspace/POLICY.md` inside the sandbox in addition to printing |

### `$$nemoclaw <name> hosts-add`

Add a host alias to the sandbox pod template.
Expand Down
45 changes: 45 additions & 0 deletions src/commands/sandbox/policy/explain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";

import { explainSandboxPolicy } from "../../../lib/actions/sandbox/policy-explain";
import { NemoClawCommand } from "../../../lib/cli/nemoclaw-oclif-command";
import { sandboxNameArg } from "../../../lib/sandbox/command-support";

export default class SandboxPolicyExplainCommand extends NemoClawCommand {
static id = "sandbox:policy:explain";
static strict = true;
static summary = "Explain the active policy context for the sandbox";
static description =
"Print a redacted summary of the active policy presets, allowed host categories, approval paths, and support boundaries. The agent can read this output to decide whether a host or integration is allowed and what remediation step to suggest.";
static usage = ["<name> [--json] [--write]"];
static examples = [
"<%= config.bin %> sandbox policy explain alpha",
"<%= config.bin %> sandbox policy explain alpha --json",
"<%= config.bin %> sandbox policy explain alpha --write",
];
static args = {
sandboxName: sandboxNameArg,
};
static flags = {
json: Flags.boolean({
description: "Emit the policy context as JSON for agent consumption.",
default: false,
}),
write: Flags.boolean({
description:
"Also write the rendered context to the sandbox at /sandbox/.openclaw/workspace/POLICY.md so the in-sandbox agent can read it.",
default: false,
}),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(SandboxPolicyExplainCommand);
explainSandboxPolicy(
args.sandboxName,
{ json: flags.json, writeToSandbox: flags.write },
{ logJson: (value) => this.logJson(value) },
);
}
}
Loading
Loading