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

skaldsong

Wizard + reader surface for multi-agent storytelling. Single FastAPI process serves both the SvelteKit-built SPA (static, baked into the image) and the wizard backend. Talks to Worldtree as a Bifrost consumer — Worldtree issues per-dispatch JWTs (post-v0.3) that skaldsong's /bifrost endpoint verifies against the shared HS256 secret.

Server: ana-docker URL: http://10.250.50.70:8300 (configurable via .env) Upstream repo: vh/skaldsong Image: gitea.phasefinal.com/vh/skaldsong:<sha> — built + pushed by vh/skaldsong's CI on every push to main and on tag.

Path layout (on ana-docker)

Host path Container path Purpose Restic?
/opt/docker/compose/skaldsong/ compose.yaml + .env included (via /opt/docker)
/opt/docker/conf/skaldsong/db/ /app/state/db SQLite (skaldsong-ui.db + WAL/SHM) — wizard state, story rows included
/opt/docker/conf/skaldsong/runs/ /app/state/runs Per-story generation pipeline artifacts (spec, state, output, logs, manuscripts) included

Network model

Internal tooling, LAN-only. Container's :8000 published on host :8300 (configurable via SKALDSONG_BIND / SKALDSONG_PORT); access direct at http://10.250.50.70:8300. No Traefik, no TLS terminator, no public hostname.

Skaldsong talks to two services on the LAN:

  • Worldtree at http://10.250.50.152:8080 (corviduo-dev, LAN-direct from ana-docker — same subnet).
  • Kokoro TTS at http://10.100.79.3:8193 (irv-ml1, reached via the WireGuard tunnel on ana-wg — same path asset-engine uses).

Worldtree calls back to skaldsong's /bifrost endpoint at http://10.250.50.70:8300 for tool-dispatch — that URL must be in Worldtree's BIFROST_CLIENT_ALLOWED_HOSTS. Host:port changes require a coordinated allowlist update on Worldtree-side (see corviduo-dev README for the docker-as-root edit pattern).

Deploy

Two paths — automated (preferred) and manual (escape hatch).

Automated (Gitea Actions, push-to-main)

vh/skaldsong ships .gitea/workflows/deploy.yaml. Push to main + manual workflow_dispatch triggers:

  1. Build + push gitea.phasefinal.com/vh/skaldsong:${{ github.sha }} and :latest to the gitea registry.
  2. Checkout this management repo using MGMT_REPO_TOKEN.
  3. Run scripts/elway ana-docker --playbook playbooks/deploy-skaldsong.yaml --var ref=${{ github.sha }} via DEPLOY_SSH_KEY.

Three secrets required on vh/skaldsong's Actions settings:

  • DEPLOY_SSH_KEY — private key authorized on ana-docker for lkraven.
  • MGMT_REPO_TOKEN — Gitea PAT with read:repository on this repo.
  • REGISTRY_USER + REGISTRY_TOKEN — for the docker login step (token needs write:package scope).

Manual (elway from a workstation)

# Deploy a specific SHA (CI just pushed it):
scripts/elway ana-docker --playbook playbooks/deploy-skaldsong.yaml \
    --var ref=<sha>

# Or pin to a release tag:
scripts/elway ana-docker --playbook playbooks/deploy-skaldsong.yaml \
    --var ref=v0.1.0

Env-var contract

Var In compose? In .env? Notes
SKALDSONG_IMAGE ✓ (image:) Full image URI; CI passes SHA-pin via elway --var ref=<sha> → playbook → this var.
SKALDSONG_PORT Host-side port (8300 default). Internal always 8000.
SKALDSONG_BIND Host bind address (0.0.0.0 default).
SKALDSONG_DB_DIR ✓ (mount) Host path for SQLite.
SKALDSONG_RUNS_DIR_HOST ✓ (mount) Host path for generation artifacts.
SKALDSONG_BIFROST_JWT_KEY ✓ (anchor) Shared HS256 secret w/ Worldtree's WORLDTREE_SKALDSONG_USER_KEY. Same value as WORLDTREE_TOKEN.
SKALDSONG_HOST_BIFROST_ENDPOINT_URL ✓ (anchor) URL Worldtree calls back to (must match allowlist).
WORLDTREE_TOKEN ✓ (anchor) Bearer for outbound HTTP to Worldtree. Same value as SKALDSONG_BIFROST_JWT_KEY.
WORLDTREE_BASE_URL ✓ (anchor) URL skaldsong calls Worldtree at.
SKALDSONG_HOST_WIZARD_AGENT_ID ✓ (anchor) Existing agent slot ID (skaldsong:wizard-v2). Blank burns a Heimdall quota slot on first boot.
SKALDSONG_HOST_CORS_ORIGINS ✓ (anchor) Comma-separated CORS origins.
SKALDSONG_TTS_ENGINE ✓ (anchor) kokoro (only engine in v1).
SKALDSONG_TTS_BASE_URL ✓ (anchor) Kokoro URL (WG-routed).

&skaldsong-env anchor declares all SKALDSONG_* vars via ${VAR:-} substitution. Same env-anchor pattern as Worldtree's &worldtree-env (see Worldtree #175 heuristic); new var additions must land in BOTH the .env AND the anchor.

Restart semantics

Per skaldsong's INV-006 (host CLAUDE.md): process restart marks any running generation rows partial and never auto-resumes. Deploy mid-run is safe — the SPA's reader treats partials like a power outage. Cancellation of in-flight runs on deploy is acceptable for v1.

docker compose up -d --force-recreate --pull always is the playbook's apply step (or --pull never when called with a specific --var ref=<sha> where the image was pre-pulled). Same kill-signal + graceful-shutdown pattern as Worldtree.

Worldtree allowlist coordination

The SKALDSONG_HOST_BIFROST_ENDPOINT_URL value MUST be in Worldtree's BIFROST_CLIENT_ALLOWED_HOSTS env var on corviduo-dev. As of deploy-time the value is 10.250.50.70:8300.

If you change SKALDSONG_PORT or move skaldsong off ana-docker, the allowlist needs a paired update on corviduo-dev. Reference: the edit-via-docker-as-root pattern in servers/corviduo-dev/README.md.