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
1 change: 1 addition & 0 deletions docs/manage-sandboxes/runtime-controls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The table below maps each commonly changed item to the layer that owns it and th
| Channel enable/disable (turn a configured channel off without removing the token) | Rebuild required (`openclaw.json` is the source of truth at runtime, see #3453) | `$$nemoclaw <name> channels stop <channel>` then rebuild |
| Dashboard forward port | Runtime. Port is re-resolved on next `connect` | `NEMOCLAW_DASHBOARD_PORT=<port> $$nemoclaw <name> connect` |
| Dashboard bind address (loopback compared to all interfaces) | Runtime. Applies on next `connect` | `NEMOCLAW_DASHBOARD_BIND=0.0.0.0 $$nemoclaw <name> connect` (see #3259) |
| Default OpenClaw workspace template seed (`AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `TOOLS.md`, `HEARTBEAT.md`) | Locked at first sandbox boot. Re-onboard required to change the bake-time choice. | Set `NEMOCLAW_MINIMAL_BOOTSTRAP=1` before `$$nemoclaw onboard` to skip default template seeding for new/pristine workspaces. **Does not delete files already present.** Partial mitigation for #2598 (cuts ~3k tokens of project-context overhead off OpenClaw's per-turn bootstrap injection). |
| Web search backend (Brave, Tavily, and so on) | Runtime through `web.backend` config flag; rebuild only if `web.fetchEnabled` flips | `$$nemoclaw <name> config set --key web.backend --value tavily` |
| Filesystem layout (Landlock zones, read-only mounts, container caps) | **Locked at creation**. No runtime change | Re-onboard with `$$nemoclaw onboard --recreate-sandbox` |
| Sandbox name | **Locked at creation** | Re-onboard with a different `--name` |
Expand Down
8 changes: 8 additions & 0 deletions scripts/nemoclaw-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2746,6 +2746,14 @@ seed_default_workspace_templates() {
local templates_dir="${2:-}"
local config_file="${3:-/sandbox/.openclaw/openclaw.json}"

# #2598: opt-in flag that skips default workspace template seeding for
# new/pristine workspaces (does NOT delete files already present). Cuts
# ~3k tokens off OpenClaw's per-turn bootstrap context injection.
if [ "${NEMOCLAW_MINIMAL_BOOTSTRAP:-}" = "1" ]; then
echo "[setup] NEMOCLAW_MINIMAL_BOOTSTRAP=1; skipping default workspace template seed" >&2
return 0
fi

if [ ! -f "$config_file" ]; then
return 0
fi
Expand Down
15 changes: 15 additions & 0 deletions src/lib/onboard/host-proxy-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ export function appendHostProxyEnvArgs(
}
}

// #2598: NEMOCLAW_MINIMAL_BOOTSTRAP is a host-side opt-in flag (set to
// "1") that the sandbox's nemoclaw-start.sh:seed_default_workspace_templates
// reads to skip default workspace template seeding for new/pristine
// workspaces (does NOT delete files already present), knocking ~3k tokens
// off OpenClaw's per-turn bootstrap context injection. Partial #2598
// mitigation: addresses the project-context contribution from NemoClaw's
// seeded templates; the remaining OpenClaw framework/non-project context
// is tracked upstream. Bundled here with the proxy propagation because
// both are env vars forwarded from the host into `openshell sandbox
// create -- env ... nemoclaw-start`, and the top-level onboard.ts
// entrypoint is line-budget-constrained per codebase-growth-guardrails.
if (env.NEMOCLAW_MINIMAL_BOOTSTRAP === "1") {
envArgs.push(formatEnvAssignment("NEMOCLAW_MINIMAL_BOOTSTRAP", "1"));
}

const hasProxy =
proxyEnv.HTTP_PROXY || proxyEnv.HTTPS_PROXY || proxyEnv.http_proxy || proxyEnv.https_proxy;
if (!hasProxy) return;
Expand Down
48 changes: 47 additions & 1 deletion test/nemoclaw-start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2652,7 +2652,7 @@ describe("seed_default_workspace_templates (#3240)", () => {
);
return spawnSync("bash", [scriptPath], {
encoding: "utf-8",
env: { ...process.env, ...(options.env ?? {}) },
env: { ...process.env, NEMOCLAW_MINIMAL_BOOTSTRAP: "", ...(options.env ?? {}) },
timeout: 5000,
});
}
Expand Down Expand Up @@ -2934,6 +2934,52 @@ describe("seed_default_workspace_templates (#3240)", () => {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

it("skips seeding when NEMOCLAW_MINIMAL_BOOTSTRAP=1 (#2598)", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-seed-minimal-"));
const workspaceDir = path.join(tmpDir, "workspace");
const templatesDir = path.join(tmpDir, "templates");
fs.mkdirSync(workspaceDir, { recursive: true });
writeTemplates(templatesDir);
try {
const result = runSeed(workspaceDir, templatesDir, path.join(tmpDir, "seed.sh"), {
env: { NEMOCLAW_MINIMAL_BOOTSTRAP: "1" },
});
expect(result.status).toBe(0);
expect(result.stderr).toContain("NEMOCLAW_MINIMAL_BOOTSTRAP=1");
expect(result.stderr).toContain("skipping default workspace template seed");
for (const name of [
"AGENTS.md",
"SOUL.md",
"IDENTITY.md",
"USER.md",
"TOOLS.md",
"HEARTBEAT.md",
]) {
expect(fs.existsSync(path.join(workspaceDir, name))).toBe(false);
}
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

it("still seeds when NEMOCLAW_MINIMAL_BOOTSTRAP is not '1' (#2598)", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-seed-noopt-"));
const workspaceDir = path.join(tmpDir, "workspace");
const templatesDir = path.join(tmpDir, "templates");
fs.mkdirSync(workspaceDir, { recursive: true });
writeTemplates(templatesDir);
try {
const result = runSeed(workspaceDir, templatesDir, path.join(tmpDir, "seed.sh"), {
env: { NEMOCLAW_MINIMAL_BOOTSTRAP: "0" },
});
expect(result.status).toBe(0);
expect(result.stderr).not.toContain("skipping default workspace template seed");
expect(fs.existsSync(path.join(workspaceDir, "SOUL.md"))).toBe(true);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
});

describe("Slack secrets-on-disk tripwire (#2085)", () => {
Expand Down
15 changes: 15 additions & 0 deletions test/onboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ describe("onboard helpers", () => {
}
});

it("propagates NEMOCLAW_MINIMAL_BOOTSTRAP=1 from host into sandbox env (#2598)", () => {
const envArgs: string[] = [];
appendHostProxyEnvArgs(envArgs, { NEMOCLAW_MINIMAL_BOOTSTRAP: "1" });
expect(envArgs).toContain("NEMOCLAW_MINIMAL_BOOTSTRAP=1");
});

it("omits NEMOCLAW_MINIMAL_BOOTSTRAP when unset or not the literal '1' (#2598)", () => {
for (const value of [undefined, "", "0", "true", "yes"]) {
const envArgs: string[] = [];
const env: NodeJS.ProcessEnv = value === undefined ? {} : { NEMOCLAW_MINIMAL_BOOTSTRAP: value };
appendHostProxyEnvArgs(envArgs, env);
expect(envArgs.some((e) => e.startsWith("NEMOCLAW_MINIMAL_BOOTSTRAP="))).toBe(false);
}
});

it("prints doctor logs automatically when gateway fails to start (#1605)", testTimeoutOptions(20_000), () => {
const repoRoot = path.join(import.meta.dirname, "..");
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-diag-"));
Expand Down
Loading