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>:8080for the web clienthttp://<your-unraid-ip>:8999/statusfor 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/projectsby default. Your project workspaces live here. Themanual_slop.tomlper project is in this directory./config— Mounted from/mnt/user/appdata/manual_slopby 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.ymlto 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_slopfor 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. ✓