Cory Policht
← Back to the Devlog
DEVLOG · 2026-06-22 .. 2026-06-27 · BUILD

Repo Intelligence Service closes in the gateway.

The proxy breaks apart its single-file monolith into modular routing, translation, and forwarders, brings worker-advisor escalation online, and fixes silent drops in Anthropic-to-OpenAI stream translation.

WINDOW
Start2026-06-22
End2026-06-27
Kindbuild
Projectsllm-proxy-gateway
Delivered
  • llm-proxy-gateway
    Worker-advisor escalation via dynamic call_advisor tool
    Injects a call_advisor tool into local worker requests so local models can signal uncertainty. When invoked, the proxy intercepts the call and reroutes the prompt to frontier advisor chains.
    llm-proxy · #17llm-proxy · c1d0179
  • llm-proxy-gateway
    Monolith decomposed into focused modules and machine profiles
    Splits the 2400-line index.js into discrete modules for gateway routing, payload translation, forwarding, and profile-based backend isolation.
    llm-proxy · #18llm-proxy · 02b2790
  • llm-proxy-gateway
    Jinja chat template system message hoisting
    Mid-array role:system messages from Claude Code are hoisted into top-level system prompts before reaching local LM Studio backends whose templates mandate system-first ordering.
    llm-proxy · #21llm-proxy · 9b7c7c3
  • llm-proxy-gateway
    Fast discovery timeouts and streaming fallback for silent drops
    Caps model discovery fan-out with MODEL_DISCOVERY_TIMEOUT_MS, detects dropped non-JSON frames in Anthropic-to-OpenAI translation, and provides re-streaming fallback for non-streaming upstreams.
    llm-proxy · #23llm-proxy · #33llm-proxy · 0decee8
  • llm-proxy-gateway
    Enforced container vulnerability scanning and SARIF uploads
    Gates pull requests on critical vulnerability scans via Trivy, uploads SARIF artifacts, and aligns base container dependencies to internal repos.
    llm-proxy · #29llm-proxy · #34

The repo intelligence service inside llm-proxy reached done this week, but the substantive change was architectural: the proxy grew out of its prototype shoes and turned into a modular gateway.

Up to this point, the gateway lived as an unwieldy single file of roughly 2,400 lines. That worked when all it did was proxy HTTP requests to a local llama.cpp instance. As soon as multiple backend targets, model translation layers, and fallback schemes entered the picture, every small fix risked touching shared state. PR #18 cleanly cut the monolith into discrete modules: gateway.js for HTTP lifecycle, routing.js for rules and fallback chains, translation.js for mapping between Anthropic and OpenAI schemas, and forward.js for SSE streaming and timeouts. Alongside that cut, machine-profile routing landed, isolating local machine capabilities from shared cluster endpoints.

With clean seams in place, routing grew actual judgment through worker-advisor escalation (#17). Instead of blindly sending every prompt to expensive hosted models or failing when a small local model ran out of depth, the gateway injects a synthetic call_advisor tool into local worker requests. When a local model encounters a query it cannot resolve with confidence, it invokes the tool; the proxy intercepts the call and forwards the original payload to a configured advisor chain without requiring client-side intervention.

The rest of the window tackled edge cases in local inference interoperability. Claude Code frequently interleaves role: "system" messages mid-array during tool execution loops. Frontier APIs accept this without issue, but local model runtimes running Jinja chat templates choke unless the system prompt is strictly first. PR #21 restored message sanitization specifically for local backends, hoisting mid-stream system prompts into the root turn while leaving native cloud payloads untouched.

A trickier failure mode cropped up during Ask Sage streaming (#33). When an upstream OpenAI-compatible endpoint returned an error page over an HTTP 200 SSE connection, the proxy’s parser swallowed the non-JSON text in a bare catch block, returning an empty-but-successful response to the client. The fix exposes unparseable chunks as actionable error events and adds a buffering fallback that re-streams responses over Anthropic-compatible SSE when an upstream target lacks native streaming support.

Lessons learned

  • Never swallow upstream SSE parse failures into successful empty streams. Catching a JSON parse error on an SSE chunk and doing an early return turns an upstream backend crash into a successful empty turn, completely blinding the client and caller telemetry to the failure.
  • Local inference templates enforce strict role ordering that frontier APIs tolerate. Anthropic’s managed endpoint permits late or repeated system turns; local Jinja chat templates fail catastrophically if role: "system" appears anywhere except turn zero. Target-specific sanitization belongs in the gateway layer.
  • Bound backend discovery with explicit timeouts. If /v1/models queries three local machines in parallel, a single sleeping GPU workstation will block the entire discovery fan-out indefinitely unless bounded by a short discovery timeout.