Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
90da1e0
refactor(agent): derive state handling from definitions
jyaunches Aug 3, 2026
3d7a4a2
merge: resolve conflicts with main
github-actions[bot] Aug 3, 2026
622dc27
refactor(agent): address state definition review
jyaunches Aug 3, 2026
f6f0085
test(state): validate Hermes config digest fixture
jyaunches Aug 3, 2026
d5313dc
merge: refresh from main
jyaunches Aug 3, 2026
6bec1a8
docs(security): clarify state guard boundary
jyaunches Aug 3, 2026
445b9c6
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
f622c45
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
493f035
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
e4e2c14
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
1278b61
merge(main): refresh PR and address review
jyaunches Aug 4, 2026
410e4b2
fix(shields): authorize startup state repair
jyaunches Aug 4, 2026
0a633eb
merge(main): integrate deadline recovery serialization
jyaunches Aug 4, 2026
fa97f8b
chore(ci): retry transient PR gate
jyaunches Aug 4, 2026
478243b
refactor(state): avoid plan variable shadowing
jyaunches Aug 4, 2026
fa5ae3e
test(e2e): accept managed startup paths
jyaunches Aug 4, 2026
d7bb3cd
merge(main): refresh PR after E2E
jyaunches Aug 4, 2026
a6257fb
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
4151bd7
test(e2e): retry transient DCode status
jyaunches Aug 4, 2026
16e0897
merge(main): refresh PR after E2E hardening
jyaunches Aug 4, 2026
452ad68
merge(main): refresh PR after transport diagnostics
jyaunches Aug 4, 2026
62006d5
test(e2e): keep retry assertions linear
jyaunches Aug 4, 2026
e4f37f4
merge(main): refresh PR after image qualification
jyaunches Aug 4, 2026
cc396e6
test(e2e): avoid dynamic shell sourcing
jyaunches Aug 4, 2026
5a26175
test(e2e): bound generated driver processes
jyaunches Aug 4, 2026
e9fe555
merge(main): refresh PR after image provenance fix
jyaunches Aug 4, 2026
c8bbb5f
test(tunnel): allow coverage-load startup time
jyaunches Aug 4, 2026
a687345
merge(main): refresh PR 8143 base
jyaunches Aug 4, 2026
13b355a
merge(main): refresh PR after E2E
jyaunches Aug 4, 2026
e225e16
merge(main): refresh PR after E2E verification
jyaunches Aug 4, 2026
42d2631
merge(main): refresh PR 8143 base
jyaunches Aug 5, 2026
ba8141c
docs(security): correct OpenClaw confidential state
jyaunches Aug 5, 2026
7d7ce4d
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
5c44a4d
test(shields): isolate recovery policy command
jyaunches Aug 5, 2026
72936fa
merge: refresh main
jyaunches Aug 5, 2026
c6b6201
test(shields): pin recovery command fixture
jyaunches Aug 5, 2026
7c86cbf
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
3099cdc
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
9809a36
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
0641b39
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
fd98869
test(state): align refreshed fixtures with current contracts
jyaunches Aug 5, 2026
eaadbfc
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
bbe50fb
docs(security): clarify Hermes state protection
jyaunches Aug 5, 2026
e4799bd
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
e8910b0
test(e2e): wait for sandbox readiness after restart
jyaunches Aug 5, 2026
7062e02
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
9f6554e
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
1fa40e3
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
f155f30
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
820292c
test(state): align merged target fixtures
jyaunches Aug 5, 2026
f59602b
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
29564c9
docs(security): align state plan inventory
jyaunches Aug 5, 2026
2154233
merge: reconcile published PR history
jyaunches Aug 5, 2026
15ede40
test(e2e): support flat Hermes cron state
jyaunches Aug 5, 2026
b95ca42
test(e2e): validate redacted Hermes cron receipt
jyaunches Aug 5, 2026
4d26e8e
merge: sync main into PR 8143
jyaunches Aug 5, 2026
c8c71c7
test(e2e): exercise Hermes cron scheduler drain
jyaunches Aug 5, 2026
582ba9a
merge: sync main into PR 8143
jyaunches Aug 5, 2026
dc18539
merge: sync latest main into PR 8143
jyaunches Aug 5, 2026
d89caf6
test(e2e): tolerate Hermes restart status transient
jyaunches Aug 5, 2026
14c334a
merge: sync security review rubric from main
jyaunches Aug 5, 2026
e25a51f
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
af5d956
merge: sync latest main into PR 8143
jyaunches Aug 6, 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
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ COPY scripts/lib/openclaw_device_approval_policy.py /usr/local/lib/nemoclaw/open
COPY scripts/lib/clean_runtime_shell_env_shim.py /usr/local/lib/nemoclaw/clean_runtime_shell_env_shim.py
COPY scripts/lib/normalize_mutable_config_perms.py /usr/local/lib/nemoclaw/normalize_mutable_config_perms.py
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY agents/openclaw/state-lock-plan.json /usr/local/share/nemoclaw/state-lock-plan.json
COPY scripts/openclaw-config-guard.py /usr/local/lib/nemoclaw/openclaw-config-guard.py
COPY scripts/managed-gateway-control.py /usr/local/lib/nemoclaw/managed-gateway-control.py
COPY scripts/nemoclaw-start.sh /usr/local/bin/nemoclaw-start
Expand Down Expand Up @@ -977,12 +978,14 @@ RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-codex-acp \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control \
/usr/local/lib/nemoclaw/gateway-supervisor.sh \
/usr/local/lib/nemoclaw/state-dir-guard.py \
/usr/local/share/nemoclaw/state-lock-plan.json \
/usr/local/lib/nemoclaw/openclaw-config-guard.py \
/usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py \
/usr/local/lib/nemoclaw/openclaw-config-guard.py \
/usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 444 /usr/local/share/nemoclaw/state-lock-plan.json \
&& chmod 444 /usr/local/lib/nemoclaw/gateway-supervisor.sh \
/usr/local/lib/nemoclaw/sandbox-rlimits.sh \
&& chmod 644 /usr/local/lib/nemoclaw/openclaw_device_approval_policy.py \
Expand Down Expand Up @@ -1651,6 +1654,7 @@ RUN check_metadata() { \
&& check_metadata /usr/local/lib/nemoclaw/patch-openclaw-tool-catalog.mts 'root:root:755' \
&& check_metadata /usr/local/bin/nemoclaw-gateway-control 'root:root:700' \
&& check_metadata /usr/local/lib/nemoclaw/state-dir-guard.py 'root:root:500' \
&& check_metadata /usr/local/share/nemoclaw/state-lock-plan.json 'root:root:444' \
&& check_metadata /usr/local/lib/nemoclaw/preloads/sandbox-safety-net.js 'root:root:644' \
&& check_metadata /scripts/checks/node-tar-image-scan.mts 'root:root:755' \
&& install -d -m 0755 /usr/local/share/nemoclaw \
Expand Down
5 changes: 4 additions & 1 deletion agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ COPY agents/hermes/build-mcp-digest.py /usr/local/lib/nemoclaw/build-hermes-mcp-
COPY agents/hermes/mcp-config-transaction.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py
COPY src/lib/actions/sandbox/openshell-child-visible-credentials.v0.0.85.json /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY agents/hermes/state-lock-plan.json /usr/local/share/nemoclaw/state-lock-plan.json
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/

FROM scratch AS hermes-wrapper-payload
Expand Down Expand Up @@ -222,9 +223,10 @@ RUN chmod -R a+rX /opt/nemoclaw-blueprint/
# minimum supported Hermes sandbox base tag guarantees those artifacts and
# test/sandbox-rlimit-hooks.test.ts covers that base.
RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/lib/nemoclaw/sandbox-init.sh /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py /usr/local/lib/nemoclaw/seed-hermes-dashboard-config.py /usr/local/lib/nemoclaw/hermes-runtime-config-guard.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/share/nemoclaw/state-lock-plan.json /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 444 /usr/local/share/nemoclaw/state-lock-plan.json \
&& chmod 444 /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py \
&& chmod 444 /usr/local/lib/nemoclaw/patch-hermes-langfuse-credentials.mts \
&& chmod 444 /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
Expand Down Expand Up @@ -909,6 +911,7 @@ RUN check_metadata() { \
&& check_metadata /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py 'root:root 755' \
&& check_metadata /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py 'root:root 755' \
&& check_metadata /usr/local/bin/nemoclaw-gateway-control 'root:root 700' \
&& check_metadata /usr/local/share/nemoclaw/state-lock-plan.json 'root:root 444' \
&& check_metadata /usr/local/lib/nemoclaw/preloads/sandbox-safety-net.js 'root:root 444' \
&& check_metadata /usr/local/lib/nemoclaw/hermes-wrapper.py 'root:root 755' \
&& check_metadata /scripts/checks/node-tar-image-scan.mts 'root:root 755' \
Expand Down
38 changes: 29 additions & 9 deletions agents/hermes/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ config:
dir: /sandbox/.hermes
config_file: config.yaml
env_file: .env # relative to dir — API keys
# Additional generated config files that Shields seals with config_file and
# .config-hash. Each entry must exist before a Shields transition. The
# runtime-config-guard parity test protects the separate in-image boundary.
shields_files:
- .env
auth_file: auth.json # OAuth tokens (Nous Portal, Codex, etc.)
format: yaml

Expand All @@ -61,29 +66,44 @@ config:
state_dirs:
- memories
- sessions
- skills
- plugins
- cron
- path: skills
shields: read-only
- path: plugins
shields: read-only
- path: cron
shields: read-only
# Hermes creates this mutable integration directory today, and the existing
# Shields helper protects it. Keep that lock behavior without newly treating
# its contents as portable snapshot state.
- path: hooks
backup: false
shields: read-only
- logs
- skins
- path: skins
shields: read-only
- plans
- workspace
- profiles
- path: workspace
shields: read-only
- path: profiles
shields: read-only
- cache
- pairing
- path: pairing
shields: confidential
# Hermes' Web dashboard uses a separate profile home for its config,
# memories, and user identity. Preserve it so rebuilds do not reset the
# dashboard profile independently of the main Hermes runtime.
- dashboard-home
# Hermes' WhatsApp bridge stores QR-paired session credentials under
# ~/.hermes/platforms/whatsapp/session. Preserve the parent so rebuilds
# keep the in-sandbox pairing state without re-scanning.
- platforms
- path: platforms
shields: read-only
# Hermes' iLink WeChat adapter persists per-account context tokens under
# ~/.hermes/weixin/accounts/<id>.context-tokens.json so the long-poll
# cursor survives a rebuild. The bot token itself comes from .env via
# the L7 proxy and is not stored on disk inside the sandbox.
- weixin
- path: weixin
shields: read-only

# ── Top-level durable state files ───────────────────────────────
# NemoClaw stores Hermes gateway-created top-level state under runtime/ and
Expand Down
19 changes: 18 additions & 1 deletion agents/hermes/runtime-config-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
)
NEMOCLAW_START_ARGV = (b"nemoclaw-start", b"/usr/local/bin/nemoclaw-start")
OPENSHELL_SUPERVISOR_ARGV0 = b"/opt/openshell/bin/openshell-sandbox"
# Keep this in exact parity with manifest config_file + config.shields_files +
# .config-hash. The host manifest remains authoritative for host transitions;
# the integration test protects this separate in-image recovery boundary.
SEALED_FILE_NAMES = ("config.yaml", ".env", ".config-hash")
RESTART_ORPHAN_MARKER_NAME = ".nemoclaw-hermes-restart-seal"
SHIELDS_TRANSITION_LEASE_SECONDS = 300
Expand Down Expand Up @@ -4411,7 +4414,11 @@ def abort_shields_transition(hermes_dir: str, state_file: str, lock_token: str)


def run_state_dir_transition(
hermes_dir: str, state_file: str, lock_token: str, action: str
hermes_dir: str,
state_file: str,
lock_token: str,
action: str,
state_lock_plan_json: str,
) -> None:
if action not in ("lock", "unlock"):
raise UnsafePathError("refusing unsupported Hermes state-dir action")
Expand All @@ -4433,6 +4440,13 @@ def run_state_dir_transition(
helper = installed if os.path.isfile(installed) else checkout
if not os.path.isfile(helper):
raise UnsafePathError("Hermes state-dir guard is unavailable")
if state_lock_plan_json:
plan_args = ["--plan-json", state_lock_plan_json]
else:
plan_file = "/usr/local/share/nemoclaw/state-lock-plan.json"
if not os.path.isfile(plan_file):
raise UnsafePathError("Hermes state lock plan is unavailable")
plan_args = ["--plan-file", plan_file]
# Preserve this exact PID/start identity as GNU timeout while it owns and
# waits for the recursive worker. Cancel the Python alarm before exec so
# timeout alone owns TERM/KILL tree cleanup and no orphan child survives.
Expand All @@ -4449,6 +4463,7 @@ def run_state_dir_transition(
action,
"--config-dir",
hermes_dir,
*plan_args,
],
)

Expand Down Expand Up @@ -5004,6 +5019,7 @@ def main() -> int:
parser.add_argument("--expected-config-sha256", default="")
parser.add_argument("--lock-token", default="")
parser.add_argument("--state-action", choices=("lock", "unlock"), default="")
parser.add_argument("--state-lock-plan-json", default="")
parser.add_argument("--shields-mode", choices=("locked", "mutable"), default="")
parser.add_argument(
"--rollback-shields-mode", choices=("locked", "mutable"), default=""
Expand Down Expand Up @@ -5186,6 +5202,7 @@ def main() -> int:
args.state_file,
args.lock_token,
args.state_action,
args.state_lock_plan_json,
)
except UnsafePathError as exc:
_die(str(exc))
Expand Down
21 changes: 21 additions & 0 deletions agents/hermes/state-lock-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0",
"version": 1,
"readOnlyRoots": [
"cron",
"hooks",
"platforms",
"plugins",
"profiles",
"skills",
"skins",
"weixin",
"workspace"
],
"confidentialRoots": [
"pairing"
],
"readOnlyPrefixes": [],
"confidentialPrefixes": [],
"writableSubpaths": []
}
6 changes: 4 additions & 2 deletions agents/langchain-deepagents-code/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ config:
# .state and skills — otherwise skills are silently lost on rebuild.
state_dirs:
- .state
- skills
- agent/skills
- path: skills
shields: read-only
- path: agent/skills
shields: read-only

# ── Top-level durable state files ───────────────────────────────
# config.toml mixes DCode preferences with NemoClaw-managed model routing.
Expand Down
70 changes: 43 additions & 27 deletions agents/openclaw/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,33 +41,49 @@ config:

# ── State directories ──────────────────────────────────────────
state_dirs:
- agents
- extensions
- workspace
- skills
- hooks
- identity
- devices
- canvas
- cron
- memory
- telegram
- wechat
- whatsapp
- credentials

# Machine-local gateway auth state: the Ed25519 device identity
# (identity/device.json) and paired-device token store (devices/). Backup
# sanitization scrubs their key/token fields, so a restored copy can never
# authenticate — restoring it replaces working pairing state with corrupt
# files and the CLI fails with GatewayCredentialsRequiredError (issue #6852).
# These dirs stay in state_dirs so destroy still wipes them from the durable
# volume, but they are never captured into or restored from snapshots;
# OpenClaw regenerates the identity on demand and NemoClaw auto-pair
# re-pairs on connect.
runtime_auth_state_dirs:
- identity
- devices
- path: agents
shields: read-only
writable_subpaths:
- "*/sessions"
- path: extensions
shields: read-only
# Legacy layouts can still contain this pre-extensions directory. Preserve
# its existing Shields posture without newly treating it as portable state.
- path: plugins
backup: false
shields: read-only
- path: workspace
shields: read-only
# Multi-agent OpenClaw deployments create workspace-<agent> siblings.
- prefix: workspace-
shields: read-only
- path: skills
shields: read-only
- path: hooks
shields: read-only
# Machine-local gateway auth state is wiped on destroy but never captured.
# Sanitization removes the identity key and paired-device tokens, so a
# restored copy cannot authenticate (#6852).
- path: identity
backup: false
shields: confidential
- path: devices
backup: false
shields: read-only
- path: canvas
shields: read-only
- path: cron
shields: read-only
- path: memory
shields: read-only
- path: telegram
shields: read-only
- path: wechat
shields: read-only
- path: whatsapp
shields: read-only
- path: credentials
shields: confidential

# ── Top-level durable state files ───────────────────────────────
# openclaw.json holds the core OpenClaw settings the state dirs above do not
Expand Down
30 changes: 30 additions & 0 deletions agents/openclaw/state-lock-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0",
"version": 1,
"readOnlyRoots": [
"agents",
"canvas",
"cron",
"devices",
"extensions",
"hooks",
"memory",
"plugins",
"skills",
"telegram",
"wechat",
"whatsapp",
"workspace"
],
"confidentialRoots": [
"credentials",
"identity"
],
"readOnlyPrefixes": [
"workspace-"
],
"confidentialPrefixes": [],
"writableSubpaths": [
"agents/*/sessions"
]
}
5 changes: 5 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"test": "accepts only the tracked published Hermes base digest",
"category": "security"
},
{
"file": "src/lib/agent/state-directory-contract.test.ts",
"test": "keeps generated image plans equal to their AgentDefinition projections (#8006)",
"category": "security"
},
{
"file": "src/lib/onboard/managed-startup-profile.test.ts",
"test": "classifies every stock Docker ARG as startup-affordance or deliberate exclusion",
Expand Down
2 changes: 1 addition & 1 deletion ci/test-file-size-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"src/lib/onboard/preflight.test.ts": 1904,
"test/generate-openclaw-config.test.ts": 1941,
"test/install-preflight.test.ts": 3921,
"test/nemoclaw-start.test.ts": 4819,
"test/nemoclaw-start.test.ts": 4793,
"test/onboard-messaging.test.ts": 2043,
"test/onboard-selection.test.ts": 4769
}
Expand Down
3 changes: 3 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,9 @@ navigation:
- page: "Credential Storage"
path: _build/agent-variants/security/credential-storage.deepagents.generated.mdx
slug: credential-storage
- page: "Trusted Computing Base"
path: _build/agent-variants/security/tcb-boundary.deepagents.generated.mdx
slug: trusted-computing-base
- page: "OpenShell 0.0.72 Compatibility Review"
path: _build/agent-variants/security/openshell-0.0.72-compatibility-review.deepagents.generated.mdx
slug: openshell-0.0.72-compatibility-review
Expand Down
10 changes: 5 additions & 5 deletions docs/manage-sandboxes/backup-restore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ They are the preferred backup and restore path.

## Understand Snapshot Contents

Snapshots capture all workspace state directories defined in the agent manifest and store them in `~/.nemoclaw/rebuild-backups/<name>/`.
Snapshots capture the manifest-declared snapshot state directories and store them in `~/.nemoclaw/rebuild-backups/<name>/`.
Agent manifests can also declare durable top-level state files.
Treat snapshot directories as private local data.

Expand Down Expand Up @@ -70,10 +70,10 @@ Wait for active `dcode` work to finish before running `$$nemoclaw <name> snapsho
Snapshots preserve sandbox registry metadata that affects rebuild behavior, including custom policy presets applied with `policy add --from-file` or `policy add --from-dir` and baseline network policy entries excluded with `policy exclude`.
When you restore a snapshot, NemoClaw replays those recorded custom presets with their stored YAML content, so you do not need the original preset files on disk, and rebuild continues to apply the recorded baseline exclusions.

The target sandbox's current agent manifest remains authoritative for state-file restore behavior.
NemoClaw rejects the restore when the snapshot's agent, config directory, state-file path, or state-file strategy conflicts with that manifest.
Restore limits directory cleanup to state directories declared by the snapshot manifest.
It preserves directories that exist only in the target manifest or whose backup failed.
The target sandbox's current agent manifest remains authoritative for directory and state-file restore behavior.
NemoClaw rejects the restore when the snapshot's agent, config directory, any snapshot directory, state-file path, or state-file strategy conflicts with that manifest.
Restore limits directory cleanup to state directories authorized by both the snapshot and the current manifest.
It preserves target-only directories and directories whose backup failed.

For managed images, NemoClaw applies the current manifest's managed config merge rules by default and does not fall back to whole-file replacement.
For Deep Agents targets, whole-file config replacement is limited to sandboxes created from a custom Dockerfile.
Expand Down
Loading
Loading