localgate
Turn any local LLM into a managed API — real API keys, token accounting, and RAG memory that makes a small model remember far more than its context window holds.
The built-in dashboard at /dashboard/ — token spend, latency, and per-model
usage across every key. Shown with sample data.
Why
Ollama, LM Studio and LocalAI solve model serving. They deliberately don't solve anything around it:
- No API key management. No per-user keys, no revocation, no usage tracking.
- No memory past the context window. Your 8K model forgets everything beyond 8K tokens.
- No token accounting. You guess at what you've spent.
- No database story. You wire up Postgres yourself.
localgate is the management layer. It sits between your app and your inference server and adds all four — without touching how you serve models.
Installation
localgate is on PyPI. Pick whichever of these you already have — you don't need all three:
uv tool install localgate # no Python/pip setup needed if you have uv
# or
pipx install localgate # isolated install, doesn't need a venv
# or
pip install localgate # into your current environment/venv
All three put a localgate command on your PATH. If pip or pipx say "command not
found": macOS doesn't ship them by default — pip3 (from python3 -m ensurepip or
Homebrew's python) and pipx (brew install pipx) both need to be installed first. If
you don't already have Python tooling set up, uv tool install is the path of least
resistance: install uv (one
command, no Python required first), then uv tool install localgate.
A container image is also published, at ghcr.io/anjalllll/localgate.
Quick start
ollama serve # your inference backend
ollama pull llama3
ollama pull nomic-embed-text # enables RAG memory
localgate init # set up config dir, generate admin key, run migrations
localgate keys create --name my-app # prints your key, once
localgate serve
Developing localgate itself, instead of just using it:
git clone https://github.com/AnjalLLL/localgate.git && cd localgate
uv sync --all-extras
uv run localgate db upgrade
uv run localgate serve
Now use it like OpenAI, because it is the OpenAI API:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="lg_9f3a...")
response = client.chat.completions.create(
model="llama3",
messages=[{"role": "user", "content": "Hello!"}],
)
Full walkthrough: Getting Started.
The memory bit
This is the part that isn't a proxy. Send an X-Session-ID and the gateway stores each
turn, embeds it, and retrieves what's relevant on later turns:
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="lg_9f3a...",
default_headers={"X-Session-ID": "conversation-1"},
)
client.chat.completions.create(
model="llama3",
messages=[{"role": "user", "content": "My name is Ana and I prefer Postgres."}],
)
# A separate request. No history sent. The model still knows.
client.chat.completions.create(
model="llama3",
messages=[{"role": "user", "content": "What database do I prefer?"}],
)
# → "You prefer Postgres."
The model answers correctly not because you sent the history, but because the gateway retrieved it. Past a threshold, older turns are folded into a rolling summary, so the context window holds the useful part of a long conversation rather than the most recent part of it. See RAG Memory.
Features
- OpenAI-compatible — works with any OpenAI SDK, LangChain, or curl. Unknown fields are forwarded to the backend, so your sampling knobs keep working.
- API key management — create, revoke, and rate-limit per key. Hashed, never stored raw.
- RAG memory — automatic chunking, embedding, retrieval, and rolling summarization.
- Token accounting — prompt/completion tokens per key, per model, over time.
- Any database — SQLite with zero config; Postgres or Neon with one env var.
- Any backend — Ollama, vLLM, llama.cpp, or any OpenAI-compatible server. Third parties can add more via an entry point, no fork required.
- Model aliasing — map
fast→phi4-miniand swap models without touching clients. - Prompt caching — opt-in; identical prompts skip inference entirely.
- Production-ready — structured JSON logs with correlation IDs, Prometheus metrics, liveness/readiness split, graceful shutdown, fail-fast config validation.
- Dashboard — keys, usage and conversations in the browser, at
/dashboard/.
Dashboard
Served at /dashboard/ — no build step, no separate deploy. It talks to the same /admin
API the CLI does, so anything it can do is equally scriptable.
Create and revoke keys, watch token spend per model, browse stored conversations with their rolling summaries, and point the gateway at a new database — with the connection tested before it is saved.
CLI
localgate init # first-time setup: config dir, admin key, migrations
localgate doctor # diagnose your installation (paths, DB, perms)
localgate serve # start the gateway
localgate health # is the backend up? is the DB migrated?
localgate backends # what adapters are installed
localgate keys create --name my-app # create a key (printed once)
localgate keys list # every key, active and revoked
localgate keys revoke <id> # revoke (history is kept)
localgate keys update <id> --rate-limit N # change a key's rate limit
localgate keys usage <id> # token usage for one key
localgate db upgrade # apply migrations
localgate db current # current schema revision
localgate db set-url <url> # test and save a new database URL
localgate login --url https://gw.example.com --api-key <key> # connect to a remote gateway
localgate whoami # show your usage on the connected gateway
localgate code # interactive coding session in the current directory
localgate code "add a health check" # one-off task, then exit
localgate code --remote # route inference through the logged-in gateway
localgate deploy --domain gw.example.com --target compose # generate Caddyfile + docker-compose
localgate deploy --domain gw.example.com --target systemd # generate Caddyfile + systemd unit
The CLI talks to the database (and, for code, the inference backend) directly, not to a
running server — because keys create has to work before you have a key, and db upgrade
has to work when the server won't start.
Shell completion: localgate --install-completion (bash/zsh/fish/PowerShell, via Typer).
localgate code
A minimal coding agent that reads and edits files in the current project, backed by whatever
model localgate is already pointed at — no separate API key needed, since it talks to the
backend directly rather than through the gateway.
localgate code # REPL — run /help once inside for the full list
localgate code "fix the off-by-one in parser.py" # one-shot
localgate code "..." --auto-approve --auto-commit # auto-create; confirm overwrites/deletes
localgate code --plan # writes are queued and reviewed as a batch
-
Every mutation is shown as a colored diff. Manual mode confirms all mutations;
--auto-approveskips confirmation only for new files, while overwrites and deletes remain gated. -
Write modes — manual (default), auto-accept (
--auto-approve), and plan (--plan: writes are queued during the turn and applied as one all/none/pick-individually batch at the end). In a real terminal, Shift+Tab cycles between the three live;/modeis the same toggle for terminals where that key doesn't come through, or to set it non-interactively. -
On a dirty working tree, it warns once before writing anything (
--forceto skip). -
--auto-commitcommits only the exact paths written in that turn, taggedlocalgate-agent:; unrelated human changes are never staged./undorestores the last file's pre-agent checkpoint (and records a restoration commit when auto-commit is on), while/rewind [n]restores the lastncheckpoints without resetting Git history. -
/modelopens a picker (name, size, quantization);/model <name>switches directly, with a warning before a mid-session switch and a check for tool-calling support first. -
/theme [dark|light|none],--no-color/NO_COLOR, and/configfor persisted preferences (~/.config/localgate/config.toml: theme, default model, auto-approve, max-turns — precedence is flags > env vars > that file > built-in defaults). -
/usage(session token/request totals),/context(how full the conversation is vs. the model's context window),/resume(pick a past session for this project to continue),/tools(everything available this session, and what's off and why). -
Tools:
read_file,create_file,update_file,delete_file,write_file,list_directory,search_files(grep-like),git_status, andgit_diff. All are confined to the selected project directory; symlink traversal is rejected, atomic replacement prevents partial writes, and.gitignore/.localgateignorehide excluded content from reads, searches, status, and diffs. There is no shell/run_commandtool: it stays disabled until a strong OS sandbox is available. -
What decides when the agent searches, delegates, or writes? The model does — there's no separate routing logic. Each tool's own description is the primary steering (e.g.
write_file's says to read a file first;web_search's says to only use it for things not in the project or training data).delegate_task/web_searchalso get a couple of extra sentences appended to the system prompt, but only when that tool is actually enabled for the session — seeAgentSession.system_prompt()inagent/loop.py. In manual write-mode (the default —/mode, shift+tab), a search or delegation asks for confirmation first, same as a write; auto/plan mode run both without asking. Web search is available by default through DuckDuckGo unless another provider is configured. -
Sub-agents (
--allow-delegation, off by default): the agent can hand off a self-contained sub-task to a fresh, isolated sub-agent and get back only its summary. Read-only tools unless the delegating call explicitly grants more; a sub-agent cannot itself delegate (depth 1). Test this against your own model before relying on it — a small local model may not reliably judge when delegating actually helps. -
Web search (opt-in, off unless
LOCALGATE_SEARCH_PROVIDERis set — the tool doesn't exist at all otherwise, not just disabled). Two providers:openserp(recommended) — a free, self-hosted, no-API-key search API (karust/openserp):docker run -p 127.0.0.1:7000:7000 karust/openserp:latest serve -a 0.0.0.0 -p 7000, thenLOCALGATE_SEARCH_PROVIDER=openserp. Runs on your own machine, so this doesn't actually leave your network — the closest fit to localgate's local-first stance. Override the URL withLOCALGATE_SEARCH_BASE_URLif it's not on the defaulthttp://localhost:7000.tavily— a hosted, paid API; needsLOCALGATE_SEARCH_API_KEYtoo. Kept for anyone who already has a key. This one genuinely sends query text to a third party.
Either way, results are title + short snippet + URL, never full page content.
-
MCP servers (config-driven, stdio only): list servers in
~/.config/localgate/mcp_servers.json([{"name": "...", "command": "...", "args": [...]}]) and their tools are connected at startup and offered to the model asmcp__<server>__<tool>. A server that fails to connect is skipped with a warning, not a reason to fail the whole session./mcpin the REPL lists what's connected;--no-mcpskips connecting for one run. -
Conversation history and recalled context persist per project (
.localgate/session_id), reusing the same RAG memory layer as the HTTP API — re-running it in a project you worked on before picks up where you left off. Disable with--no-memoryorLOCALGATE_MEMORY_ENABLED=false. -
Exit codes for scripted/non-interactive use:
0success,2bad usage,3hit--max-turns,4a write was declined,5the backend rejected the request,6the backend was unreachable,130interrupted.
Tool-calling quality depends entirely on the model — verify yours actually returns structured tool calls (not JSON printed as text) before relying on this day to day.
Self-hosting with automatic HTTPS
localgate deploy generates a Caddyfile (Caddy handles ACME/TLS automatically) and either a
Docker Compose file or a systemd unit, with a fresh admin key written to a 0600 env file:
localgate deploy --domain gw.example.com --target compose
# Copy the generated files to your server, then:
# docker compose up -d
localgate deploy --domain gw.example.com --target systemd
# Follow the printed instructions to install the unit and Caddyfile.
By default only /v1/* and /health* are exposed. Pass --expose-admin to also expose the
dashboard and admin routes (add an IP allowlist to the Caddyfile before doing so).
Once the gateway is running, hand out keys and connect from any machine:
localgate keys create --name alice # on the server
localgate login --url https://gw.example.com --api-key <key> # on Alice's machine
localgate code --remote "refactor auth.py" # runs inference on your gateway
Documentation
| Getting Started | Zero to working gateway |
| Configuration | Every setting |
| API Reference | Every endpoint |
| Database Setup | SQLite → Postgres → Neon |
| RAG Memory | How memory works, and how to tune it |
| Architecture | How it's built, and why |
| Deployment | Running it somewhere real |
| Decisions | ADRs for the choices that shaped the codebase |
Contributing
See CONTRIBUTING.md. Adding a backend means writing one class. Issues
tagged good-first-issue are a good place to start.
License
Give star if you like this project
Metadata
Release files for localgate 0.10.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| localgate-0.10.8.tar.gz | 541.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| localgate-0.10.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 709.4 kB
Release files / localgate-0.10.8.tar.gz
| Download URL | localgate-0.10.8.tar.gz |
|---|---|
| Size | 541.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1846eb31cbee4563e5262ee0d3961ff474514cd74c7255a3ad796cf8e5ba894d
|
|
BLAKE2b-256 checksum How to use checksums |
e553bcddc6e4baab65a741fe319a55aa67c7be51bae8b6a4268262a8525febe5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / localgate-0.10.8-py3-none-any.whl
| Download URL | localgate-0.10.8-py3-none-any.whl |
|---|---|
| Size | 167.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
01512fc0eb1b05f4f9a931669bb7af10da8c20fdb80b3765591f0d317c66449f
|
|
BLAKE2b-256 checksum How to use checksums |
3895cf042c2aa1277c78fa9b40b43e0d5999f21ce99f6d91d0ff004a018a7f5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency log