feat: route agents to user-owned local LLMs (closes #24) - #49
Open
daniel-sarosi-gwc wants to merge 1 commit into
Open
feat: route agents to user-owned local LLMs (closes #24)#49daniel-sarosi-gwc wants to merge 1 commit into
daniel-sarosi-gwc wants to merge 1 commit into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:OPENCODE_MODEL=local/<model-id>localprovider block inopencode.json; the cloud provider block is omitted entirelyOPENCODE_API_KEY,HTTPS_PROXY,HTTP_PROXYfrom the agent's env (defensive)opencode serveto127.0.0.1:4096(was0.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).[Retry] [Abort], plus[Use cloud fallback]whenlocal_llm_fallback=cloudand the cloud API key is configured at the infra level); wait up to 10 min for a reply; cloud switch re-emitsopencode.jsonwith the cloud provider and restartsopencode servejust-in-time.Config
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):
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 checkandblack --checkboth clean.terraform validatepasses for both root anduser-poolmodules.Rebased onto current main
Rebased onto main; resolved two conflicts (one in
handler.pywhere main added STT env exports that needed to coexist with my conditionalOPENCODE_API_KEYexport; one intests/test_handler.pyfor new import). Both resolved cleanly.Follow-up issues filed
feat: lock down EC2 egress via nftables when local LLM is configured— full network containment; usesapi.github.com/metafor GitHub IP ranges.feat: Tailscale auth-key bootstrap for blitzlog agents— EC2-side enrollment with ephemeral + 4h-TTL auth keys.feat: ship a user-side helper for always-on tunnel startup— brew formula / plist templates for the three transports.Out of scope
Closes #24.