5704fbb08f
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.
111 lines
5.6 KiB
Markdown
111 lines
5.6 KiB
Markdown
# althing-chamber
|
|
|
|
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`)
|
|
**Upstream repo:** [vh/althing](https://gitea.phasefinal.com/vh/althing)
|
|
**Image:** `althing-chamber:local` — built on the host from the git repo
|
|
by the deploy playbook. Not pulled from a registry.
|
|
|
|
## Services in this stack
|
|
|
|
| Container | Role | Port | Healthcheck |
|
|
|---|---|---|---|
|
|
| `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` |
|
|
|
|
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
|
|
`floor_grants` and sleeps when the query returns empty. Multi-instance
|
|
safe via the atomic `UPDATE … WHERE consumed_at IS NULL` claim primitive.
|
|
|
|
## Deploy
|
|
|
|
Two paths — automated (preferred) and manual (escape hatch / first-time).
|
|
|
|
### Automated (Gitea Actions, push-to-main)
|
|
|
|
The vh/althing repo ships `.gitea/workflows/deploy.yaml`. Every push to
|
|
main + manual `workflow_dispatch` triggers the elway playbook below
|
|
pinned to the triggering commit SHA. A reference copy of the workflow
|
|
lives next to this README at
|
|
[`gitea-workflow-deploy.yaml.example`](gitea-workflow-deploy.yaml.example);
|
|
the canonical source is in the vh/althing repo. The example header
|
|
lists the two repo secrets required (`DEPLOY_SSH_KEY`,
|
|
`MGMT_REPO_TOKEN`).
|
|
|
|
### Manual (elway from a workstation)
|
|
|
|
```bash
|
|
# First deploy (or update to latest main)
|
|
scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml
|
|
|
|
# Pin to a specific ref (tag, branch, or commit SHA)
|
|
scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml --var ref=v0.1.0
|
|
```
|
|
|
|
## Path layout (on ana-docker)
|
|
|
|
| Host path | Container path | Purpose | Restic? |
|
|
|---|---|---|---|
|
|
| `/opt/docker/build/althing-chamber/` | — | git checkout used as docker build context | excluded |
|
|
| `/opt/docker/compose/althing-chamber/` | — | compose.yaml + .env | included (via `/opt/docker`) |
|
|
| `/opt/docker/conf/althing-chamber/data/` | `/app/data` | SQLite + state for BOTH services | **included** |
|
|
|
|
## Network model
|
|
|
|
Internal tooling, LAN-only. Chamber's container port 8000 is published
|
|
on the host at `0.0.0.0:7881` (configurable via `ALTHING_BIND` /
|
|
`ALTHING_PORT`); access is direct via `http://10.250.50.70:7881`. No
|
|
Traefik, no TLS terminator, no public hostname.
|
|
|
|
Forseti has no port — it's a daemon. The two services communicate only
|
|
via the shared SQLite file under the `${ALTHING_DATA_DIR}` bind-mount;
|
|
no docker network coupling beyond compose's default bridge.
|
|
|
|
## Env-var contract
|
|
|
|
| Var | In env? | In `~/.althing/config.yaml`? | Notes |
|
|
|---|---|---|---|
|
|
| `ALTHING_ROOT` | ✓ (set in compose) | n/a | Overrides the default `~/.althing/` dir. Container sets `/app/data`. |
|
|
| `ALTHING_DB` | ✓ (set in compose) | n/a | Explicit SQLite path. Defaults to `${ALTHING_ROOT}/althing.db`. |
|
|
| `ALTHING_BIND` | ✓ (deploy-time) | also accepted | Bind address for the chamber HTTP server. Container always sets 0.0.0.0 internally. |
|
|
| `ALTHING_PORT` | ✓ (deploy-time) | also accepted | Chamber listens here internally (always 8000 inside the container). |
|
|
|
|
Env-var support for `ALTHING_BIND` / `ALTHING_PORT` was added on the
|
|
galdrabok side as part of the container-deploy cycle (env > config.yaml
|
|
> defaults precedence).
|
|
|
|
## First-deploy sequence (when galdrabok's Dockerfile lands)
|
|
|
|
1. Generate a deploy keypair on ana-docker (private stays on host;
|
|
pubkey lands in `~lkraven/.ssh/authorized_keys`).
|
|
2. Wire two secrets in `vh/althing` Actions settings:
|
|
- `DEPLOY_SSH_KEY` — the private key from step 1.
|
|
- `MGMT_REPO_TOKEN` — Gitea PAT with `read:repository` on this repo,
|
|
used by the workflow to clone the management repo for the playbook.
|
|
3. Push to vh/althing's main branch (or trigger `workflow_dispatch`);
|
|
the Actions runner clones both repos, configures SSH, runs
|
|
`scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml --var ref=$SHA`.
|
|
4. Playbook: clones into `/opt/docker/build/althing-chamber`, builds
|
|
the image, uploads compose + .env (one-time seed), brings both
|
|
services up, polls `/health` until 200.
|