Skip to content

feat: route agents to user-owned local LLMs (closes #24) - #49

Open
daniel-sarosi-gwc wants to merge 1 commit into
mainfrom
feat/issue-24-local-llm-routing
Open

feat: route agents to user-owned local LLMs (closes #24)#49
daniel-sarosi-gwc wants to merge 1 commit into
mainfrom
feat/issue-24-local-llm-routing

Conversation

@daniel-sarosi-gwc

Copy link
Copy Markdown
Contributor

Implements #24 with the locked-in plan from the design discussion.

What it does

Adds per-user local LLM routing. When a user has configured local_llm_* in their user-pool, the bootstrap:

  • Switches OPENCODE_MODEL=local/<model-id>
  • Writes a local provider block in opencode.json; the cloud provider block is omitted entirely
  • Strips OPENCODE_API_KEY, HTTPS_PROXY, HTTP_PROXY from the agent's env (defensive)
  • Binds opencode serve to 127.0.0.1:4096 (was 0.0.0.0)

Endpoint guard (Lambda-side)

Hard-rejects any endpoint that resolves to a public IP (no opt-in).
Block-until-opt-in for RFC1918, ULA, and Tailscale CGNAT (100.64.0.0/10).
Always-blocks loopback and link-local (covers IMDS 169.254.169.254).

Preflight probe + per-mode resilience

10×30s probe of <endpoint>/health (fallback /v1/models).

  • Autonomous: abort on exhaustion. No silent fallback to cloud.
  • Assisted: send Telegram inline keyboard ([Retry] [Abort], plus [Use cloud fallback] when local_llm_fallback=cloud and the cloud API key is configured at the infra level); wait up to 10 min for a reply; cloud switch re-emits opencode.json with the cloud provider and restarts opencode serve just-in-time.

Config

local_llm_endpoint                       = "http://100.x.y.z:11434"
local_llm_model                          = "qwen2.5-coder:32b"
local_llm_api_key                        = ""
local_llm_endpoint_allow_private_cidrs   = true
local_llm_fallback                       = "closed"  # or "cloud"

SSM params land at /blitzlog/users/<login>/local-llm/{endpoint,model,api-key} (all SecureString) plus {allow-private-cidrs,fallback} (String).

Transports (documented in README)

Always-on tunnels from the local LLM host (not per-run, since EC2 is ephemeral):

  • Tailscale (recommended default)
  • WireGuard
  • AWS Client VPN

Public-domain transports (Cloudflare Tunnel, public ssh -R) are deliberately not supported — endpoint safety guard rejects public IPs unconditionally.

Tests

260 tests passing (54 new from this PR, the rest from main's whisper-STT work).
ruff check and black --check both clean. terraform validate passes for both root and user-pool modules.

Rebased onto current main

Rebased onto main; resolved two conflicts (one in handler.py where main added STT env exports that needed to coexist with my conditional OPENCODE_API_KEY export; one in tests/test_handler.py for new import). Both resolved cleanly.

Follow-up issues filed

Out of scope

Closes #24.

Adds per-user local LLM routing so a user can point their blitzlog
agents at a local model they control (Ollama, LM Studio, llama.cpp
on a Mac mini at home, etc.) instead of the shared cloud provider.

Config is per-user under /blitzlog/users/<login>/local-llm/:
  - endpoint (SecureString; hard-rejects public IPs at the Lambda)
  - model (SecureString)
  - api-key (SecureString, empty for no-auth endpoints)
  - allow-private-cidrs (bool opt-in for RFC1918/ULA/Tailscale CGNAT)
  - fallback (closed|cloud; assisted-mode Telegram prompt only)

When configured, the bootstrap:
  - Switches OPENCODE_MODEL=local/<model-id>
  - Writes a 'local' provider block in opencode.json; omits the
    minimax-coding-plan block entirely (no cloud credentials in env)
  - Defensive unset of OPENCODE_API_KEY/HTTPS_PROXY/HTTP_PROXY
  - Binds opencode serve to 127.0.0.1:4096

Preflight probes GET <endpoint>/health (fallback /v1/models) ten
times at 30s. Autonomous mode aborts on exhaustion. Assisted mode
sends a Telegram inline keyboard (Retry/Abort, plus Use cloud when
local_llm_fallback=cloud) and waits up to 10 minutes; cloud switch
re-emits opencode.json with the cloud provider and restarts the
opencode serve just-in-time, reading OPENCODE_API_KEY from SSM.

Always-on transports (Tailscale / WireGuard / AWS Client VPN)
documented in README; per-run tunnel setup is not supported.

nftables egress lockdown and Tailscale auth-key bootstrap are
filed as follow-up issues.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: route agents to external local LLMs (e.g. Mac mini over VPN/WebSocket)

1 participant