Files
nexa/docs/12-optimization-opportunities.md
T
Claude 0b470b3e12 Resolve Q6/Q7/Q8/Q9/Q11; pin actual list names; add host tuning suggestions
Verified from screenshots:

- Q6/Q7 → Lists & calendars discovered by NAME, not by ID. Names confirmed:
  "Persönlich" (personal), "DLR" (work), "Einkaufsliste" (shopping),
  "Wunschliste" (wishes). Self-healing: #nexa:config runs daily and on
  cache-miss, so user-added lists become routable without code changes.
  Routing logic in docs/07 expanded from 2-way to 4-way switch with a
  Resolver step that consults the Qdrant _config cache before fanning out;
  default route is "Persönlich" when SAIA confidence < 0.7.
- Q8 → Single Nextcloud Mail account fkrebs@nucli.de. No separate IMAP.
  The "Waiting" folder is honored as a manual user signal — items there are
  excluded from digest.
- Q9 → Pocket-ID SSO is global at the Zoraxy layer; Nexa surfaces inherit
  it. No app-level basic-auth. Q10 folded in.
- Q11 → Capacity confirmed: 22 threads (Core Ultra 7 155H), 62 GiB RAM
  (~32 GiB used, ~24 GiB of which is ZFS ARC), 1.64 TiB disk (0.35%).
  Phase-3 budget (~7 GB) is comfortable; two levers documented (raise
  LXC cap, or cap zfs_arc_max). Earlier "16 CPU" references corrected.

Propagation:
- system_prime.txt rewritten with the four context labels and the new
  list names; legacy NC_LIST_ID_* placeholders removed.
- docs/04 integration matrix uses the German names directly.
- docs/05 documents that #nexa:config also runs scheduled and on
  cache-miss, with the example output reflecting real list IDs.
- docs/09 step 6 mirrors the same flow + lists the Waiting-folder
  semantics.
- CLAUDE.md gets an authoritative block of homelab decisions so future
  agents don't re-litigate any of these.

New section in docs/12 (#17–25): Proxmox host tuning suggestions
based on the live node summary — cap ZFS ARC (frees ~16 GiB),
enable KSM (~1-3 GiB savings across 40 containers), suppress the
no-subscription banner, drop swappiness, verify scrub/SMART/fstrim
timers, NTP via AdGuard.
2026-05-04 22:08:10 +00:00

6.6 KiB
Raw Blame History

12 — Optimization Opportunities

Observations from the running infrastructure. Each item is independent — accept, defer, or reject.

For Nexa directly

  1. Reuse, don't redeploy. The earlier DEPLOYMENT.md would have spun a second Memos / n8n / Qdrant. The current homelab already runs all three. The new 09-deployment treats these as pre-existing — keeps the config minimal and avoids port collisions.
  2. Use LiteLLM virtual keys per logical caller. Today there's one SAIA key. Issuing one key per workflow (nexa-router, nexa-embed, nexa-digest) lets you set different per-key rate/cost limits and disable a single workflow without rotating everything.
  3. Use n8n's credential objects, never inline secrets. The current workflows under nexa-core/n8n-workflows/phase-1/*.json should be reviewed — if any header Authorization is hardcoded, replace with credential references before importing.
  4. Centralise system alerts on a single ntfy topic (nexa.system). Backrest, Proxmox notifications, n8n failure-webhook and the Octoprint Exited state all go to that topic; one Memos system memo aggregates them.
  5. Defer graph DB until Phase 3.4. Qdrant alone covers ~80% of the assistant's daily value. The graph DB is justified once you actually need dependency analysis or critical-path queries.
  6. Auto-export n8n workflows. nexa-core/scripts/backup_workflows.sh already exists. Schedule it inside the n8n container (cron) and let it git commit && git push — this is the cheapest disaster recovery.

For the wider homelab (out of scope but worth noting)

  1. AI gateway naming. ai.nuclide.systems currently proxies LobeHub (a chat UI on :3210), while the LiteLLM API lives on :4000. For Nexa, point n8n directly at LiteLLM (http://192.168.1.40:4000 over the docker net — no public TLS hop needed) to save latency and isolate from UI restarts.
  2. MCP servers consolidation. Dozzle shows crawl4ai-mcp, markitdown-mcp, papersearch-mcp running individually. They're all MCP servers — Nexa Phase-3 could pull from these via LiteLLM's MCP support to enrich the embedding pipeline (e.g. fetch + markitdown a Karakeep link before embedding).
  3. Backup the n8n SQLite file — Backrest covers /home/node/.n8n if added; today the only "backup" is the workflow JSON which omits credentials and execution history.
  4. Pocket-ID SSO in front of n8n would let you remove n8n basic-auth and unify session management across the whole stack. One-time setup, large UX win.
  5. Vaultwarden as the secret store for Nexa secrets (SAIA_API_KEY, MEMOS_API_KEY, …) — read at bootstrap via the Bitwarden CLI from inside the docker host. Removes the need for a .env on disk.
  6. AdGuard as DNS-based control plane. Since AdGuard is the resolver for the LAN, you can rewrite *.nuclide.systems to 192.168.1.4 (Zoraxy) internally and avoid a hairpin via the WAN — already the case if AdGuard rewrite rules are set, worth verifying.
  7. Disk usage on LXC 104 is 47.7 % (Proxmox). Monitor; n8n execution logs and Dozzle history are the usual culprits. Setting EXECUTIONS_DATA_PRUNE=true and EXECUTIONS_DATA_MAX_AGE=168 (7 days) on n8n keeps it bounded.
  8. Obsidian plugin embeddings collide with Nexa's. The vault already runs Obsidian Copilot (.copilot, .copilot-index) and Smart Connections / Smart Composer (.smart-env, ~13 MB). They each embed the same notes into their own vector stores — three indexes for the same content. Nexa's value is the cross-source index (memos + mail + obsidian + RDF graph), so it has to embed independently, but the plugins could be retired once Nexa's RAG is satisfying. Track separately, decide later.
  9. Real-time Obsidian sync via notify_push. Phase 3.1 polls WebDAV every 15 min (Q4 resolution). Once that works, swap to Nextcloud's notify_push app for sub-second propagation. One-line workflow change in n8n.
  10. assets/ is 186 MB of binaries in the Obsidian vault — worth a glance to confirm it's mostly images (Phase-3.2 visual queue) rather than something that should live in Nextcloud Files proper.

Proxmox host (NUC 14 Pro) tuning

Observed from the node summary: 22 threads, 62 GiB RAM (32 GiB used, ~24 GiB of which is ZFS ARC), 1.64 TiB disk (0.35% used), load avg <2.0, IO delay 0.04%, kernel 6.17.13-4-pve, PVE 9.1.9, EFI. Suggestions in priority order:

  1. Cap ZFS ARC. Default is 50% of RAM (~31 GiB); current actual ~24 GiB. For a node that runs services rather than a pure storage box, capping at 812 GiB frees ~1216 GiB for guests without measurable IO impact (disk is 1.64 TiB and 0.35% used — there's nothing hot to cache):
    echo 'options zfs zfs_arc_max=8589934592' > /etc/modprobe.d/zfs.conf   # 8 GiB
    update-initramfs -u
    
    Reboot or echo 8589934592 > /sys/module/zfs/parameters/zfs_arc_max to apply live.
  2. Enable KSM (Kernel Same-page Merging). With ~40 docker containers + several LXCs, KSM typically frees 13 GiB by deduplicating identical memory pages. Currently KSM sharing: 0 B in the summary. PVE has ksmtuned available — systemctl enable --now ksmtuned.
  3. Suppress the pve-no-subscription repository warning — either accept it (it's a homelab) and apply the pve-no-subscription-warning polyfill, or move to the enterprise repo. Pure cosmetic, but the orange banner in the UI is noise.
  4. Swap is 31 GiB on a 62 GiB box with ZFS root — almost certainly oversized. Drop vm.swappiness to 10 (sysctl -w vm.swappiness=10 + persist) so swap is only used under genuine pressure, and consider shrinking the swap volume if disk-layout permits.
  5. Verify scheduled ZFS scrub is enabled. PVE ships zfs-scrub-monthly@.timersystemctl list-timers | grep zfs to confirm. Cheap insurance on a 1.6 TiB pool.
  6. SMART monitoring on the NVMe. smartctl -a /dev/nvme0 should be regularly polled; PVE's notification target can ntfy on degradation. Combine with the existing nexa.system ntfy topic (optimization #4) so disk-health alerts land in the same Memos system feed as everything else.
  7. NTP source via AdGuard. AdGuard already resolves DNS for the LAN; pointing the host's systemd-timesyncd at pool.ntp.org resolved through AdGuard avoids any external dependency for time. One-line change in /etc/systemd/timesyncd.conf.
  8. fstrim.timer enabled for the SSD pool — verify with systemctl status fstrim.timer. Default-on in modern PVE, but quick to confirm.
  9. Watchdog config is irrelevant for a single-node setup (HA is the use case), so leave the default. Mentioned only so future agents don't add it speculatively.