OverlayFS Session Graph Lab is a Linux-only educational tool that maps AI-agent session state transitions to real OverlayFS mounts.
- Node = one interaction state (overlay layer)
- Edge = parent relationship (lowerdir ancestry)
- Session = branch/timeline
- Revert = move active pointer without file copy
- Docs Index
- Backend OverlayFS Implementation
- Backend API and Request Flows
- Operations and Troubleshooting
backend/app/services/overlay_manager.py: mount/unmount and mount lifecycle managementbackend/app/services/graph_store.py: persistent session/node graph metadatabackend/app/services/file_service.py:.txt/.mdCRUD and node diff generationbackend/app/services/cleanup.py: startup orphan mount cleanup + idle TTL unmount
Graph-first UI built with React Flow:
- Click node: inspect files/layers
- Right-click node:
- create interaction node
- branch session
- revert session to node
- diff from active
Secondary panel:
- layer inspector (
lowerdir/upperdir/workdir/merged) - merged-view file operations (
.txt,.md) - diff viewer
- contextual OverlayFS learning cues (
ibuttons) + full in-app guide popup
Session UX:
- app auto-creates a default
startsession if graph is empty - branch sessions are shown with fallback labels
branch-1,branch-2, ... when unnamed - reset button clears node/session data and reboots the graph back to
start
Node directory layout:
overlay_lab/
base/
nodes/
<node_id>/
upper/
work/
merged/
sessions/
<session_id>.json
- lowerdir =
overlay_lab/base - upperdir/workdir/merged created under node directory
- mounted via:
mount -t overlay overlay -o lowerdir=<base>,upperdir=<upper>,workdir=<work> <merged>- parent = selected or active node
- lowerdirs are flattened from parent ancestry (no nested overlay-on-overlay dependency):
lowerdirs = [parent.upperdir, ...parent.lowerdirs]- duplicates removed while preserving order
- new empty
upper/work - mount new
merged
- only
active_node_idchanges - no file copying
- mount performed on-demand if needed
- new session root parent = selected source node
- new session root lowerdirs follow source ancestry stack:
lowerdirs = [source.upperdir, ...source.lowerdirs]
- independent active pointer and future node chain
- avoids brittle deep chains like
lowerdir=<other_node_merged> - keeps mounts stable across long interaction histories
- makes layer provenance explicit in inspector (
upperlineage +base)
- Linux kernel with OverlayFS support (
/proc/filesystemsincludesoverlay) or available overlay module files - Mount capability (this implementation expects root privileges)
mount/umountbinaries available
Preflight endpoint:
GET /health/preflight
POST /session/createPOST /node/createPOST /session/branch/{node_id}POST /node/revert/{node_id}GET /node/{node_id}/filesPOST /node/{node_id}/fileDELETE /node/{node_id}/fileGET /graphPOST /admin/resetGET /node/{node_id}/layersGET /diff?from_node_id=...&to_node_id=...
uv sync
make backend-devIf running directly on host, use root for mount operations:
sudo env "PATH=/home/<user>/.local/bin:$PATH" make backend-devcd frontend
npm ci
npm run devSet API base if needed:
export VITE_API_BASE=http://localhost:8000Build and run:
docker compose up --buildOpen:
http://localhost:8000(React UI served by FastAPI)
Notes:
- This container is configured with
privileged: truebecause OverlayFS mounts require elevated privileges. - Persistent graph/layer metadata is stored in the named Docker volume
overlay_lab_data. - APIs are served from the same origin (
http://localhost:8000), so no frontend API URL setup is needed. - Docker image now uses deterministic frontend dependency install via
npm ci.
make testNote: mount integration behavior requires Linux + mount privileges. Included tests focus on API, graph persistence, and lifecycle logic with mocked mounts.
OVERLAY_NOT_SUPPORTED: host is not Linux or overlay module unavailable.MOUNT_FAILED: run backend with sufficient privileges and check mount options/path permissions.- stale mounts after crash: restart backend to trigger startup orphan cleanup, or manually
umount overlay_lab/nodes/*/merged. - On macOS, running in Docker can work because Docker Desktop runs Linux inside a VM. It is still kernel/privilege dependent, so keep
privileged: trueand test withGET /health/preflight. - inspector path preview looks stale after file save: hover again or keep hover active; cache refresh is wired to file list updates.