Private
Public Access
0
0
Files
manual_slop/docs/superpowers/plans/2026-06-02-docker-web-frontend.md
T

18 KiB

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). 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: <type>(<scope>): <imperative description>.

  • 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

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:

# 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:

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
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

git -C C:\projects\manual_slop add .
  • Step 2.2: Create the 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
.git
__pycache__
*.pyc
.pytest_cache
.ruff_cache
.venv
.env
tests/artifacts/
tests/logs/
logs/
md_gen/
*.log
  • Step 2.4: Commit
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

git -C C:\projects\manual_slop add .
  • Step 3.2: Create the compose file
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
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

git -C C:\projects\manual_slop add .
  • Step 4.2: Create scripts/docker_build.sh
#!/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
#!/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
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

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:

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
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

git -C C:\projects\manual_slop add .
  • Step 6.2: Create the test file
# 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"<html" in body or b"<!doctype" in body, "Web endpoint did not return HTML"

    finally:
        _cleanup_container()


def _docker_available() -> 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
uv run pytest tests/test_docker_build.py -v

Expected: 2 skipped.

  • Step 6.4: Commit
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

git -C C:\projects\manual_slop add .
  • Step 7.2: Create the guide
# 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:

./scripts/docker_build.sh

Running the Container

Edit docker-compose.yml to set your volume paths and provider keys (via .env file or environment).

# Create a .env file with your API keys
cat > .env <<EOF
GEMINI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
DEEPSEEK_API_KEY=your-key-here
MINIMAX_API_KEY=your-key-here
EOF

# Start the container
docker compose up -d

Accessing the GUI

Open a browser and navigate to:

  • http://<your-unraid-ip>:8080 for the web client
  • http://<your-unraid-ip>: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:

# Check status
curl http://<your-unraid-ip>:8999/status

# Get MMA state
curl http://<your-unraid-ip>:8999/api/gui/mma_status

See 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

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/<project>/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
# 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
uv run pytest tests/test_docker_build.py -v

Expected: 2 skipped.

  • Step 8.3: If Docker is available, run the tests
RUN_DOCKER_TEST=1 uv run pytest tests/test_docker_build.py -v
  • Step 8.4: Create the checkpoint commit
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. ✓