Captures the May 18-20 session arc: - Skaldsong CD shipped on ana-docker:8300 via registry-pull pattern (differs from althing/asset-engine which build-on-host); three first-deploy footguns surfaced + canonical-patched. - Worldtree CD disk-hygiene PR #184 (vh/Worldtree) — watermark gate, eager post-deploy SHA prune, set -e hardening in SSH blocks. Approved by worldtree-dev, pending merge. - mead-hall Bifrost v0.3 wire-layer + data-flow end-to-end smoke green (task #32 closed) — resolves the "stalled mid-Worldtree" state in the prior snapshot. - Volva systemd install — three-stage debug (PATH, ReadWritePaths, handle pinning); impersonation footgun caught via PID/journal cross-reference. - Per-project user keys minted against personal Worldtree (:8081) for skaldsong, althing, mead-hall + skaldsong-prod. Personal becomes multi-consumer dev iteration instance; demo stays for isolation. File now at 434 lines (over 300 soft cap); next /snapshot run will surface archival candidates.
23 KiB
Persistent memory — eshpfi-management
Last updated: 2026-05-20
Repo purpose
Reference workspace for PFI infrastructure: server inventory, canonical
Docker Compose stacks, ops playbooks, and conventions. Authoritative
copies of compose files live on the servers under
/opt/docker/compose/<stack>/; this repo mirrors them for version
control, editing, planning, and CI-driven deploys.
Tools and conventions
Sister repos (separate gitea repos, deployed by playbooks here):
| Repo | Role | CI status |
|---|---|---|
vh/task-board |
MCP + web dashboard for assistant task state (port 7878) | push-to-main → CI deploys (2026-04-29) |
vh/vor |
Inquisitor UI sidecar (port 7879) | push-to-main → CI deploys (2026-04-29) |
vh/nevermore |
Twice-daily LLM-curated briefing (port 8181, replaces news-digest) | push-to-main → CI deploys (2026-04-30) |
vh/asset-engine |
Internal control plane over inference services (port 8200, LAN-direct) | push-to-main → CI deploys (2026-05-12) |
vh/althing |
Inter-agent message bus (chamber UI port 7881, forseti + agent-runner daemons, valkey IPC) | push-to-main → CI deploys (2026-05-14) |
vh/mead-hall |
Bifrost tool-provider sidecar (port 5173 on dev VM 10.100.10.50) | push-to-main → CI deploys (2026-05-16) |
vh/skaldsong |
Wizard + reader surface (port 8300, ana-docker, registry-pull pattern) | push-to-main → CI deploys (2026-05-19) |
vh/worldtree |
Conversation API (corviduo-dev demo :8080 / personal :8081 / pinned :8082) — Heimdall auth, Bifrost integration | push-to-main → CI deploys |
vh/volva |
Codex peer agent on althing bus (single-turn oracle, systemd daemon on nh3-dev) | manual install via deploy/volva.service (2026-05-18) |
-
Two-layer backups — Backrest orchestrates restic for file+DB (5 fleet repos, daily 01:00 PDT); PBS-ANA primary + PBS-NH3 DR mirror for VM images. ana-nas is the SPOF for postgres + PBS-ANA datastore + cross-site restic targets — see
docs/runbooks/disaster-recovery.mdfor the blast-radius matrix. -
pull-hf-repo.yamlis the canonical "get a HuggingFace model/dataset onto ana-ml2's shared cache at/tank/aimodels/huggingface/" playbook. Supports--var repo_type=model|dataset|space. Replaces ad-hochuggingface_hub.snapshot_downloadpatterns. -
Worldtree admin auth — per-instance. Each Worldtree deployment (demo :8080, personal :8081, pinned :8082) has its own Heimdall registry and its own bootstrap admin key. Infra-ops's stored long-lived admin key (
key_id 61419c92) atana-docker:/opt/docker/conf/.secrets/worldtree-infra-ops-adminauths against demo only. For personal-instance admin ops, fetch the bootstrap admin per-op viadocker exec worldtree-personal-worldtree-api-1 printenv WORLDTREE_BOOTSTRAP_ADMIN_KEYon corviduo-dev. Used forPOST /admin/keys, admin diagnostics (/admin/sessions/<id>/{bifrost,tools}, etc.). -
Per-project user keys against personal Worldtree (issued 2026-05-19):
skaldsong:79744637(nh3-dev iteration),skaldsong:7c1dbbbe(ana-docker prod),althing:50d85460,mead-hall:a360822d. Sameuser_id=skaldsongacross both skaldsong keys → shared Heimdall agent slot; differentkey_id→ independently rotatable. Pattern: mint via/admin/keys, drop value to/tmp/wt-personal-<name>.keymode 600, dev collects + shreds (DO NOT cat to chat transcript). -
Skaldsong CD pattern (registry-pull). Differs from althing / asset-engine which build-on-host. vh/skaldsong's CI builds and pushes
gitea.phasefinal.com/vh/skaldsong:<sha>+:latest;playbooks/deploy-skaldsong.yamlon ana-docker pulls + recreates. SHA-pin only (no:latesthealth-gated advance yet). Prereq: host needsdocker login gitea.phasefinal.comonce (read:package PAT) — not currently in the workflow. -
docker-as-root pattern (for ops that have no admin API, e.g.
SqliteUserStore.set_bifrost_credentials): on hosts where the SSH user is in thedockergroup but lacks passwordless sudo, rundocker run --rm -v <target-dir>:/wt -v /var/run/docker.sock:/var/run/docker.sock docker:cli sh -c "..."to edit deploy-owned files without sudo. Documented with security warning inservers/corviduo-dev/README.md. docker-group membership is effectively root via bind-mount; treat as a sudo-equivalent grant.
Current state / in-flight
As of 2026-05-20:
-
Worldtree CD disk-hygiene PR #184 approved, ready for merge. Three commits on
infra/disk-watermark-gate-cdin vh/Worldtree: watermark gate (pre-pull recovery), eager post-deploy SHA prune (post-:latest-advance),set -ehardening in SSH blocks. Pending worldtree-dev's merge button. Follow-up extension todeploy-personal.yml+deploy-pinned.yml+ Beszel disk-usage metric all queued, not bundled. -
Skaldsong live on ana-docker:8300 (registry-pull pattern), pointed at personal Worldtree (:8081). Three first-deploy footguns surfaced + canonical-patched: SPA path mismatch (
/app/web/dist→/app/spa), CORS env shape (pydantic-settings expects JSON array), verify-step race vs healthcheckstart_period(grep^Upnothealthy). nh3-dev hand-launched skaldsong on :8080 also still running for dev iteration — same user_id=skaldsong, different key_id, both authorized. -
Worldtree personal (:8081) is the iteration-target instance. skaldsong + mead-hall + althing-chamber all rotated to personal via the per-project user keys minted 2026-05-19. Demo (:8080) stays as the canonical prod surface; pinned (:8082) intentionally lags. Personal's
BIFROST_CLIENT_ALLOWED_HOSTSnow mirrors demo's:10.100.10.50:5173,10.100.10.50:8080,10.250.50.70:8300. -
Disclosed-keys hygiene queue — these warrant rotation at convenience (mostly in-bus disclosure during this session):
/tmp/wt-personal-skaldsong-prod.keyon nh3-dev — operator should shred after verify (file mode 600, lkraven-owned)- mead-hall's prior Worldtree bearer
(
wt_live_80e1570620ef2aba998dc63954cce3a6) — now superseded by personal-instance keya360822dper the 2026-05-19 rotation - Worldtree provider keys
Z_AI_API_KEY+ZAI_API_KEY— from the 2026-05-12 corviduo-dev outage - chamber config.yaml's
forseti.api_key+agent_runner.api_key— superseded by personal-instance key50d854602026-05-19 - Gitea runner registration token (
a1135753...) — rotate via Gitea admin UI's runner-token reset
-
Still open from prior sessions: rotate
MINIFLUX_PASSWORD(leaked twice); clean up legacynews-digestdetritus on ana-docker (/opt/docker/compose/news-digest/,/opt/docker/data/news-digest/, imagelocal/news-digest:v5); watch nh3-nas/volume1(was 65%; recheck retention or expand before ~80%); thedocker push 60s client-side ceilingmystery remains uninstrumented.
Recent decisions
-
[2026-05-19]Worldtree CD disk-hygiene strategy: watermark gate (env-tunable threshold + window, fail-loud on still-low post-prune)- eager post-deploy prune (only after
:latestadvance succeeds, usesdocker image prune -a --filter "until=24h"which respects in-use semantic — protects pinned + personal images automatically). Combined: demo VM holds ~24h of deploy history instead of unbounded accumulation. Shipped in vh/Worldtree PR #184 (306cd61+613dac2+bd91df5).
- eager post-deploy prune (only after
-
[2026-05-19]Skaldsong CD shape: shape (1) of three operator options — container + Gitea registry + pull-restart, matching Worldtree's pattern. Target host ana-docker (NOT nh3-dev where skaldsong-dev runs for iteration). SHA-pin only for now; health-gated:latestadvance is a follow-up once/healthexercises Worldtree- Kokoro reachability.
-
[2026-05-19]Skaldsong prod (ana-docker) switched from demo Worldtree (:8080) to personal (:8081). Sameuser_id=skaldsongas the nh3-dev hand-launch key — shared Heimdall agent slot (skaldsong:wizard-v2), differentkey_ids for independent rotation. Demo Worldtree stays for isolation; personal becomes the multi-consumer dev iteration instance. -
[2026-05-19]mead-hall Bifrost v0.3 end-to-end smoke green. Closed task #32 (althing thread01KRV1M2KW6N6HBEXGTH72QXCA). Wire layer (handshake + binding + dispatch) + data-flow (per-dispatch JWT claims →ctx.session_idpopulated → real session-scoped data) + agent-loop (LLM reads + quotes back) all proven. Resolves the "stalled mid-Worldtree" state from the 2026-05-17 snapshot. -
[2026-05-18]Volva systemd install complete after three-stage debug. Final unit at/etc/systemd/system/volva.serviceruns asUser=lkravenwithProtectHome=read-only+ReadWritePaths=/home/lkraven/.althing /home/lkraven/.codexcarve-outs for state writes.VOLVA_ALTHING_CLI=/home/lkraven/ .local/bin/althing-cli+ALTHING_HANDLE=volvaboth pinned in env.sh. -
[2026-05-17]Phase 3.1 cross-process streaming uses Valkey 8 alpine as a sibling compose service instacks/althing-chamber/, redis-protocol pub/sub for high-volumemsg_delta/msg_thinking/msg_start/msg_completeevent kinds. DB bridge keepsmsg_curated+floor_grant(structured / canonical). Two-channel architecture, no overlap. chamber + agent-runnerdepends_on: valkey: service_healthy. -
[2026-05-17]Worldtree admin workflow shift (per vh): infra-ops gets its own permanent admin-tier key (61419c92, stored atana-docker:/opt/docker/conf/.secrets/worldtree-infra-ops-admin). Future admin ops route through this key, not the bootstrap admin via docker-as-root. -
[2026-05-17]Worldtree env-var addition checklist: anytime introducingos.environ.get("FOO")in worldtree code, update BOTH.env.exampleANDcompose.yaml's&worldtree-envanchor in the same PR. Same Z_AI_API_KEY-shape footgun bitBIFROST_CLIENT_ALLOWED_HOSTS(#170) until worldtree-dev added the passthrough line in08f02b2. -
[2026-05-16]althing-chamber Phase 2: addedalthing-agent-runneras third compose service (worldtree-driver agent dispatcher). All three althing services use the same image;command:selects entrypoint. Safe to enable preemptively (sleeps when no driver=worldtree handles declared). -
[2026-05-14]althing-chamber stack scaffolded: chamber + forseti. Internal LAN-only at port 7881 (chamber default 7878 collides with task-board). Two-service compose, shared SQLite bind-mount, build-on-host pattern via vh/althing's gitea-workflow. Forseti is the canonical dev for this stack (galdrabok is on a different project). -
[2026-05-13]vllm-qwen3→vllmstack rename. Addedvllm-rewardservice (Skywork-Reward-V2-Llama-3.1-8B-AWQ classifier). Three vLLM services share GPU 1 (embed 0.20, rerank 0.20, reward 0.30 utilization; 30% headroom). All use--runner pooling; classification drives via model'sarchitectures: [LlamaForSequenceClassification]in config.json, NOT--task classify(deprecated in vLLM 0.19.1). -
[2026-05-13]pull-hf-repo.yamlis the canonical HF-fetch playbook on ana-ml2. Supports--var repo_type=model|dataset|space. Replaces ad-hochuggingface_hub.snapshot_downloadcalls. -
[2026-05-13]Selene-1-Mini-Llama-3.1-8B added to llama-swap as judge model. mradermacheri1-Q6_Kimatrix quant (~6.5GB). AtlaAI reward/eval model — temp 0.01, ctx 32K, q8_0 KV cache. New JUDGE / EVAL MODELS section instacks/llama-swap/conf/config.yaml. -
[2026-05-13]/tend-docs first pass deletions:stacks/infinity/removed (retired by vllm). Archiveddocs/asset-engine/design-brief.md→docs/archive/asset-engine/with archival header. Fixedpfi-pveVM list to fullqm listenumeration. Dropped stale weak-password section frompfi-postgres(rotation done 2026-04-23). -
[2026-05-12]corviduo-dev (Worldtree-team dev VM, 10.250.50.152, CT 106 on pfi-pve) added toservers/inventory. Treat like SF client hosts: PFI hosts + provides emergency-ops backstop; Worldtree team owns OS config + deploys + backup decisions. -
[2026-05-12]Worldtree:latesttag drift bug — fixed by health-gated:latestadvance in vh/worldtree's deploy workflow (architect commit8ef3801): only tag:latestAFTER the new container's/healthprobe passes. Build-on-host stacks here don't have this problem because the playbook always builds the SHA-tagged image from agit reset --hard <ref>checkout. -
[2026-05-12]asset-engine stack scaffolded LAN-direct athttp://10.250.50.70:8200. Initially included Traefik labels for public hostname; user pulled them out (internal tool, no public TLS surface needed). Pattern: internal tools default LAN-direct; Traefik wiring only when external/TLS required. -
[2026-05-12]asset-engine catalog gainslifecycle: { stack, vram_gb, gpu_device_id }per irv-ml1 service for the orchestrator feature. SSH keypair scaffolded atana-docker:/opt/docker/conf/asset-engine/ssh/for asset-engine container → irv-ml1 orchestration via dedicated ed25519 key.
Tried and abandoned
-
[2026-05-19]Naivedocker rmi worldtree:<old-sha> --forcefor CD SHA cleanup — would untag pinned/personal worldtree images since all three deployments share corviduo-dev. Usedocker image prune -a --filter "until=Xh"instead — respects in-use semantic (Docker won't remove an image referenced by any container on the host), so pinned/personal protected automatically. -
[2026-05-19]Skaldsong CD first attempt:docker pullstep failed with 401 unauthorized. ana-docker had nodocker loginforgitea.phasefinal.com. My playbook prereq note ("docker login has been done at least once") was an unverified assumption. One-time manual login persists in~/.docker/config.json; architectural fix (workflow-sidessh ana-docker 'docker login ...'step usingREGISTRY_USER/REGISTRY_TOKENsecrets) flagged as v2. -
[2026-05-19]SKALDSONG_HOST_CORS_ORIGINS=http://10.250.50.70:8300as a bare URL — pydantic-settings parses complex env vars viajson.loads(); first-boot crashloop withSettingsError: error parsing value for field "cors_origins". Must be JSON array literal:SKALDSONG_HOST_CORS_ORIGINS=["http://..."]. -
[2026-05-19]SKALDSONG_HOST_STATIC_ASSETS_PATH=/app/web/distin compose — mismatched Dockerfile reality. The Dockerfile COPYs SvelteKit build output flat into/app/spa(not/app/spa/dist). Lifted the path from skaldsong-dev's CD-ask message ("/app/web/dist") rather than verifying against the actual Dockerfile they shipped. Lesson: when encoding container-internal paths in compose, verify against the Dockerfile, not the design-doc. -
[2026-05-19]Playbook verify stepdocker ps | grep healthyracing the container'sstart_period(30s in compose's healthcheck). Verify ran 0.09s aftercompose up -d --force-recreate— well before docker's healthcheck could flip the status from(health: starting)to(healthy). False-negative; container was operationally up (the earlier/healthpoll verify already confirmed). Fix: grep^Upnothealthy. /health-200 IS the liveness check; docker's(healthy)is just a delayed echo. -
[2026-05-19]Volva daemon impersonating volva-dev (the human) for hours of debugging because cwd-based handle resolution.session_handles.jsonmaps/home/lkraven/development/volva→volva-dev; the daemon's systemd unit setsWorkingDirectory=/home/lkraven/development/volvaand env.sh didn't pinALTHING_HANDLE, so the daemon polled volva-dev's inbox and replied AS volva-dev. Catch: cross-reference journal PIDs againstmessage_receivedevents to verify which voice generated which reply. Fix: pinALTHING_HANDLE=volvain env.sh (worldtree-dev'se39be87made this the default in env.sh.template). Real follow-on risk: hallucinated structured feedback from impersonating-daemon can bootstrap real (and correct) downstream commits — the technical artifact survives but the conversational attribution rots. -
[2026-05-18]Volva env.sh.template$HOMEin commented examples — systemd'sEnvironmentFile=parser doesn't expand$HOME; uncommenting lands the literal$HOME/...string. Volva-dev'sf4dda73swapped to/home/<svc-user>/...placeholders. -
[2026-05-18]Initial Volva systemd unit'sProtectHome=read-onlywithoutReadWritePaths=— althing-cli's SQLite (~/.althing/ althing.db) and codex's session state (~/.codex/) both need to write. Container started but every poll failed with "db path not writable". Surgical fix:ReadWritePaths=/home/lkraven/.althing /home/lkraven/.codex(preserves the hardening intent, only carves out the specific dirs). -
[2026-05-18]Trusting that env.sh'sexport VOLVA_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"template line works under systemd —EnvironmentFile=parser aborts on the first unparseable line (command substitution), andVOLVA_ALTHING_CLIdeclared below silently never lands. Symptom:Environment=property empty, daemon error "althing-cli not found at 'althing-cli'". Fix: replace command-substitution with literal path. Volva-dev'sd436c3cdropped VOLVA_ROOT entirely upstream. -
[2026-05-17]--task classifyfor Skywork in vLLM 0.19.1 — flag was deprecated. Use--runner pooling; the model'sarchitectures: [LlamaForSequenceClassification]in config.json drives the classification head. Surfaced asvllm: error: unrecognized arguments: --task classifyin container logs. -
[2026-05-17]Trusting that.envedit alone propagates a new env var into a worldtree container —compose.yaml's&worldtree-envanchor must explicitly declare the passthrough or the value silently doesn't land. Same footgun bitZ_AI_API_KEY(2026-05-12) ANDBIFROST_CLIENT_ALLOWED_HOSTS(2026-05-17). Cost ~10 min of "why is env empty?" diagnosis each time. Worldtree-side fix invh/worldtree@08f02b2. -
[2026-05-17]--force-recreate --pull neverfrom the docker:cli sandbox without explicit-e WORLDTREE_IMAGE=<sha>re-pins the container to:latest, even when a newer SHA-tagged image is on disk. Symptom: container "recreated" but actually reverted to a stale image. Pass-e WORLDTREE_IMAGE=...:<sha>to the docker run invocation. Worldtree-dev's8ef3801health-gated:latestadvance is the long-term fix. -
[2026-05-13]Initial Voxtral default voicealloy(OpenAI-compat naming) — vLLM-Omni serving Voxtral does NOT translate aliases. Native presets are<register>_<gender>shape (neutral_female,casual_male, etc.). Always live-probe/v1/audio/voicesfor the exact wrapper-deployed preset names before setting a catalog default. Same caveat for Qwen3-TTS (wrapper exposes 15 voices: 9 Qwen presets + 6 OpenAI aliases) and Kyutai-TTS (NillPointer wrapper has NO voice-listing endpoint at all; voices are filesystem paths under thekyutai/tts-voicesHF repo). -
[2026-05-12]Defaulting asset-engine to Traefik-routed (asset-engine.phasefinal.comwithanaprodcert resolver) on first scaffold — user pulled it back to LAN-direct. Internal tools default LAN-direct; only add Traefik when an external/TLS surface is actually needed. -
[2026-05-12]Routing althing thread replies throughgaldrabokwhen the actual dev handle isforseti— bus rejectedto=forsetiinitially because thread participants list was[galdrabok, infra]. Solved by starting a new thread withforsetias the direct recipient. Lesson: when the bus auto-resolves a sender handle that doesn't match the actual dev role, start a fresh thread rather than fighting the participant list. -
[2026-05-08]Filtering Traefik's UTC access log by Gitea-local-PDT timestamp substrings (grep "2026/05/08 15:1[2-7]") returned zero matches and led to a wrong "no /v2/ traffic in 12 days" conclusion. Gitea logs in PDT, Traefik logs in UTC — same host, different timezones. Always normalize timezones (UTC) when correlating logs across services on the same box. Cost: ~30 min in the wrong direction. -
[2026-05-08]Validating a user-proposed Traefik/Gitea timeout bump for "Traefik is dropping connections on big docker pushes" without first verifying which component was actually in the failure path. Traefik turned out to be innocent (499 / 60012ms= client closed, Traefik never timed out), Gitea'sPER_WRITE_TIMEOUTgoverns response writes (wrong direction), and the real ceiling was a ~60s client-side timer no compose change can reach. Lesson: validate the diagnostic premise — which component is actually in the failure path? — before refining the proposed fix. -
[2026-05-08]Bumping GiteaPER_WRITE_TIMEOUT/PER_WRITE_PER_KB_TIMEOUTto addressunexpected EOFon/v2/.../blobs/uploads/PATCH — wrong direction. Both govern response writes, not request body reads.unexpected EOFfrom Go's HTTP server means the client closed mid-body-upload; not a knob Gitea exposes server-side. -
[2026-04-30]task-board workflow withcontainer: image: debian:bookworm-slim— fails:actions/checkout@v4needsnodeat runtime, slim image lacks it. Switched tonode:20-bookworm-slim(has node + apt) or runner-label default. (Pattern revisited 2026-05-17 for skaldsong-dev: container override needsnodejsapt-installed unless it IS the default.) -
[2026-04-30]Dropping thecontainer:directive before runner re-registration with docker-schema labels — runner silently falls back to host mode (jobs run inside the alpineact_runnercontainer itself, no apt). The:hostsuffix in startup logs (labels updated to: [pfi-fleet:host ana-docker:host]) is the giveaway. Fix: register withpfi-fleet:docker://<image>schema labels. -
[2026-04-30]Updating runner labels by editing.envand bouncing — doesn't take. The.runnerregistration cache pins labels at first registration; env-var updates are read each start but the stored token + UUID are tied to the original label set on the gitea side. Fix: stop runner, delete.runner, generate new admin registration token, redeploy. -
[2026-04-30]git reset --hard origin/<sha>indeploy-task-board.yaml(and the in-repo nevermore playbook before fix) — invalid syntax:origin/prefix only works for branch refs. SHAs needgit reset --hard <sha>directly. Resolved withgit rev-parse --verify --quiet "origin/{{ ref }}^{commit}"first, then bare"{{ ref }}^{commit}"fallback. -
[2026-04-30]AssumingDEPLOY_SSH_KEYwas at user scope after task-board wiring — it was actually only repo-scope onvh/task-board. vor's first CI run failed with empty SSH key (printf '%s\n' "" > ~/.ssh/id_ed25519). Fix: copy secret to user scope atgitea.phasefinal.com/user/settings/actions/secrets. -
[2026-04-30]grep -vE "^(#|$)"to inspect.envfor sanity — leaked the fullMINIFLUX_PASSWORDline into the transcript. Then a follow-up redaction attempt withsed -E "s/=(.{4}).*$/=\1<redacted>/"still leaked the first 4 chars. Lesson: when probing secret-bearing files, use field-by-field SELECTIVE inspection (grep -E "^(KEY1|KEY2)=") rather than negative filters; for any password line,grep -c(existence) ortest -n "$(...)"(non-empty), nevercator value-printing.