althing-chamber: add valkey for Phase 3.1 cross-process streaming

Phase 3.1 closes the cross-process gap the Phase 3 smoke surfaced —
streaming events (msg_start/thinking/delta/complete/curated) flow
from agent-runner → chamber via valkey pub/sub rather than the
SQLite bridge (too high-volume + ephemeral for the DB).

New service: `althing-valkey` (stock `valkey/valkey:8-alpine`).
Internal-only — no exposed port, no volume. chamber + agent-runner
reach via docker DNS at `valkey:6379` on the compose default
network. healthcheck via `valkey-cli ping` (5s interval). chamber
+ agent-runner gain `depends_on: valkey: service_healthy` so the
bridge is up before either side starts publishing or subscribing.

Forseti unchanged — never publishes Phase 3 events.

Operational properties (per forseti's deployment notes):
  - Mixed-state safe at every step. Missing valkey.url config key
    → chamber + runner stay on v3.0 / Phase 2 equivalent paths.
  - Backward path is single config-key delete + restart.
  - streaming_enabled: true (set on agent-runner 2026-05-17) is
    unaffected by this change.

README's services table + playbook header + verify section all
extended to reflect the four-service shape. Forseti's contract
at vh/althing:docs/contracts/phase3_1_valkey_bridge.contract.md
carries the wire-protocol spec.
This commit is contained in:
2026-05-17 08:36:46 -07:00
parent 59899e4e0f
commit 5704fbb08f
3 changed files with 61 additions and 23 deletions
+10 -4
View File
@@ -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"
+16 -11
View File
@@ -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
+35 -8
View File
@@ -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