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
14 changes: 14 additions & 0 deletions docs/inference/inference-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ For Deep Agents, NemoClaw writes `/sandbox/.deepagents/config.toml` with the man
The managed `dcode` runtime uses Chat Completions through OpenShell even when a compatible endpoint also supports the Responses API.
</AgentOnly>

<AgentOnly variant="openclaw">

## OpenClaw Context Compaction

For OpenClaw sandboxes that use NemoClaw's managed `inference.local` route, NemoClaw configures safeguard compaction unless the upstream provider is Local Ollama.
Each automatic or explicitly requested compaction attempt is limited to 120 seconds and reports its progress to the user.
After a successful attempt, OpenClaw rotates the active transcript while preserving the most recent turn.

The safeguard bounds each attempt, but OpenClaw still owns the summarization behavior.
NemoClaw does not guarantee that compaction succeeds or reduces the active context.
This setting is generated when NemoClaw builds or recreates the sandbox image.

</AgentOnly>

## Provider Status

{/* provider-status:begin */}
Expand Down
64 changes: 64 additions & 0 deletions scripts/generate-openclaw-config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ const OPENCLAW_MIN_PROMPT_BUDGET_TOKENS = 8_000;
const SMALL_OLLAMA_CONTEXT_THRESHOLD =
OPENCLAW_DEFAULT_RESERVE_TOKENS_FLOOR + OPENCLAW_MIN_PROMPT_BUDGET_TOKENS;
const LOCAL_OLLAMA_UPSTREAM_PROVIDER = "ollama-local";
const MANAGED_INFERENCE_PROVIDER_KEY = "inference";
const MANAGED_INFERENCE_HOSTNAME = "inference.local";
// Upstream source of truth (#4781): OpenClaw's `AgentCompactionConfig` schema and
// safeguard compactor/session runtime shipped by the exact `OPENCLAW_VERSION`
// pin in the production image (`Dockerfile` and `Dockerfile.base`). The observed
// long-running `/compact` operation and growing active context occur there after
// NemoClaw hands off this config.
// NemoClaw does not own that runtime, so this is a generator-side mitigation,
// not a source fix.
// The runtime-overrides E2E validates this object with the pinned OpenClaw CLI;
// that does not prove live token reduction, so keep #4781 open. Remove this
// override only after a newer pinned OpenClaw runtime has managed-inference
// regression evidence that `/compact` completes and leaves a no-larger active
// context without it.
const MANAGED_INFERENCE_SAFEGUARD_COMPACTION: JsonObject = {
mode: "safeguard",
timeoutSeconds: 120,
maxHistoryShare: 0.35,
recentTurnsPreserve: 1,
qualityGuard: { enabled: true, maxRetries: 0 },
notifyUser: true,
truncateAfterCompaction: true,
};
const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
const WEB_SEARCH_PROVIDERS = {
brave: { credentialEnv: "BRAVE_API_KEY" },
Expand Down Expand Up @@ -1018,6 +1041,39 @@ export function buildLocalOllamaSmallContextCompaction(
return { reserveTokens, reserveTokensFloor: reserveTokens };
}

function isManagedInferenceLocalRoute(
providerKey: string | undefined,
inferenceBaseUrl: string,
): boolean {
if ((providerKey || "").trim() !== MANAGED_INFERENCE_PROVIDER_KEY) {
return false;
}
return parseUrl(normalizeUrlForParse(inferenceBaseUrl)).hostname === MANAGED_INFERENCE_HOSTNAME;
}

// Managed inference sessions other than Local Ollama use OpenClaw's safeguard
// compaction rather than its plain runtime compactor. A two-minute timeout
// bounds each attempt, lifecycle notices expose automatic and agent-run
// compaction progress, and
// successful compaction rotates the active transcript. These safeguards do not
// guarantee that summarization succeeds or that the resulting context is smaller.
export function buildManagedInferenceSafeguardCompaction(
providerKey: string | undefined,
upstreamProvider: string | undefined,
inferenceBaseUrl: string,
): JsonObject | undefined {
if (!isManagedInferenceLocalRoute(providerKey, inferenceBaseUrl)) {
return undefined;
}
if ((upstreamProvider || "").trim() === LOCAL_OLLAMA_UPSTREAM_PROVIDER) {
return undefined;
}
return {
...MANAGED_INFERENCE_SAFEGUARD_COMPACTION,
qualityGuard: { ...MANAGED_INFERENCE_SAFEGUARD_COMPACTION.qualityGuard },
};
}

export function buildConfig(env: Env = process.env): JsonObject {
const proxyHost = env.NEMOCLAW_PROXY_HOST || "10.200.0.1";
const proxyPort = env.NEMOCLAW_PROXY_PORT || "3128";
Expand Down Expand Up @@ -1237,6 +1293,14 @@ export function buildConfig(env: Env = process.env): JsonObject {
if (smallOllamaCompaction) {
agentDefaults.compaction = smallOllamaCompaction;
}
const managedInferenceCompaction = buildManagedInferenceSafeguardCompaction(
providerKey,
env.NEMOCLAW_UPSTREAM_PROVIDER,
inferenceBaseUrl,
);
if (managedInferenceCompaction) {
agentDefaults.compaction = managedInferenceCompaction;
}

const config: JsonObject = {
agents: {
Expand Down
38 changes: 38 additions & 0 deletions test/e2e/live/runtime-overrides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ type OpenClawConfig = {
[key: string]: unknown;
};

const MANAGED_INFERENCE_SAFEGUARD_COMPACTION = {
mode: "safeguard",
timeoutSeconds: 120,
maxHistoryShare: 0.35,
recentTurnsPreserve: 1,
qualityGuard: { enabled: true, maxRetries: 0 },
notifyUser: true,
truncateAfterCompaction: true,
};

function commandResult(result: ReturnType<typeof spawnSync>): CommandResult {
return {
status: result.status,
Expand Down Expand Up @@ -197,6 +207,32 @@ function runConfigHashCheck(
return result.stdout.trim();
}

function assertManagedInferenceCompactionRuntime(dockerLog: string[], image: string): void {
const result = runContainer(
dockerLog,
image,
"managed inference compaction runtime validation",
{},
String.raw`set -eu
validation="$(openclaw config validate --json)"
compaction="$(openclaw config get agents.defaults.compaction --json)"
printf '{"validation":%s,"compaction":%s}\n' "$validation" "$compaction" >&3
sleep 0.1`,
);
expect(result.status, spawnResultText(result)).toBe(0);

let proof: { validation?: { valid?: boolean }; compaction?: unknown };
try {
proof = JSON.parse(result.stdout.trim()) as typeof proof;
} catch (error) {
throw new Error(
`managed inference compaction proof did not emit valid JSON: ${(error as Error).message}\n${result.stdout}`,
);
}
expect(proof.validation?.valid).toBe(true);
expect(proof.compaction).toEqual(MANAGED_INFERENCE_SAFEGUARD_COMPACTION);
}

function runOverrideStderr(
dockerLog: string[],
image: string,
Expand Down Expand Up @@ -248,6 +284,7 @@ test(
image,
contract: [
"baseline config hash validates",
"pinned OpenClaw accepts and loads managed inference safeguard compaction",
"model/API/context/max-token/reasoning overrides patch openclaw.json",
"CORS origin override extends gateway.controlUi.allowedOrigins",
"combined overrides apply atomically",
Expand Down Expand Up @@ -277,6 +314,7 @@ test(
const baselineContextWindow = baselineFirstModel.contextWindow;
const baselineOriginCount = allowedOrigins(baseline).length;

assertManagedInferenceCompactionRuntime(dockerLog, image);
expect(runConfigHashCheck(dockerLog, image, "baseline")).toBe("OK");

const overrideModel = "anthropic/claude-sonnet-4-6";
Expand Down
62 changes: 59 additions & 3 deletions test/ollama-local-openclaw-config-propagation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { afterEach, describe, expect, it } from "vitest";
import {
buildConfig,
buildLocalOllamaSmallContextCompaction,
buildManagedInferenceSafeguardCompaction,
} from "../scripts/generate-openclaw-config.mts";
import { patchStagedDockerfile } from "../src/lib/onboard/dockerfile-patch";

Expand Down Expand Up @@ -131,7 +132,7 @@ describe("ollama-local OpenClaw config propagation", () => {
});
});

describe("ollama-local small-context compaction policy (#5468)", () => {
describe("OpenClaw managed-route compaction policy (#5468, #4781)", () => {
it("emits a lowered compaction reserve for a small Local Ollama window", () => {
const config = buildConfig({
NEMOCLAW_MODEL: "qwen2.5:0.5b",
Expand All @@ -152,7 +153,7 @@ describe("ollama-local small-context compaction policy (#5468)", () => {
});
});

it("does not touch compaction for a non-ollama upstream provider", () => {
it("uses safeguard compaction for remote managed inference (#4781)", () => {
const config = buildConfig({
NEMOCLAW_MODEL: "nvidia/nemotron-3-super-120b-a12b",
NEMOCLAW_PROVIDER_KEY: "inference",
Expand All @@ -164,7 +165,33 @@ describe("ollama-local small-context compaction policy (#5468)", () => {
NEMOCLAW_MAX_TOKENS: "4096",
NEMOCLAW_AGENT_TIMEOUT: "600",
});
expect(config.agents.defaults.compaction).toBeUndefined();
expect(config.agents.defaults.compaction).toEqual({
mode: "safeguard",
timeoutSeconds: 120,
maxHistoryShare: 0.35,
recentTurnsPreserve: 1,
qualityGuard: { enabled: true, maxRetries: 0 },
notifyUser: true,
truncateAfterCompaction: true,
});
});

it("treats a missing legacy upstream provider as remote managed inference (#4781)", () => {
expect(
buildManagedInferenceSafeguardCompaction(
"inference",
undefined,
"https://inference.local/v1",
),
).toEqual({
mode: "safeguard",
timeoutSeconds: 120,
maxHistoryShare: 0.35,
recentTurnsPreserve: 1,
qualityGuard: { enabled: true, maxRetries: 0 },
notifyUser: true,
truncateAfterCompaction: true,
});
});

it("leaves OpenClaw's default reserve intact for large Local Ollama windows", () => {
Expand All @@ -182,6 +209,35 @@ describe("ollama-local small-context compaction policy (#5468)", () => {
expect(config.agents.defaults.compaction).toBeUndefined();
});

it("does not enable managed-inference safeguards outside inference.local (#4781)", () => {
expect(
buildManagedInferenceSafeguardCompaction(
"inference",
"nvidia-prod",
"https://integrate.api.nvidia.com/v1",
),
).toBeUndefined();
});

it.each([
"https://inference.local.evil/v1",
"https://inference.local@evil.example/v1",
])("rejects a confusing managed-inference hostname %s (#4781)", (baseUrl) => {
expect(
buildManagedInferenceSafeguardCompaction("inference", "nvidia-prod", baseUrl),
).toBeUndefined();
});

it("does not enable managed-inference safeguards for another provider key (#4781)", () => {
expect(
buildManagedInferenceSafeguardCompaction(
"nvidia-prod",
"nvidia-prod",
"https://inference.local/v1",
),
).toBeUndefined();
});

it("clamps the reserve so the prompt budget never drops below OpenClaw's 8k minimum", () => {
// A pathological maxTokens must not make the window worse than the default.
const compaction = buildLocalOllamaSmallContextCompaction("ollama-local", 16384, 99999);
Expand Down
Loading