Skip to main content

Architecture

Layer8 Proxy is a credential broker with a hardened egress path. This page covers the broker/ACL/egress model and the kind taxonomy that discriminates what agents can see and what they can reach.

The broker / ACL / egress model

┌────────────┐ ┌────────────┐ ┌──────────┐
agent ──HTTPS─▶│ locksmith │──CONNECT─▶│ pipelock │──HTTPS──▶│ upstream │
bearer/mtls │ (broker) │ proxied │ (egress) │ │ provider │
└─────┬──────┘ └────────────┘ └──────────┘

│ infra call

┌────────────┐
│ lf-scan │ (operator-only middleware)
└────────────┘
  1. Broker (locksmith). Terminates the agent connection, authenticates the per-agent bearer (or mTLS cert), checks the ACL, resolves the target registration, strips agent-sent auth headers, and injects the real upstream credential. One audit row per request.
  2. ACL. Each agent has a flat allowlist (or denylist) of registration names. The allowlist references names directly; locksmith resolves the kind at request time. A call to a name outside the allowlist returns 403 tool_not_allowed and an audit row.
  3. Egress. Registrations marked egress: proxied route through pipelock via HTTP CONNECT; pipelock enforces a network-layer allowlist + blocklist + DLP. egress: direct registrations (typically LAN-local models) bypass the CONNECT proxy.

The kind taxonomy (ADR-0004)

Pre-Phase-E, every registration was a homogeneous "tool" with one discovery endpoint (/tools) and one admin family. Three forces drove a split:

  1. Agent reasoning differs by type. An LLM provider is token-priced, prompt-shaped, and rate-limit-sensitive; a web-search tool is response-shaped and latency-sensitive. Mixing them conflated two mental models during agent-side selection.
  2. Operator reasoning differs for infra middleware. lf-scan is called by locksmith itself, never by an agent. Stuffing it into /tools meant operators had to ACL-deny it from every agent — inverting the trust default.
  3. Authless tools needed a distinct shape. DuckDuckGo, Wikipedia, and similar public endpoints have no credential to inject. Absence of an auth: block used to mean both "no injection" and "operator forgot the key" — two indistinguishable cases.

The decision: a kind discriminator with three locked values.

  • model — LLM, embedding, reranker, audio, image, multimodal. Agents discover via GET /models (ACL-filtered, kind=model only).
  • tool — anything that is not a model: web search, code repos, document fetch, sandboxes, KV stores. Agents discover via GET /tools (ACL-filtered, kind=tool only).
  • infra — operator-only middleware that locksmith itself calls (today: lf-scan). Not discoverable by agents — there is no GET /infra endpoint.

Names live in a single global namespace; a name belongs to exactly one kind. Cross-kind reuse is rejected at register-time with 409 / name_in_use.

Authless registrations use an explicit auth: none. Implicit absence of an auth: block on kind=tool / kind=model is rejected at register-time with 400 / auth_required — closing the "forgot-the-API-key" footgun while preserving operator intent for deliberately public endpoints.

Where state lives

Per-host proxy-operator state lives in a site repo:

  • site.cfg — site identity + pinned Layer8 Proxy version.
  • .env — provider keys, LF_SCAN_INTERNAL_TOKEN, backup creds (gitignored).
  • pipelock/pipelock.yaml — allowlist + blocklist + DLP policy.
  • locksmith/base.yaml — locksmith base config (listen, egress_proxy, audit).
  • locksmith/secrets/ — sealed-cred files (systemd-creds on Linux; openssl-AES fallback on macOS dev). Plaintext is never committed.
  • agents.yaml — per-agent ACL manifest; each entry maps to a locksmith agent register --name X --allowlist ... call.

The seed catalog (baked into the locksmith image) supplies 16 default registrations (anthropic, openai, openrouter, ai-gateway, ollama, lmstudio, tavily, github, duckduckgo, wikipedia, lf-scan + 5 OAuth providers). The site tools/ directory holds only site-local overrides for registrations not in the seed catalog.

See also