diff --git a/playbooks/deploy-althing-chamber.yaml b/playbooks/deploy-althing-chamber.yaml index d2bf6b2..73026cc 100644 --- a/playbooks/deploy-althing-chamber.yaml +++ b/playbooks/deploy-althing-chamber.yaml @@ -2,14 +2,16 @@ # Docker host following the PFI /opt/docker/ convention (ana-docker by # default, but the playbook works against any host with Docker in place). # -# Brings up THREE compose services from a single image: +# Brings up FOUR compose services: # althing-chamber — FastAPI/HTMX web UI, port 7881 host → 8000 container # althing-forseti — moderator daemon, no port # althing-agent-runner — Phase 2 worldtree-driver dispatcher, no port +# althing-valkey — Phase 3.1 valkey 8 alpine, pub/sub bridge (internal only) # -# All three processes share the SQLite store via the same bind-mount under -# /opt/docker/conf/althing-chamber/data. eventbus.bridge_from_db is the -# cross-process glue. +# The three althing services use the same image (built from vh/althing); +# valkey is a stock upstream image. SQLite-backed state shares between the +# three althing services via the bind-mount under /opt/docker/conf/althing-chamber/data; +# streaming events ride pub/sub on the docker default network via valkey. # # Idempotent: rerunning is safe. Creates-gates skip work that's already # done; `docker compose up -d` is itself idempotent (no restart unless @@ -139,3 +141,7 @@ verify: - name: agent-runner container running shell: docker ps --filter name=^/althing-agent-runner$ --format '{{.Status}}' | grep -q '^Up' changed_when: "false" + + - name: valkey container running + healthy + shell: docker ps --filter name=^/althing-valkey$ --format '{{.Status}}' | grep -q 'healthy' + changed_when: "false" diff --git a/stacks/althing-chamber/README.md b/stacks/althing-chamber/README.md index b2bb922..70c03b5 100644 --- a/stacks/althing-chamber/README.md +++ b/stacks/althing-chamber/README.md @@ -1,13 +1,15 @@ # althing-chamber -Web UI + moderator daemon + agent-runner for the althing inter-agent -message bus. Three services share a single SQLite store via a -bind-mount; the chamber serves FastAPI/HTMX, the forseti daemon runs -the moderation + curation loops, and the agent-runner daemon claims -floor grants for worldtree-driver agents and dispatches them to -Worldtree's conversation API. Cross-process glue is -`eventbus.bridge_from_db` polling the same DB all three processes -write. +Web UI + moderator daemon + agent-runner + Valkey IPC bridge for the +althing inter-agent message bus. Four services on the compose default +network. The chamber serves FastAPI/HTMX, the forseti daemon runs the +moderation + curation loops, the agent-runner daemon claims floor +grants for worldtree-driver agents and dispatches them to Worldtree's +conversation API, and Valkey carries the Phase 3.1 cross-process +streaming events (msg_start/delta/thinking/complete/curated) that are +too high-volume to flow through SQLite. SQLite-backed state still +shares between chamber + forseti + agent-runner via the bind-mount; +streaming events ride pub/sub on the docker default network. **Server:** ana-docker **URL:** `http://10.250.50.70:7881` (configurable via `.env`) @@ -22,10 +24,13 @@ by the deploy playbook. Not pulled from a registry. | `althing-chamber` | FastAPI/HTMX web UI; SSE subscribers; `/health` endpoint | host 7881 → container 8000 | `python urllib /health` | | `althing-forseti` | Moderator + curator daemon; writes events that chamber's bridge picks up | — (no HTTP) | none (process-up signal only) | | `althing-agent-runner` | Phase 2: claims worldtree-driver floor grants and dispatches to Worldtree's conversation API | — (no HTTP) | none (process-up signal only) | +| `althing-valkey` | Phase 3.1: Valkey 8 redis-protocol pub/sub bridge — carries streaming events from agent-runner → chamber | — (internal-only, `valkey:6379` on default network) | `valkey-cli ping` | -All three use the same `${ALTHING_IMAGE}`; the `command:` line in compose -picks which entrypoint (`althing-chamber` / `althing-forseti` / -`althing-agent-runner`) runs in each container. +The three althing services (chamber/forseti/agent-runner) use the same +`${ALTHING_IMAGE}`; the `command:` line in compose picks which entrypoint +runs in each container. Valkey is a stock upstream image (no custom build). +chamber + agent-runner `depends_on: valkey: service_healthy` so the pub/sub +bridge is up before either side starts publishing or subscribing. The agent-runner is safe to enable preemptively — when no `driver=worldtree` handles are declared in config, it polls diff --git a/stacks/althing-chamber/compose.yaml b/stacks/althing-chamber/compose.yaml index 8529bd6..5388c03 100644 --- a/stacks/althing-chamber/compose.yaml +++ b/stacks/althing-chamber/compose.yaml @@ -1,17 +1,25 @@ -# althing-chamber + forseti + agent-runner — web UI + moderator daemon + -# Worldtree-driver agent-runner for the althing inter-agent message bus. +# althing-chamber stack — chamber + forseti + agent-runner + valkey. # -# Three services share the althing SQLite store via a single bind-mount: +# 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. # -# eventbus.bridge_from_db is load-bearing — forseti's DB commits reach -# chamber's SSE subscribers via the bridge, not via shared memory or IPC. -# Both processes read+write the same `~/.althing/althing.db` SQLite file -# at /app/data/althing.db inside the container, backed by the host's -# /opt/docker/conf/althing-chamber/data bind-mount. +# 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 @@ -26,10 +34,26 @@ # 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: @@ -81,6 +105,9 @@ services: 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