homelabby/.planning/phases/06-lab-advisor/06-02-SUMMARY.md
Mikkel Georgsen ae34bc73fe docs(06-lab-advisor-02): complete advisor backend plan
- 06-02-SUMMARY.md: SSE streaming chat, InventoryContextBuilder, 3 endpoints
2026-04-10 07:37:15 +00:00

7.1 KiB

phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
06-lab-advisor 02 api
go
sse
openrouter
claude
advisor
streaming
postgresql
chi
phase provides
06-lab-advisor-01 store.Store, CreateConversation, AddMessage, GetConversation, ListConversations, RunMigrations
phase provides
04-usb-events SSE pattern (text/event-stream headers, Flush loop)
POST /api/advisor/chat — SSE streaming chat backed by Claude Opus via OpenRouter
GET /api/advisor/conversations — list past conversations from PostgreSQL
GET /api/advisor/conversations/{id} — full message thread
InventoryContextBuilder — 60s-cached compact NetBox inventory summary for system prompt
Tier3 config field in AIConfig for OpenRouter lab-advisor model
07-frontend-advisor
future-ai-tiers
added patterns
SSE streaming via go-openai CreateChatCompletionStream + http.Flusher.Flush per token
Nil-guard in router: advisorHandler==nil returns 503 instead of panic
Stale cache on NetBox error: return cached context rather than failing the chat request
Body size guard: http.MaxBytesReader(64KB) + message truncation at 8000 chars
created modified
internal/advisor/context.go
internal/advisor/handler.go
internal/ai/types.go
internal/config/config.go
internal/api/router.go
cmd/hwlab/main.go
Tier3 config added to AIConfig struct (not a separate config) for consistency with Tier1/Tier2
advisorHandler nil-guard in router returns 503 — graceful degradation when DB not configured
Stale InventoryContext served on NetBox error rather than failing chat — availability over freshness
HWLAB_DATABASE_URL absence is non-fatal; advisor endpoints return 503 rather than crashing server
SSE pattern: Content-Type: text/event-stream + Cache-Control: no-cache + X-Accel-Buffering: no + Flush per write
Per-request openai.Client construction from Tier3 config allows model override without restart
ADV-01
ADV-02
ADV-03
ADV-05
25min 2026-04-10

Phase 06 Plan 02: Lab Advisor Backend Summary

SSE streaming advisor chat backed by Claude Opus via OpenRouter, with 60s-cached NetBox inventory context injected into every system prompt and full PostgreSQL persistence of conversations and messages.

Performance

  • Duration: 25 min
  • Started: 2026-04-10T07:11:00Z
  • Completed: 2026-04-10T07:36:19Z
  • Tasks: 2
  • Files modified: 6 (2 created, 4 modified)

Accomplishments

  • InventoryContextBuilder: fetches up to 200 devices from NetBox, builds a compact text summary (total count, category breakdown, recent 20 items), caches 60s under sync.Mutex, falls back to stale cache on NetBox error
  • AdvisorHandler: StreamChat streams tokens from OpenRouter via go-openai CreateChatCompletionStream; each token delivered as data: {"conversation_id":"...","token":"..."} SSE event; body limited to 64KB, message truncated to 8000 chars; API key never echoed
  • Full persistence: CreateConversation + AddMessage(user) before stream; AddMessage(assistant) after stream completes
  • Three endpoints registered under /api/advisor/ with nil-guard for DB-unavailable graceful degradation
  • Tier3 TierConfig added to AIConfig with HWLAB_AI_TIER3_* env bindings and OpenRouter/claude-opus-4 defaults

Task Commits

  1. Task 1: InventoryContextBuilder with 60s cache - 7b02e67 (feat)
  2. Task 2: AdvisorHandler SSE streaming + router wiring - 0190e85 (feat)

Files Created/Modified

  • internal/advisor/context.go - InventoryContextBuilder with 60s cache and compact NetBox summary
  • internal/advisor/handler.go - AdvisorHandler: StreamChat, GetConversations, GetConversation
  • internal/ai/types.go - Added Tier3 TierConfig field to AIConfig
  • internal/config/config.go - Tier3 defaults (OpenRouter, claude-opus-4, 120s timeout) + env bindings
  • internal/api/router.go - /api/advisor routes with nil-guard; import advisor package
  • cmd/hwlab/main.go - Store init from HWLAB_DATABASE_URL, RunMigrations, AdvisorHandler wired

Decisions Made

  • Tier3 config follows the same TierConfig struct as Tier1/Tier2 — no special advisor config type needed
  • Per-request openai.Client construction (not cached) allows model field in ChatRequest to override model without server restart, satisfying ADV-05
  • advisorHandler is nil when HWLAB_DATABASE_URL is absent; router nil-guard returns 503 on all three endpoints rather than panicking
  • Stale inventory context returned on NetBox error (availability over freshness — homelab context)

Deviations from Plan

Auto-fixed Issues

1. [Rule 2 - Missing Critical] Added Tier3 to AIConfig struct

  • Found during: Task 2 (AdvisorHandler implementation)
  • Issue: Plan referenced aiCfg.Tier3 but AIConfig only had Tier1 and Tier2 fields — handler would not compile
  • Fix: Added Tier3 TierConfig to AIConfig in internal/ai/types.go; added defaults and env bindings in config.go
  • Files modified: internal/ai/types.go, internal/config/config.go
  • Verification: go build ./... passes; Tier3 config loads from HWLAB_AI_TIER3_* env vars
  • Committed in: 0190e85 (Task 2 commit)

2. [Rule 2 - Missing Critical] Nil-guard in router for missing DB

  • Found during: Task 2 (router wiring)
  • Issue: advisorHandler can be nil when HWLAB_DATABASE_URL is not set; registering nil method handlers would panic at request time
  • Fix: Added nil check in router; returns 503 with descriptive message when advisor is disabled
  • Files modified: internal/api/router.go
  • Verification: go build ./... passes; server starts without DB configured
  • Committed in: 0190e85 (Task 2 commit)

Total deviations: 2 auto-fixed (2 missing critical) Impact on plan: Both fixes required for correctness and safe server startup. No scope creep.

Issues Encountered

  • .gitignore entry hwlab matched the directory cmd/hwlab/git add cmd/hwlab/main.go failed with "ignored file" warning. The file was already staged from a prior add, so commit proceeded normally. The gitignore entry targets the compiled binary, not the source directory; no fix needed.

Known Stubs

None — all three endpoints are fully wired. StreamChat requires HWLAB_AI_TIER3_API_KEY to be set with a valid OpenRouter key for actual AI responses; without it, OpenRouter returns 401 which is surfaced as a 502 to the client. This is expected behavior for a homelab tool.

Threat Flags

No new threat surface beyond what the plan's threat model covers. All mitigations from T-06-02-01 through T-06-02-03 are implemented (parameterized store calls, API key isolation, 64KB/8000-char input guards).

Next Phase Readiness

  • All three /api/advisor/* endpoints ready for frontend integration
  • Set HWLAB_DATABASE_URL + HWLAB_AI_TIER3_API_KEY to enable advisor
  • Frontend advisor chat UI (Phase 07 or equivalent) can connect to POST /api/advisor/chat and consume the SSE token stream

Phase: 06-lab-advisor Completed: 2026-04-10