Files
esh-pfi-infrastructure/stacks/skaldsong/compose.yaml
T
vh 569e1af9ca feat(homepage): split AI fleet into role-based groups on a dedicated AI tab
Move the ~22-service flat "AI Systems" group off the Main tab into a new
four-tab layout (Main / AI / Infrastructure / Toolchain). The AI tab sorts
the inference fleet by function into seven groups:

  AI - Inference        gen, char-rp, char-rp-reasoning, Granite summarizer
  AI - Eval & Retrieval Selene, Skywork Reward, Qwen3 rerank/embed, image-bench
  AI - Gateways & Chat  LiteLLM, Asset Engine, Gateway Chat, Open WebUI, ...
  AI - Speech (TTS)     Chatterbox Fast, Kokoro, mOrpheus
  AI - Audio Tools      Parakeet ASR, YT Voice Clipper
  AI - Image & Media    ComfyUI, Arbo
  AI - Dormant          stopped rollback seats + retired auditions

Relabel each stack's homepage.group so canonical stacks/ matches the live
containers on ana-ml2, ana-docker, and irv-ml1. Dormant stacks were refreshed
with `docker compose up --no-start` so they carry the new label while staying
stopped (compose-start rollback preserved). settings.yaml drives tab/order/
columns; services.yaml and README updated to the new scheme.
2026-07-14 20:05:50 -07:00

121 lines
6.0 KiB
YAML

# skaldsong stack — single FastAPI + SPA container.
#
# Skaldsong is the wizard + reader surface that talks to Worldtree as a
# Bifrost consumer. One process serves both the SvelteKit-style SPA
# (static, baked into the image) and the wizard backend (FastAPI),
# plus the Bifrost endpoint Worldtree calls back to.
#
# Image is built + pushed by vh/skaldsong's `.gitea/workflows/deploy.yaml`
# to gitea.phasefinal.com/vh/skaldsong, then pulled here. Workflow
# passes the triggering commit SHA via `--var ref=...` to elway, which
# substitutes it into ${SKALDSONG_IMAGE} for this compose. SHA-pinned
# only by current preference — no :latest moving-tag advance (re-evaluate
# once the /health endpoint exercises Worldtree + Kokoro reachability).
#
# Internal tooling — accessed directly on host:port over the LAN, does
# NOT traverse Traefik. State persists under /opt/docker/conf/skaldsong/
# {db,runs} on the host; db/ holds skaldsong-ui.db (SQLite, wizard state
# + story rows), runs/ holds per-story generation pipeline artifacts
# (spec, state, output, logs, manuscripts).
#
# Per skaldsong's INV-006: process restart marks any 'running' generation
# rows 'partial' and never auto-resumes — deploy mid-run is safe; in-flight
# work looks to the SPA like a power outage.
#
# All tunables live in .env — edit that, not this file.
services:
skaldsong-host:
image: ${SKALDSONG_IMAGE}
container_name: skaldsong
restart: unless-stopped
ports:
- "${SKALDSONG_BIND:-0.0.0.0}:${SKALDSONG_PORT}:8000"
environment: &skaldsong-env
# App always listens on 8000 internally; host port is the only knob.
# Worldtree integration — JWT key is the shared-secret Bearer per
# Worldtree's HS256 path (same value as Worldtree's own
# WORLDTREE_SKALDSONG_USER_KEY). NEVER commit; .env on the server
# holds the real value.
SKALDSONG_BIFROST_JWT_KEY: "${SKALDSONG_BIFROST_JWT_KEY:-}"
# URL Worldtree uses to call BACK to skaldsong's Bifrost endpoint
# at tool-dispatch time. This is skaldsong's host-side reachable
# URL from corviduo-dev's perspective — must match Worldtree's
# BIFROST_CLIENT_ALLOWED_HOSTS entry. Coordinated allowlist update
# required if host:port changes.
SKALDSONG_HOST_BIFROST_ENDPOINT_URL: "${SKALDSONG_HOST_BIFROST_ENDPOINT_URL:-}"
# Worldtree API client. WORLDTREE_TOKEN is the SAME shared-secret
# Bearer as SKALDSONG_BIFROST_JWT_KEY above — different code path
# consumes it (outbound HTTP Authorization header vs inbound HS256
# JWT verify). WORLDTREE_BASE_URL is corviduo-dev's IP:port
# LAN-direct from ana-docker.
WORLDTREE_TOKEN: "${WORLDTREE_TOKEN:-}"
WORLDTREE_BASE_URL: "${WORLDTREE_BASE_URL:-http://10.250.50.152:8080}"
# Wizard agent ID on Worldtree. MUST be pinned to the existing
# agent slot (`skaldsong:wizard-v2` per skaldsong-dev's prior
# hand-launched setup); leaving blank on first boot would POST
# /agents/define and burn another slot of the 50-agent-per-key
# Heimdall quota. Heimdall scopes agent_id to user_id, so the
# ana-docker deployment inherits the same id as the nh3-dev
# hand-launch.
SKALDSONG_HOST_WIZARD_AGENT_ID: "${SKALDSONG_HOST_WIZARD_AGENT_ID:-skaldsong:wizard-v2}"
# CORS for the SPA. Comma-separated origins; include both the
# public hostname and any dev origins still in rotation.
SKALDSONG_HOST_CORS_ORIGINS: "${SKALDSONG_HOST_CORS_ORIGINS:-}"
# TTS — Kokoro on irv-ml1, reached over WireGuard via ana-wg from
# ana-docker. Same URL works fleet-wide.
SKALDSONG_TTS_ENGINE: "${SKALDSONG_TTS_ENGINE:-kokoro}"
SKALDSONG_TTS_BASE_URL: "${SKALDSONG_TTS_BASE_URL:-http://10.100.79.3:8193}"
# VibeVoice secondary TTS engine (issue #73, added 2026-05-27).
# Same fleet (irv-ml1) as Kokoro, separate port 8194. Encode-once
# batch-synth path (long-form bedtime narration); SPA shows a
# second "encode with vibevoice" row below the Kokoro playback bar.
# Empty/unset → feature stays dormant (routes 404, SPA button hidden).
SKALDSONG_TTS_VIBEVOICE_BASE_URL: "${SKALDSONG_TTS_VIBEVOICE_BASE_URL:-}"
SKALDSONG_TTS_VIBEVOICE_DEFAULT_VOICE: "${SKALDSONG_TTS_VIBEVOICE_DEFAULT_VOICE:-fable}"
# SPA static assets — baked into the image at /app/spa by the
# Dockerfile's web-builder stage (COPYs the SvelteKit build output
# flat into /app/spa, not into /app/spa/dist).
SKALDSONG_HOST_STATIC_ASSETS_PATH: /app/spa
# Persistent state paths inside the container. The db/ parent
# dir is the bind-mount target (not the file itself) so SQLite
# can write its WAL + SHM siblings. Names MUST match what the
# app actually reads (per skaldsong's Dockerfile ENV defaults):
# SKALDSONG_HOST_SQLITE_PATH + SKALDSONG_HOST_RUNS_ROOT. Earlier
# this block used SKALDSONG_DB_PATH / SKALDSONG_RUNS_DIR — those
# names are orthogonal to what the app reads, so the app fell
# back to Dockerfile defaults pointing at /app/data/... which
# is NOT bind-mounted → state wiped on every recreate.
SKALDSONG_HOST_SQLITE_PATH: /app/state/db/skaldsong-ui.db
SKALDSONG_HOST_RUNS_ROOT: /app/state/runs
volumes:
# db/ and runs/ are separate bind-mounts so runs/ can later move to
# a bigger volume without touching DB state.
- ${SKALDSONG_DB_DIR}:/app/state/db
- ${SKALDSONG_RUNS_DIR_HOST}:/app/state/runs
healthcheck:
test: ["CMD-SHELL", "python -c 'import urllib.request,sys; r=urllib.request.urlopen(\"http://127.0.0.1:8000/health\",timeout=3); sys.exit(0 if r.status==200 else 1)' || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
labels:
- homepage.group=AI - Gateways & Chat
- homepage.name=Skaldsong
- homepage.icon=mdi-book-edit-outline
- homepage.description=Wizard + reader surface for multi-agent storytelling (Bifrost consumer of Worldtree)
- homepage.href=http://10.250.50.70:${SKALDSONG_PORT}
networks:
default:
name: skaldsong_default