rockandAdmin Bot ef5e736673
CI / CI (push) Successful in 3m20s
feat(cag): CAG integration — memory adapter, warmup semaphore, cache-aware routing (#47)
## Summary

Three issues in one PR. Implements Cache-Augmented Generation (CAG) support in the api-gw.

---

### #44 — Memory ServiceAdapter: standing-context resource

- Code-registers `memory` adapter with `standing-context` resource (`POST /memory/standing-context`)
- Fixes dispatcher bug: method-level `Auth.Capability` override now respected (was always using verb-derived capability)
- Auth: `memory:read` required via method-level override; `memory:write` for ingest
- `MEMORY_UPSTREAM_URL` env configures upstream

### #45 — Continuous batching: warmup deprioritisation + per-backend semaphore

- In-memory bounded queue per backend: `VLLM_MAX_QUEUE_DEPTH` slots (default 20)
- Concurrency limiter: `VLLM_MAX_NUM_SEQS` slots (default 4)
- `X-Poimen-Warmup: true` → sleep `WARMUP_DELAY_MS` (default 500ms) before queue entry, strip header, add `X-Priority: low`
- Queue full → 503 immediately, no goroutine pile-up
- Metrics: `warmup_requests_delayed_total`, `queue_full_requests_total` at `/metrics`

### #46 — Cache-aware routing: rendezvous hash + prefix cache hit logging

- `addresses: [...]` YAML field for multi-backend models (backward-compat with `address`)
- `X-Poimen-Cache-Key` header → rendezvous-hash backend selection (same key = same GPU)
- No cache key → round-robin
- Failed backend excluded 30s; next-highest-score fallback
- `vllm-num-cached-tokens` response header captured → `vllm_prefix_cache_hit_tokens_total` counter + structured log

---

## Verification

```
go build ./...    ✅
go vet ./...      ✅
go test -race ./... ✅ (all packages, 0 races)
```

## Reviewer verdicts

- reviewer-sre: SIGN-OFF
- reviewer-quality: SIGN-OFF

## Non-blocking findings (🟡🔵 only)

- `getOrCreateTransport` not goroutine-safe by design; pre-population invariant documented
- `time.Sleep(warmupDelay)` holds goroutine; context-cancel alternative left as TODO
- OTel trace_id not propagated into CAG log events (existing gap)

Closes #44 Closes #45 Closes #46

---------

Co-authored-by: Admin Bot <[email protected]>
Reviewed-on: #47
2026-09-27 11:16:40 +00:00

homelab-frontend

Production API gateway for the homelab cluster. Single entry point (api.riotpiao.com) for all services: LLM inference, workflows, queues, memory, and cluster operations.

Status: Live in production. Replaced Kong OSS entirely.


  • API Reference: See API.md — how to call every service
  • Base URL: https://api.riotpiao.com
  • Source: ssh://git.riotpiao.com:2222/rock/homelab-frontend.git

Architecture

┌─────────────┬──────────────┬────────────┐
│  Browser    │     SDK      │    CLI     │
└──────┬──────┴──────┬───────┴────┬───────┘
       │             │            │
       └─────────────┼────────────┘
                     │
                  HTTPS/TLS
                     │
       ┌─────────────┼────────────┐
       │   Cloudflare Edge        │
       │   (DDoS, caching)        │
       └──────────┬──────────────┘
                  │
            ingress-nginx
         (SSL termination)
                  │
    ┌─────────────────────────────┐
    │   homelab-frontend Gateway  │
    │   (routing, auth, limits)    │
    └──────┬───────────────────────┘
           │
    ┌──────┴───────────────────────────────────┐
    │                                           │
 /v1/*           X-Service header routing       /
(LLM)    (workflow, sqs, s3, iam, memory)    /
    │             │                           │
    ▼             ▼                           ▼
llm-serving  temporal:7233   kmsvc/Kafka, MinIO,
(vLLM, Ollama) (gRPC)        Authentik, poimen-memory
(TEI)

Design principles:

  • ✅ Single hostname, unified X-Service + X-Resource header routing
  • ✅ HTTP REST gateway → gRPC Temporal bridge (via X-Service: workflow)
  • ✅ Bearer token auth via Authentik (JWT + RBAC)
  • ✅ Streaming unbuffered (SSE, WebSocket, HTTP/2 multiplexing)
  • ✅ Per-route timeouts & rate limits
  • ✅ No cluster credentials held by gateway

Services & Capabilities

Service Method Upstream Status
LLM Chat POST /v1/chat/completions llm-serving (vLLM) ✅ Live
Embeddings POST /v1/embeddings llm-serving (TEI) ✅ Live
Reranking POST /v1/rerank llm-serving (TEI) ✅ Live
Workflows X-Service: workflow + X-Resource: {action} Temporal gRPC (7233) ✅ Live (START, DESCRIBE, SIGNAL, QUERY, etc)
Queues X-Service: sqs + X-Resource: {action} kmsvc/Kafka ✅ Live
Memory X-Service: memory + X-Resource: {action} poimen-memory ✅ Live
IAM X-Service: iam + X-Resource: {action} Authentik API ✅ Live
S3 X-Service: s3 + X-Resource: {action} MinIO ✅ Live

How to Use

1. Get a token

Human (OIDC device code):

core auth login
export TOKEN=$(cat ~/.cache/talos/authentik_id_token)

Service account (client credentials):

core mwinit login --username sa-name --password secret
export TOKEN=$(cat ~/.talos/.riotpiao-auth)

2. Call any service

Chat:

curl -X POST https://api.riotpiao.com/v1/chat/completions \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "model": "reasoning",
    "messages": [{"role": "user", "content": "What is 2+2?"}]
  }'

Workflow (via X-Service header):

curl -X POST https://api.riotpiao.com/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Service: workflow" \
  -H "X-Resource: start" \
  -d '{
    "namespace": "default",
    "workflow_id": "my-workflow",
    "workflow_type": "MyWorkflow",
    "task_queue": "default"
  }'

Memory:

curl -X GET https://api.riotpiao.com/ \
  -H "Authorization: Bearer $TOKEN" \
  -H 'X-Service: memory' \
  -H 'X-Resource: query' \
  -G --data-urlencode 'query=explain machine learning'

Full examples: See API.md


Available Models

LLM (Chat & Reasoning)

  • reasoning — DeepSeek-R1-Distill-Qwen-32B (8 concurrent slots)
  • ornith:35b — Ollama 35B
  • qwen2.5:3b-instruct — Qwen 2.5 3B

Embeddings

  • nomic-ai/nomic-embed-text-v2-moe — Fast, multilingual

Reranking

  • BAAI/bge-reranker-base — Document relevance scoring

Authentication

All endpoints (except /healthz, /readyz) require:

Authorization: Bearer <jwt-token>

Tokens validated via Authentik JWKS (runtime fetched, cached, auto-rotated).

Capabilities (RBAC):

  • llm:inference — /v1/* chat/embeddings/rerank
  • workflow:execute — /workflow operations
  • memory:read / memory:write — Memory operations
  • sqs:access — Queue operations
  • s3:access — S3 operations
  • iam:admin — User/group management

Error Handling

All errors return RFC 9457 application/problem+json:

{
  "type": "https://api.example.com/problems/unknown-model",
  "title": "Unknown Model",
  "status": 400,
  "detail": "Model 'gpt-4' is not available",
  "valid_models": ["reasoning", "ornith:35b", ...]
}

Common status codes:

  • 200 OK
  • 400 Bad Request (validation, unknown model)
  • 401 Unauthorized (missing/invalid token)
  • 403 Forbidden (insufficient capability)
  • 404 Not Found (workflow, resource)
  • 429 Too Many Requests (rate limit)
  • 503 Service Unavailable (backend down)

Rate Limits

Endpoint Limit Retry-After
/v1/chat/completions 8 concurrent Yes
/v1/embeddings 10 concurrent Yes
/v1/rerank 10 concurrent Yes
/workflow 100 concurrent Yes

Hitting limit returns 429 with Retry-After header.


Timeouts

Endpoint Connect Read Write
/v1/chat 10s 1h 1h
/v1/embeddings 10s 10m 10m
/v1/rerank 10s 10m 10m
/workflow 10s 30s 10s

Client disconnects cancel upstream request immediately (no orphaned slots).


Local Development

Run without cluster, no credentials needed:

# Build
go build ./cmd/gateway

# Run locally
./gateway

# Test in another terminal
curl http://localhost:8080/healthz

Points upstreams at local stubs if not connected to cluster (see internal/config).


Code Layout

cmd/gateway/               Server entrypoint
internal/
  server/                  Router, health checks
  proxy/                   Reverse proxy, streaming, timeouts
  temporal/                Workflow handler + gRPC bridge
  serviceadapter/          X-Service dispatcher (CRD-driven)
  config/                  Route + upstream configuration
  auth/                    Authentik JWT validation
  observability/           Metrics, structured logging
k8s/
  configmap.yaml           Route definitions
  rbac.yaml                Service account, roles
  deployment.yaml          Pod spec
  networkpolicy.yaml       Ingress/egress rules
testdata/                  Fixtures for offline tests

Deployment

Deployed to Kubernetes via ArgoCD:

# Check deployment
kubectl -n api get deployment homelab-frontend

# View logs
kubectl -n api logs -l app=homelab-frontend -f

# Restart
kubectl -n api rollout restart deployment/homelab-frontend

Configuration mounted as ConfigMap (k8s/configmap.yaml).


Health Checks

# Liveness (always succeeds)
curl https://api.riotpiao.com/healthz

# Readiness (waits for config + JWKS)
curl https://api.riotpiao.com/readyz

# List available models
curl https://api.riotpiao.com/v1/models

Support

Issues: Check pod logs

kubectl -n api logs deployment/homelab-frontend

Debug config:

kubectl -n api get configmap homelab-frontend-config -o yaml

Restart pod:

kubectl -n api rollout restart deployment/homelab-frontend

Test endpoint directly:

kubectl -n api port-forward svc/homelab-frontend 8080:8080
curl http://localhost:8080/healthz

See Also

  • API.md — Complete API reference with examples
  • internal/ — Source code (handlers, routing, auth)
  • k8s/ — Kubernetes manifests
S
Description
No description provided
Readme
1.3 MiB
Languages
Go 96.1%
Shell 3.6%
Dockerfile 0.3%