Overlay Filesystem

The overlay role sets up Linux OverlayFS to give the agent a writable workspace while keeping your host files read-only. This page explains how OverlayFS works, how the sandbox configures it, and what happens to writes.

Overlay Filesystem

OverlayFS in 30 Seconds

OverlayFS is a union filesystem built into the Linux kernel. It layers directories on top of each other:

  • lowerdir: the read-only base (your project files from the host)
  • upperdir: where all writes go (inside the VM, never touches the host)
  • workdir: internal bookkeeping directory used by the kernel
  • merged: the combined view that processes actually see

When a process reads a file, OverlayFS checks the upper layer first, then falls through to the lower layer. When a process writes a file, the write always goes to the upper layer. Deletes are recorded as "whiteout" files in the upper layer -- the original file in the lower layer is untouched.

Directory Layout

The overlay role creates this structure inside the VM:

Path Role Description
/mnt/openclaw lowerdir Read-only virtiofs mount of your project
/var/lib/openclaw/overlay/openclaw/upper upperdir All agent writes land here
/var/lib/openclaw/overlay-work/openclaw workdir Kernel bookkeeping
/workspace merged Unified view -- gateway and services run here

For Obsidian vaults (when --vault is used):

Path Role Description
/mnt/obsidian lowerdir Read-only virtiofs mount of your vault
/var/lib/openclaw/overlay/obsidian/upper upperdir Vault writes
/var/lib/openclaw/overlay-work/obsidian workdir Kernel bookkeeping
/workspace-obsidian merged Unified vault view

systemd Mount Units

The overlay is managed by systemd mount units, not fstab entries. The overlay role deploys them from Jinja2 templates.

workspace.mount

Template: ansible/roles/overlay/templates/workspace.mount.j2

[Unit]
Description=OverlayFS workspace mount
After=local-fs.target
DefaultDependencies=no

[Mount]
What=overlay
Where=/workspace
Type=overlay
Options=lowerdir=/mnt/openclaw,upperdir=/var/lib/openclaw/overlay/openclaw/upper,workdir=/var/lib/openclaw/overlay-work/openclaw

[Install]
WantedBy=local-fs.target

The gateway service depends on this unit (Requires=workspace.mount, After=workspace.mount), so systemd ensures the overlay is mounted before the gateway starts.

workspace-obsidian.mount

The Obsidian overlay follows the same pattern. The unit file is named workspace\x2dobsidian.mount because systemd requires mount unit filenames to match the mount path with special characters escaped.

systemd mount unit naming

A mount at /workspace-obsidian requires a unit file named workspace\x2dobsidian.mount. The \x2d is the systemd escape for the - character. You can verify with: systemd-escape --path /workspace-obsidian.

If the vault is not mounted (no --vault flag), the role automatically cleans up stale obsidian mount units from previous provisioning runs -- stops the unit, disables it, and removes the file.

The Audit Watcher

The overlay role deploys an overlay-watcher.service that uses inotifywait to monitor the upper directory:

[Service]
Type=simple
ExecStart=/usr/bin/inotifywait -m -r \
  --timefmt '%Y-%m-%dT%H:%M:%S' \
  --format '%T %w%f %e' \
  -e create -e modify -e delete -e move \
  /var/lib/openclaw/overlay/openclaw/upper
StandardOutput=append:/var/log/openclaw/overlay-watcher.log

This gives you a timestamped audit log of every filesystem change the agent makes:

2024-03-15T14:22:01 /var/lib/openclaw/overlay/openclaw/upper/src/index.ts MODIFY
2024-03-15T14:22:03 /var/lib/openclaw/overlay/openclaw/upper/src/new-file.ts CREATE

The watcher depends on workspace.mount and restarts on failure.

Filesystem Modes

The overlay role supports three modes, controlled by bootstrap.sh flags:

Secure Mode (default)

./bootstrap.sh --openclaw ~/Projects/openclaw
  • Host mounts: read-only
  • Overlay: active
  • Sync: manual via sync-gate.sh
  • Variables: overlay_enabled: true, overlay_yolo_unsafe: false, overlay_yolo_mode: false

YOLO Mode

./bootstrap.sh --openclaw ~/Projects/openclaw --yolo
  • Host mounts: read-only
  • Overlay: active
  • Sync: automatic every 30 seconds
  • Variables: overlay_enabled: true, overlay_yolo_unsafe: false, overlay_yolo_mode: true

In YOLO mode, the role deploys two additional systemd units:

yolo-sync.service (oneshot):

ExecStart=/usr/bin/rsync -av --delete \
  /var/lib/openclaw/overlay/openclaw/upper/ /mnt/openclaw/

yolo-sync.timer:

[Timer]
OnBootSec=60s
OnUnitActiveSec=30s
AccuracySec=5s

The timer fires 60 seconds after boot, then every 30 seconds (overlay_yolo_sync_interval). This bypasses sync-gate validation entirely.

YOLO-Unsafe Mode

./bootstrap.sh --openclaw ~/Projects/openclaw --yolo-unsafe
  • Host mounts: read-write
  • Overlay: disabled
  • Sync: not needed (writes go directly to host)
  • Variables: overlay_yolo_unsafe: true

Warning

YOLO-unsafe mode disables all filesystem isolation. Agent writes go directly to your host files. This requires deleting and recreating the VM (--delete first) because Lima mount writability is baked at creation time.

How workspace_path Is Computed

The playbook (ansible/playbook.yml) sets workspace_path based on overlay state:

workspace_path: >-
  {{ overlay_workspace_path | default('/workspace')
     if (overlay_enabled | default(true) | bool
         and not (overlay_yolo_unsafe | default(false) | bool))
     else openclaw_path }}
Condition workspace_path
Overlay enabled, not yolo-unsafe /workspace (merged mount)
Overlay disabled or yolo-unsafe /mnt/openclaw (direct host mount)

Every role that needs to know where the project lives uses {{ workspace_path }} -- the gateway's WorkingDirectory, the sandbox's build commands, the buildlog's working directory, and so on.

Role Defaults

All overlay configuration has sensible defaults in ansible/roles/overlay/defaults/main.yml:

overlay_enabled: true
overlay_yolo_mode: false
overlay_yolo_unsafe: false

overlay_lower_openclaw: /mnt/openclaw
overlay_lower_obsidian: /mnt/obsidian
overlay_upper_base: /var/lib/openclaw/overlay
overlay_work_base: /var/lib/openclaw/overlay-work

overlay_workspace_path: /workspace
overlay_obsidian_path: /workspace-obsidian

overlay_yolo_sync_interval: "30s"
overlay_watcher_log: /var/log/openclaw/overlay-watcher.log

Inspecting Overlay State

From the host:

# Check overlay mount status
limactl shell openclaw-sandbox -- mountpoint -q /workspace && echo "mounted" || echo "not mounted"

# See what's in the upper layer (agent writes)
limactl shell openclaw-sandbox -- ls /var/lib/openclaw/overlay/openclaw/upper/

# Check audit log
limactl shell openclaw-sandbox -- tail -20 /var/log/openclaw/overlay-watcher.log

# View overlay mount details
limactl shell openclaw-sandbox -- mount | grep overlay

# Check overlay helper scripts
limactl shell openclaw-sandbox -- overlay-status

To reset the overlay (discard all agent writes):

limactl shell openclaw-sandbox -- sudo overlay-reset

Or from the host using the sync-gate:

./scripts/sync-gate.sh --reset