# althing-chamber stack — chamber + forseti + agent-runner + valkey. # # Four services on the compose default network: # althing-chamber — FastAPI/HTMX app (port 7881 host → 8000 container) # althing-forseti — moderator daemon (no port; cross-process glue via the DB) # althing-agent-runner — Phase 2 daemon: claims worldtree-driver grants and # dispatches to Worldtree's conversation API (no port) # valkey — Phase 3.1 sibling service: Valkey 8 alpine, redis-protocol # pub/sub bridge for cross-process streaming events from # agent-runner → chamber SSE subscribers. Reached via # docker DNS at `valkey:6379` on the default network. # No exposed port; internal-only. # # Three storage / IPC channels: # - SQLite bind-mount at /app/data backs the althing.db state shared by # chamber + forseti + agent-runner (eventbus.bridge_from_db drains DB # commits into chamber's SSE). # - Valkey pub/sub on docker-default network carries the Phase 3 streaming # events (msg_start/thinking/delta/complete/curated) that don't go # through SQLite — too high-volume + ephemeral for the DB. # - chamber's healthcheck-blocked startup gate on valkey ensures the # subscriber side is up before chamber begins handling SSE traffic. # # Image is built on the host from the vh/althing git repo by the deploy # playbook (`playbooks/deploy-althing-chamber.yaml`), which clones into # /opt/docker/build/althing-chamber and runs `docker build -t # althing-chamber:local .` before installing this compose and bringing # both services up. No registry. # # Internal tooling — accessed directly at http://10.250.50.70:7881 over # the LAN; does NOT traverse Traefik. State persists under # /opt/docker/conf/althing-chamber/data on the host. # # All tunables live in .env — edit that, not this file. services: valkey: image: valkey/valkey:8-alpine container_name: althing-valkey restart: unless-stopped # No exposed port — chamber + agent-runner reach via docker DNS # at `valkey:6379` on the compose default network. No volume — # the pub/sub channel doesn't persist anything between restarts. healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 5s timeout: 3s retries: 5 althing-chamber: image: ${ALTHING_IMAGE} container_name: althing-chamber restart: unless-stopped depends_on: valkey: condition: service_healthy ports: - "${ALTHING_BIND:-0.0.0.0}:${ALTHING_PORT}:8000" environment: # ALTHING_ROOT moves the entire ~/.althing dir; ALTHING_DB additionally # pins the SQLite path explicitly. Both point inside the bind-mount. - ALTHING_ROOT=/app/data - ALTHING_DB=/app/data/althing.db # ALTHING_BIND / ALTHING_PORT — galdrabok-side env-var precedence # (env > config.yaml > defaults) is being added in the same cycle # as this scaffold lands. Container always listens on 8000 internally; # the host port mapping above is the only externally-visible knob. - ALTHING_BIND=0.0.0.0 - ALTHING_PORT=8000 volumes: - ${ALTHING_DATA_DIR}:/app/data command: ["althing-chamber"] healthcheck: # Liveness probe — chamber's /health endpoint returns 200 with no DB # read (true liveness, not readiness). galdrabok adds this endpoint # in the same cycle as this scaffold; container will crashloop on # healthcheck until that lands. 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=Toolchain - homepage.name=althing chamber - homepage.icon=mdi-bullhorn - homepage.description=Web UI for the althing inter-agent message bus - homepage.href=http://10.250.50.70:${ALTHING_PORT} althing-forseti: image: ${ALTHING_IMAGE} container_name: althing-forseti restart: unless-stopped environment: - ALTHING_ROOT=/app/data - ALTHING_DB=/app/data/althing.db volumes: - ${ALTHING_DATA_DIR}:/app/data command: ["althing-forseti"] # No healthcheck — the forseti CLI doesn't expose one. Liveness signal # for ops is "container hasn't exited" + chamber-side observation that # bridge_from_db events are flowing. althing-agent-runner: image: ${ALTHING_IMAGE} container_name: althing-agent-runner restart: unless-stopped depends_on: valkey: condition: service_healthy environment: - ALTHING_ROOT=/app/data - ALTHING_DB=/app/data/althing.db volumes: - ${ALTHING_DATA_DIR}:/app/data command: ["althing-agent-runner"] # Phase 2 daemon (added 2026-05-16). Polls floor_grants WHERE # consumed_at IS NULL AND agents.driver='worldtree', claims via # atomic UPDATE, calls Worldtree's conversation API, posts the # agent's response back through the bus as a broadcast. Harmless # when no driver=worldtree handles are declared — runner sleeps # at poll_interval_seconds. Multi-instance safe via claim_grant's # atomic UPDATE; no flock required. Same lack-of-healthcheck story # as forseti (CLI doesn't expose one).