Implement ports.Summarizer in internal/adapters/summarizer. It routes through a
local Primary endpoint first and an optional BYO Fallback, owning the routing
itself (not delegating to llm.Router) so it can record AIProvider, AIModel, and
FallbackUsed on domain.Summary. Prompt asks for JSON {summary, highlights,
takeaways}; the parser tolerates thinking-model fences/reasoning and rejects an
empty summary.
The summarizer is the single egress point for content toward an AI model, so it
enforces the local-first guarantee from ai_routing.feature: with no BYO
configured (nil fallback) there is no external endpoint, so content reaches the
local stack and nowhere else. Tests assert all four scenarios via a fake client.
Model alias is config (TAPIR_SUMMARIZER_MODEL, host/name) — not hardcoded;
docs/homelab-integration.md notes it stays `confirm` and that thinking models
need an explicit max_tokens or they return empty content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
8.4 KiB
Markdown
137 lines
8.4 KiB
Markdown
# Tapir — Homelab Integration Facts
|
|
|
|
The concrete endpoints, conventions, and identifiers Tapir depends on, so an independent
|
|
session doesn't have to rediscover them. **Verify anything marked "confirm" before relying on
|
|
it** — endpoints and aliases drift, and this file is a snapshot (2026-06-02), not a live source.
|
|
|
|
## Local AI (the Primary in `llm.Router`)
|
|
|
|
- **LiteLLM gateway:** OpenAI-compatible, lives in k3s namespace `ai-stack` (verified 2026-06-02
|
|
from koala via `kubectl get svc -n ai-stack`):
|
|
- **In-cluster:** `http://litellm.ai-stack.svc.cluster.local:4000/v1/` (ClusterIP `10.43.159.89:4000`).
|
|
- **Off-cluster (host / NodePort):** `http://koala:30401/v1/` — service `litellm-nodeport` (`4000:30401`).
|
|
- NOTE: the earlier `31234` was **wrong** — `31234` is the `llama-swap` NodePort (`8080:31234`),
|
|
not LiteLLM. Use `30401` for off-cluster LiteLLM. `confirm` resolved.
|
|
- **Auth key:** treat as a secret via the SecretStore port; do not hardcode in committed code.
|
|
NOTE: the documented `sk-local-123` returned `401` on `30401/v1/models` (2026-06-02) — it is
|
|
stale or the gateway now enforces the `LITELLM_MASTER_KEY` (1Password `op://HomeLab/LITELLM_MASTER_KEY`).
|
|
Resolve the live key from the vault when wiring; `sk-local-123` is no longer valid. `confirm` partially resolved.
|
|
- **Model alias format:** `host/name`, e.g. `koala/qwen3-coder-30b`, `koala/phi4-mini`,
|
|
`iguana/devstral`, `iguana/deepseek-r1-14b`. **Not** the `ollama/` prefix form.
|
|
- **Which alias for summarization:** NOT yet decided. `confirm`. Tapir summarizes transcript
|
|
text, so a capable general/instruct model on koala or iguana is the candidate — pick during the
|
|
build and record the choice (an ADR if it's load-bearing). Do not assume a coder alias is right
|
|
for prose summarization. The summarizer adapter does **not** hardcode an alias: it is config,
|
|
env `TAPIR_SUMMARIZER_MODEL` (format `host/name`, e.g. `iguana/deepseek-r1-14b`).
|
|
- **Thinking models need an explicit `max_tokens`.** qwen3 / deepseek-r1 spend the budget on
|
|
reasoning and return **empty content** if `max_tokens` is too low (or unset). The summarizer's
|
|
parser treats an empty summary as an error for exactly this reason. When the alias resolves to a
|
|
thinking model, add a generous `max_tokens` to the copied `llm.Client` request (it currently
|
|
sends none — change Tapir's copy per ADR-004), or pick a non-thinking instruct model.
|
|
|
|
This maps directly onto the copied `llm` package: `Client` is the OpenAI-compatible caller,
|
|
`Router.Primary` points at this gateway with a chosen alias, `Router.Fallback` is the user's BYO.
|
|
|
|
## BYO AI (the Fallback in `llm.Router`)
|
|
|
|
- Per-user, opt-in: Anthropic / OpenAI / Gemini. All reachable as OpenAI-compatible or via a thin
|
|
adapter. The user's key is stored only as a secret reference (data-model `AI_CREDENTIAL`).
|
|
- Used **only** when Primary fails and the user has configured a provider (see
|
|
`docs/use-cases/ai_routing.feature`). With no BYO, work is queued for retry — content is never
|
|
sent externally.
|
|
|
|
## Brain sink (optional delivery target)
|
|
|
|
- Reached via **brain-mcp** over HTTP, calling the `brain_ingest` tool (ADR-005). Do **not** use
|
|
the filesystem `brain` package from `hyperguild/ingestion`.
|
|
- **brain-mcp base URL:** **confirm** — not pinned in this snapshot. The homelab convention is
|
|
`*-mcp.d-ma.be` ingress hostnames (e.g. `git-mcp.d-ma.be`, `brain-mcp.d-ma.be` is the likely
|
|
form), but verify before wiring.
|
|
- brain distinguishes `knowledge/` (session-derived) from `wiki/` (stable, manually promoted).
|
|
Tapir summaries are session-derived signal → `knowledge/`-style ingestion. `brain_ingest` runs
|
|
the LLM extraction pipeline; there is a raw-ingest path (`brain_ingest_raw`) and a reliable
|
|
`brain_write` fallback when the local extraction model is unavailable — relevant if ingestion
|
|
reliability becomes an issue.
|
|
|
|
## Auth / identity (Stage 1+, dormant at Stage 0)
|
|
|
|
- **Dex** (`auth.d-ma.be`) is the homelab OIDC provider. When Tapir needs real user identity
|
|
(Stage 1, Future B), it authenticates via Dex — not a new auth system (ADR-002).
|
|
- If Tapir ever exposes an MCP surface, use the **`mcp-chassis`** shared Go library
|
|
(`gitea.d-ma.be/mathias/mcp-chassis`) for the Dex-JWT validator + Bearer middleware + RFC 9728
|
|
metadata, rather than hand-rolling it.
|
|
|
|
## Secrets (OAuth tokens + BYO keys)
|
|
|
|
- Convention: **ESO + 1Password** (the homelab's External Secrets Operator against a 1Password
|
|
vault). Tables store only an opaque `*_secret_ref`; the secret material lives in the vault and
|
|
is surfaced to the workload via ESO.
|
|
- **Exact ref format / vault item naming for Tapir:** **confirm / decide during build.** Follow
|
|
the pattern existing services use (e.g. how `gitea-mcp` references `GITEA_MCP_DEFAULT_TOKEN`)
|
|
rather than inventing a new scheme.
|
|
|
|
## Hosts (for reference)
|
|
|
|
- **koala** — Arch, RTX 5070, k3s single-node, Gitea, GPU inference. Where Tapir most likely runs
|
|
and where the local models live.
|
|
- **iguana** — Mac Studio M2 Ultra, ollama + mlx-whisper, also serves models via the gateway.
|
|
- **piguard** — Pi, NPM perimeter (public HTTPS edge). Note: the May-2026 architecture review has
|
|
LiteLLM relocating from piguard into k3s/ai-stack on koala — **confirm current location** if
|
|
latency or endpoint matters.
|
|
|
|
## Network egress the build and runtime assume
|
|
|
|
If the koala act_runner (or a dev environment) has locked-down egress, these must be reachable
|
|
or the build/run fails. Split into build-time and runtime because they're different lists and
|
|
fail at different stages.
|
|
|
|
### Build-time (CI `check`/`build`, and local `go`/`task` work)
|
|
|
|
- `proxy.golang.org`, `sum.golang.org` — Go module proxy + checksum DB. The default `GOPROXY`.
|
|
If a homelab GOPROXY (e.g. Athens) exists, point `GOPROXY` at it and this pair is not needed
|
|
directly — **confirm** whether one is run.
|
|
- `go.dev`, `dl.google.com` — Go toolchain download (the CI `setup-go` step / `go-version-file`).
|
|
Not needed if the runner already has the matching Go installed.
|
|
- `raw.githubusercontent.com` — the CI installs golangci-lint via its install script from here
|
|
(`.gitea/workflows/ci.yml`).
|
|
- `github.com`, `codeload.github.com` — any module not served by the proxy (incl. golangci-lint
|
|
and, later, `golang.org/x/oauth2` and its deps) is fetched from GitHub.
|
|
- `objects.githubusercontent.com` — GitHub release asset downloads (some installers redirect here).
|
|
|
|
A configured `GOPROXY` collapses most of the module-fetch entries into one host. Recording the
|
|
homelab's actual `GOPROXY`/`GONOSUMCHECK` policy here once known would remove the **confirm**.
|
|
|
|
### Runtime (the running service)
|
|
|
|
- `koala:4000` (or the NodePort / post-relocation address) — LiteLLM gateway, the Primary.
|
|
- brain-mcp host (`*-mcp.d-ma.be`, **confirm**) — only when the brain sink is enabled.
|
|
- `www.googleapis.com` / YouTube Data API + `oauth2.googleapis.com` (token exchange/refresh) —
|
|
the YouTube provider adapter.
|
|
- `api.vimeo.com` — the Vimeo provider adapter.
|
|
- BYO-AI endpoints, per configured provider and **only when a user opts in**:
|
|
`api.anthropic.com`, `api.openai.com`, `generativelanguage.googleapis.com`.
|
|
- 1Password / ESO reach for secret resolution follows the homelab's existing ESO egress, not
|
|
Tapir-specific — no new rule expected.
|
|
|
|
Note: a user with **no** BYO configured means the service never needs the third-party AI hosts
|
|
(the local-first guarantee, `docs/use-cases/ai_routing.feature`). Don't allow them by default;
|
|
allow per-provider when a user connects one.
|
|
|
|
> The claude.ai chat sandbox (where planning sessions run code) has its **own** separate egress
|
|
> allowlist — unrelated to koala. It blocked `go.dev` during scaffolding, which is why the
|
|
> scaffold was reviewed but not compile-verified in-chat; verification belongs on koala via
|
|
> `task check` regardless.
|
|
|
|
## Deployment / GitOps (when Tapir reaches deploy)
|
|
|
|
- The homelab is **Flux GitOps**: manifests in `mathias/infra` under `k3s/`, Flux watches `main`.
|
|
- Tapir's k3s manifests will live in `infra/k3s/apps/tapir/` (by convention) — not in this repo.
|
|
This repo is the application; `infra` is the deployment source of truth.
|
|
- Multi-tenancy primitives (NetworkPolicy per namespace, Kyverno, postgres role-per-tenant,
|
|
`tenant=` label) exist in the architecture review (SC7/P6) and apply at Stage 1 — not Stage 0.
|
|
|
|
---
|
|
|
|
_Snapshot date 2026-06-02. Items marked **confirm** were not verified to a pinned source at
|
|
snapshot time — check brain or the live cluster before depending on them._
|