Files
ai-resume/reference/stack.md
Sam Rolfe df864cc29e deploy: health gate and shape assertion follow the LAN publish address
Once the port is published on 192.168.20.13 the host no longer listens on
127.0.0.1:8096, so wait_healthy(), do_status() and the rollback path would all
get connection-refused and every deploy would roll back. They now probe
$PUBLISH_IP (RESUME_PUBLISH_IP, default 192.168.20.13). assert_shape() asserts
the published host IP is that address instead of loopback; the mem_limit ==
memswap_limit checks are unchanged. check_port stays address-agnostic on
`sport = :8096`.

Records the amendment in reference/stack.md (ports table + the bind rule) and
docs/phase-3-deploy.md (§3.2 bullet, flow diagram, DoD line), citing Sam's
decision of 2026-10-26, interaction 54aeb1a2. AGENTS.md is Sam's to change and
is untouched.
2026-10-26 22:40:22 +11:00

4.3 KiB
Raw Permalink Blame History

Stack, hosts, ports, budgets

Hosts (measured 2026-10-17)

Host Name Role RAM total / free Swap used CPUs
.13 nixos-desktop Runtime — app, Qdrant, Prefect worker 15 GiB / 2.3 GiB 8.1 GiB 12
.35 sam-Ubuntu1 Public edge — Caddy terminates 443; also Gitea, Immich, Nextcloud, Penpot, Wiki.js 14 GiB / 4.1 GiB 4.0 GiB 2
.27 sam-4screen-desktop Build / dev — sources, Obsidian vault, agent work — — —

.13 is memory-bound, not CPU-bound. The budget is real: exceeding it destabilises services Sam relies on.

Ports

Service Host Port State
Caddy .35 443 (+ LAN 8000) existing — Sam applies any change
Go app .13 8096 new — verify free (8095 = offsite.lab); published on 192.168.20.13:8096, never 0.0.0.0
Qdrant REST .13 6333 new, 127.0.0.1 only
Qdrant gRPC .13 6334 new, 127.0.0.1 only
Prefect server .13 existing reuse — do not deploy a second
OmniRoute .13 existing reuse — the only model gateway
Postgres .13 5432 reuse if needed
Whisper / Piper .13 existing reuse (Phase 7 voice only)
LangGraph service .13 8096+1 or existing Phase 5 — see note below

Known occupied: 3000, 3020, 5000, 5002, 5432, 8000, 8001, 8080, 8082, 8090, 8091, 8092, 8095, 8230, 8231, 8500, 8501, 8788–8792, 3081–3084, 1883.

Memory budget

Component Cap
Go app 120 m
Qdrant 400 m
Prefect worker 150 m
Guardrail (if local) 300 m
Total ceiling 1.0 GiB / 4 containers

Qdrant memory, measured (2026-10-26, v1.20.0, 400 m cap, 1024 dims)

Chunks Settled RSS Peak during ingest
500 128 MiB 149 MiB
5,000 138 MiB 305 MiB
20,000 151 MiB 292 MiB

Within the cap at all three sizes, but only with the memory-frugal collection settings in corpus/qdrant.py: HNSW graph and payloads on disk (hnsw_config.on_disk, on_disk_payload), m=8, ef_construct=64, memmap_threshold=5000, indexing_threshold=20000. With Qdrant's defaults the container is OOM-killed between 5,000 and 20,000 chunks. The pipeline is the only code that creates the collection, so those settings cannot drift.

Ceiling: 20,000 chunks at 1024 dimensions. Beyond that, set RESUME_QDRANT_VECTORS_ON_DISK=1, reduce the dimension, or raise the cap — in that order. Detail: docs/phase-4-notes.md §7.

Every service sets mem_limit and memswap_limit to the same value so it cannot consume host swap. Bind services to 127.0.0.1; Caddy on .35 is the only public listener.

One documented amendment (Go app, port 8096): the app publishes on the LAN address 192.168.20.13:8096, not 127.0.0.1. Caddy runs on .35 and proxies to 192.168.20.13:8096, so a loopback-only publish is unreachable from the edge and the public URL returns 502. This is the same way 8095 (offsite), 8092 (photo-filter), 3909 (garage-ui) and 4200 (prefect) are published on .13. The port is LAN-only — never 0.0.0.0 — and Caddy on .35 is still the only public listener, so AGENTS.md §4.2 holds in substance. Decision: Sam, 2026-10-26, interaction 54aeb1a2. deploy/deploy.sh follows the same address (RESUME_PUBLISH_IP, default 192.168.20.13) in its health gate and shape assertion.

Paths

Item Path
Repo working copy /home/sam/paseo/projects/resume_ai_system_paperclip_build
Release root /home/sam/deployment/resume/
Live symlink /home/sam/deployment/resume/current
Secrets ~/.config/environment.d/10-secrets.conf
Planning reference /home/sam/paseo/projects/resume_ai_system/plan/ (read-only)

Public entry

resume.lab.audasmedia.com.au — wildcard DNS already resolves to the public edge on .35. No DNS work is required. Caddy block is specified in Phase 3 §3.3.

Open items

Item Owner
Embedding provider (Gemini free tier vs OpenRouter vs local) Sam — Phase 4 shipped provider-agnostic (corpus/embeddings.py); the omniroute adapter is ready, local-hashing is a placeholder, so the decision is still Sam's before Phase 5 answers questions
Repo public/private after launch Sam — decide after Phase 3
Phone number on the contact page Sam