# Docker & Web Frontend Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Containerize Manual Slop for Unraid deployment. Add a web frontend via the imgui-bundle web backend (per the [explorer docs](https://imgui-bundle.pages.dev/explorer/)). Preserve the existing Hook API on :8999 for agent access. **Architecture:** Python 3.11-slim base image with `uv` for dependency management. The container runs `sloppy.py` in web mode (via a new `--web-host` / `--web-port` arg), which uses the imgui-bundle Hello ImGui web backend to render ImGui to a WebGL canvas in the browser. Two exposed ports: 8080 (web client) and 8999 (hook API). Volumes for project data and app state. **Tech Stack:** Docker, docker-compose, Python 3.11, imgui-bundle (web backend), FastAPI/Uvicorn (existing, for hooks) - **[checkpoint: 99a84bd]** - **No subagents.** - **Pre-edit checkpoint:** `git add .` before any file edit. - **Per-file atomic commits.** - **Commit message format:** `(): `. - **Git note format:** 3-8 line rationale per commit. - **Style baseline:** 1-space indent, no comments, type hints. --- ## File Structure | File | Action | Responsibility | |---|---|---| | `Dockerfile` | Create | Container build for Manual Slop | | `docker-compose.yml` | Create | Multi-container orchestration for Unraid | | `scripts/docker_build.sh` | Create | Build helper | | `scripts/docker_run.sh` | Create | Run helper with env var wiring | | `sloppy.py` | Modify | Add `--web-host` and `--web-port` args | | `docs/guide_docker_deployment.md` | Create | Unraid setup guide | | `tests/test_docker_build.py` | Create | Opt-in Docker build test | | `pyproject.toml` | Modify | Add `docker` marker | --- ## Task 1: Add web args to `sloppy.py` **Files:** - Modify: `sloppy.py` - [ ] **Step 1.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 1.2: Read current `sloppy.py`** Use `manual-slop_read_file` to see current contents. - [ ] **Step 1.3: Add the new args** Find the existing argument parser setup and add the web args. If there isn't an argparse, look for how args are handled. Add: ```python # In sloppy.py, after existing argument definitions import argparse parser = argparse.ArgumentParser(description="Manual Slop entry point") # ... existing args ... parser.add_argument("--web-host", default=None, help="Enable web mode and bind to this host (e.g., 0.0.0.0)") parser.add_argument("--web-port", type=int, default=8080, help="Web mode port (default: 8080)") parser.add_argument("--enable-test-hooks", action="store_true", help="Enable the HookServer on :8999 for external automation") args = parser.parse_args() ``` If the existing entry point uses a different mechanism, integrate the args there. - [ ] **Step 1.4: Add the web mode branch** After argument parsing, before the existing main launch, add: ```python if args.web_host is not None: from imgui_bundle import hello_imgui from src.api_hooks import HookServer if args.enable_test_hooks: hook_server = HookServer() hook_server.start() runner_params = hello_imgui.RunnerParams() runner_params.app_window_params.window_title = "Manual Slop (Web)" runner_params.app_window_params.borderless = True runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_docker_space runner_params.app_window_params.restore_previous_window_size = True from src.gui_2 import App app = App() hello_imgui.run(runner_params, lambda: app.render_frame()) ``` - [ ] **Step 1.5: Commit** ```bash git -C C:\projects\manual_slop add sloppy.py git -C C:\projects\manual_slop commit -m "feat(sloppy): add --web-host and --web-port args for web mode" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Adds --web-host (enables web mode) and --web-port (default 8080) args. When --web-host is set, launches via Hello ImGui web backend with full-screen docking. Preserves --enable-test-hooks for agent access via :8999." $_ } ``` --- ## Task 2: Create the Dockerfile **Files:** - Create: `Dockerfile` - [ ] **Step 2.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 2.2: Create the Dockerfile** ```dockerfile FROM python:3.11-slim RUN apt-get update && apt-get install -y --no-install-recommends \ git curl ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN pip install uv WORKDIR /app COPY pyproject.toml uv.lock ./ RUN uv sync --frozen COPY . . RUN mkdir -p /projects /config VOLUME ["/projects", "/config"] EXPOSE 8080 8999 HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD curl -f http://127.0.0.1:8999/status || exit 1 ENTRYPOINT ["uv", "run", "sloppy.py", "--enable-test-hooks", "--web-host=0.0.0.0", "--web-port=8080"] ``` - [ ] **Step 2.3: Add a `.dockerignore`** ```dockerignore # .dockerignore .git __pycache__ *.pyc .pytest_cache .ruff_cache .venv .env tests/artifacts/ tests/logs/ logs/ md_gen/ *.log ``` - [ ] **Step 2.4: Commit** ```bash git -C C:\projects\manual_slop add Dockerfile .dockerignore git -C C:\projects\manual_slop commit -m "feat(docker): add Dockerfile and .dockerignore for containerized deployment" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Python 3.11-slim base, uv for dep management, copies pyproject + uv.lock first for layer caching. Exposes 8080 (web) and 8999 (hooks). Volumes for /projects and /config. Healthcheck on hook status. .dockerignore excludes test artifacts and git." $_ } ``` --- ## Task 3: Create the docker-compose.yml **Files:** - Create: `docker-compose.yml` - [ ] **Step 3.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 3.2: Create the compose file** ```yaml version: '3.8' services: manual_slop: build: . image: manual_slop:latest container_name: manual_slop ports: - "8999:8999" - "8080:8080" volumes: - /mnt/user/projects:/projects:rw - /mnt/user/appdata/manual_slop:/config:rw environment: - GEMINI_API_KEY=${GEMINI_API_KEY:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-} - MINIMAX_API_KEY=${MINIMAX_API_KEY:-} restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8999/status"] interval: 30s timeout: 5s retries: 3 start_period: 30s ``` - [ ] **Step 3.3: Commit** ```bash git -C C:\projects\manual_slop add docker-compose.yml git -C C:\projects\manual_slop commit -m "feat(docker): add docker-compose.yml for Unraid deployment" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two ports (8999 hooks, 8080 web), two volumes (projects, appdata config), env vars for 4 providers, healthcheck on hook status. Unraid-friendly paths." $_ } ``` --- ## Task 4: Create the build/run scripts **Files:** - Create: `scripts/docker_build.sh` - Create: `scripts/docker_run.sh` - [ ] **Step 4.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 4.2: Create `scripts/docker_build.sh`** ```bash #!/usr/bin/env bash # scripts/docker_build.sh # Build the Manual Slop Docker image. set -euo pipefail cd "$(dirname "$0")/.." docker build -t manual_slop:latest . ``` - [ ] **Step 4.3: Create `scripts/docker_run.sh`** ```bash #!/usr/bin/env bash # scripts/docker_run.sh # Run the Manual Slop container. set -euo pipefail cd "$(dirname "$0")/.." docker compose up -d ``` - [ ] **Step 4.4: Make scripts executable and commit** ```bash git -C C:\projects\manual_slop add scripts/docker_build.sh scripts/docker_run.sh git -C C:\projects\manual_slop commit -m "feat(docker): add build and run shell scripts" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two thin shell scripts: build (docker build -t manual_slop:latest .) and run (docker compose up -d). Will be made executable in chmod step during deployment." $_ } ``` --- ## Task 5: Add the `docker` marker to `pyproject.toml` **Files:** - Modify: `pyproject.toml` - [ ] **Step 5.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 5.2: Add the marker** Find the markers list (from Task 1 of the clean-install plan) and add: ```toml markers = [ "integration: integration tests requiring live GUI", "strict: tests that require strict mode", "clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)", "docker: docker build and run test (opt-in via RUN_DOCKER_TEST=1)", ] ``` - [ ] **Step 5.3: Commit** ```bash git -C C:\projects\manual_slop add pyproject.toml git -C C:\projects\manual_slop commit -m "test(pytest): add docker marker" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Registers the docker marker. Tests using Docker can be filtered with -m docker or -m 'not docker'." $_ } ``` --- ## Task 6: Write the Docker build test **Files:** - Create: `tests/test_docker_build.py` - [ ] **Step 6.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 6.2: Create the test file** ```python # tests/test_docker_build.py import os import shutil import subprocess import time import pytest import requests IMAGE_NAME = "manual_slop:test" CONTAINER_NAME = "manual_slop_test_container" WEB_PORT = 18080 HOOK_PORT = 18999 @pytest.mark.docker def test_docker_image_builds(tmp_path): """Build the Docker image. Slow; opt-in.""" if os.environ.get("RUN_DOCKER_TEST") != "1": pytest.skip("Set RUN_DOCKER_TEST=1 to enable") if not _docker_available(): pytest.skip("Docker not available in this environment") repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) result = subprocess.run( ["docker", "build", "-t", IMAGE_NAME, "."], cwd=repo_root, capture_output=True, text=True, timeout=600, ) assert result.returncode == 0, f"Docker build failed: {result.stderr}" @pytest.mark.docker def test_docker_container_starts_and_responds(): """Run the container, verify web and hook endpoints respond.""" if os.environ.get("RUN_DOCKER_TEST") != "1": pytest.skip("Set RUN_DOCKER_TEST=1 to enable") if not _docker_available(): pytest.skip("Docker not available in this environment") _cleanup_container() repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) result = subprocess.run( [ "docker", "run", "-d", "--name", CONTAINER_NAME, "-p", f"{WEB_PORT}:8080", "-p", f"{HOOK_PORT}:8999", IMAGE_NAME, ], cwd=repo_root, capture_output=True, text=True, timeout=30, ) assert result.returncode == 0, f"Docker run failed: {result.stderr}" try: start = time.time() ready = False while time.time() - start < 90: try: r = requests.get(f"http://127.0.0.1:{HOOK_PORT}/status", timeout=1) if r.status_code == 200: ready = True break except (requests.ConnectionError, requests.Timeout): pass time.sleep(1) assert ready, "Container hook API did not respond within 90s" r = requests.get(f"http://127.0.0.1:{WEB_PORT}/", timeout=5) assert r.status_code == 200 body = r.content.lower() assert b" bool: return shutil.which("docker") is not None def _cleanup_container() -> None: subprocess.run( ["docker", "rm", "-f", CONTAINER_NAME], capture_output=True, ) ``` - [ ] **Step 6.3: Run in skip mode** ```powershell uv run pytest tests/test_docker_build.py -v ``` Expected: 2 skipped. - [ ] **Step 6.4: Commit** ```bash git -C C:\projects\manual_slop add tests/test_docker_build.py git -C C:\projects\manual_slop commit -m "test(docker): add opt-in build and container-run tests" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two opt-in tests: build image (RUN_DOCKER_TEST=1), run container and verify hook + web endpoints. Skipped by default. Docker daemon required." $_ } ``` --- ## Task 7: Write the Unraid deployment guide **Files:** - Create: `docs/guide_docker_deployment.md` - [ ] **Step 7.1: Pre-edit checkpoint** ```powershell git -C C:\projects\manual_slop add . ``` - [ ] **Step 7.2: Create the guide** ```markdown # Docker Deployment Guide (Unraid) [Top](../README.md) | [Architecture](guide_architecture.md) | [Tools & IPC](guide_tools.md) --- ## Overview This guide covers deploying Manual Slop on Unraid (or any Docker host) using the containerized image. The deployment provides: - A web-accessible ImGui GUI (browser-based, no local display required) - The Hook API on `:8999` for agent access - Persistent volumes for projects and app state ## Prerequisites - Unraid 6.10+ (or any Docker host with compose support) - A project share mounted at `/mnt/user/projects` (or edit `docker-compose.yml` to match your path) - API keys for the providers you want to use ## Building the Image From the repo root: ```bash docker build -t manual_slop:latest . ``` Or use the helper: ```bash ./scripts/docker_build.sh ``` ## Running the Container Edit `docker-compose.yml` to set your volume paths and provider keys (via `.env` file or environment). ```bash # Create a .env file with your API keys cat > .env <:8080` for the web client - `http://:8999/status` for the hook API health check The web client renders the ImGui panels via WebGL. The Hello ImGui web backend streams frame deltas over WebSocket. ## Agent Access Agents interact with the running container via the Hook API on `:8999`. Examples: ```bash # Check status curl http://:8999/status # Get MMA state curl http://:8999/api/gui/mma_status ``` See [guide_tools.md](guide_tools.md) for the full Hook API reference. ## Volumes - `/projects` — Mounted from `/mnt/user/projects` by default. Your project workspaces live here. The `manual_slop.toml` per project is in this directory. - `/config` — Mounted from `/mnt/user/appdata/manual_slop` by default. App state: presets, personas, log directory, workspace profiles. ## Updating ```bash git pull docker build -t manual_slop:latest . docker compose up -d ``` ## Backup Back up `/config` to preserve presets, personas, and workspace profiles. Back up `/projects//conductor/` to preserve track history. ## Troubleshooting - **Port conflicts:** Edit `docker-compose.yml` to change the host port (e.g., `"18080:8080"` to use 18080 on the host). - **Permission errors:** Ensure the Unraid share has write permissions for the container's UID. - **Hook API not responding:** Check `docker logs manual_slop` for the startup output. The hook server should log "HookServer started on :8999". ``` - [ ] **Step 7.3: Commit** ```bash git -C C:\projects\manual_slop add docs/guide_docker_deployment.md git -C C:\projects\manual_slop commit -m "docs(docker): add Unraid deployment guide" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Unraid-specific setup guide: prerequisites, build, run via compose, web access URLs, agent access via hook API, volumes, updating, backup, troubleshooting." $_ } ``` --- ## Task 8: Phase Completion Verification - [ ] **Step 8.1: Verify all files exist and are syntactically valid** ```powershell # Python syntax python -c "import ast; ast.parse(open('sloppy.py').read())" # YAML syntax (if PyYAML available) python -c "import yaml; yaml.safe_load(open('docker-compose.yml').read())" # Dockerfile syntax (if hadolint available) # hadolint Dockerfile || echo "hadolint not installed, skipping" # Markdown lint (skip if not configured) ``` - [ ] **Step 8.2: Run the test suite in skip mode** ```powershell uv run pytest tests/test_docker_build.py -v ``` Expected: 2 skipped. - [ ] **Step 8.3: If Docker is available, run the tests** ```powershell RUN_DOCKER_TEST=1 uv run pytest tests/test_docker_build.py -v ``` - [ ] **Step 8.4: Create the checkpoint commit** ```bash git -C C:\projects\manual_slop commit --allow-empty -m "conductor(checkpoint): Docker & web frontend complete" git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Track complete. Dockerfile, docker-compose.yml, build/run scripts, --web-host arg, Unraid deployment guide, opt-in Docker build test. imgui-bundle web backend integration pending Hello ImGui runner config tuning." $_ } ``` --- ## Self-Review - **Spec coverage:** All design sections have tasks. ✓ - **Placeholder scan:** All code blocks are complete. ✓ - **Type consistency:** Args named consistently (`--web-host`, `--web-port`, `--enable-test-hooks`). ✓ - **Risk acknowledged:** The web backend integration is experimental and may need iteration after testing. The checkpoint note flags this. ✓