diff --git a/.env.example b/.env.example
index 79db4bf..2b5e60a 100644
--- a/.env.example
+++ b/.env.example
@@ -5,11 +5,15 @@ ADMIN_PORT=5200
PUBLIC_API_URL=http://localhost:5100
ADMIN_PUBLIC_URL=http://localhost:5200
-# Host Ollama — ContextMemory talks to it via OpenAI-compatible /v1
+# Preferred host-level LLM base URL (any OpenAI-compatible or Ollama-compatible engine).
+# When empty, Compose falls back to OLLAMA_ENDPOINT below.
+# LLM_ENDPOINT=http://host.docker.internal:8000
+
+# DX default: Ollama on the host (used when LLM_ENDPOINT is unset)
OLLAMA_ENDPOINT=http://host.docker.internal:11434
DEFAULT_LLM_MODEL=qwen3.5:9b
-# Optional: OpenAI / vLLM / any OpenAI-compatible host defaults
+# Optional: OpenAI / vLLM / ExLlamaSharp / any OpenAI-compatible host defaults
# OPENAI_ENDPOINT=http://host.docker.internal:8000
# OPENAI_API_KEY=
diff --git a/README.md b/README.md
index 4226f1c..38af2bd 100644
--- a/README.md
+++ b/README.md
@@ -11,14 +11,16 @@
·
Docs
·
- Demo
+ Aha storyboard (GIF)
+ ·
+ Show HN
-
+
@@ -29,7 +31,8 @@
One OpenAI-compatible /v1 URL: wiki memory, agentic tool loop, skills/guardrails, MCP, sandbox, and HITL —
- self-hosted or Cloud. Not a vector black box. Not classic RAG inject.
+ self-hosted or Cloud. Bring your own LLM (any OpenAI-compatible engine).
+ Not a vector black box. Not classic RAG inject.
---
@@ -53,7 +56,7 @@ Your client (OpenAI SDK / Cursor MCP / curl)
│ ContextMemory (.NET 9) │
│ Auth · session wiki · Global Wiki tool │
│ Agentic loop · skills · guardrails · HITL │
-│ LLM backend (per app, OpenAI-compatible) │
+│ LLM backend (per app — BYO engine) │
└───────┬──────────────────┬─────────────────┘
▼ ▼
sandbox-runtime mcp-runtime / MCP servers
@@ -63,6 +66,8 @@ Your client (OpenAI SDK / Cursor MCP / curl)
**Honest boundaries:** this is a **gateway + server-side harness**, not a client agent framework (LangGraph/CrewAI) and not an agent OS (Letta). You keep your OpenAI client; the loop runs on the server.
+**LLM engines:** ContextMemory does **not** ship or lock to one inference stack. Per tenant you pick any OpenAI-compatible `/v1` host — Ollama, vLLM, LM Studio, [ExLlamaSharp](https://github.com/Kortexio/ExLlamaSharp), OpenAI, Azure-compatible, LiteLLM, custom. Compose defaults to Ollama only for zero-friction local DX; swap in **Admin → Config → LLM**.
+
How we compare (Mem0 / Zep / Letta / **why we are not RAG**): [`docs/compare.md`](docs/compare.md).
---
@@ -74,77 +79,26 @@ How we compare (Mem0 / Zep / Letta / **why we are not RAG**): [`docs/compare.md`
| Memory that survives turns without rewriting your client | Session markdown wiki + history inject; send only the new message |
| Memory you can open, edit, audit | Files on disk / Postgres — not opaque embeddings |
| Shared company/docs knowledge in chat | **Global Wiki** digests + on-demand `wiki_search` / `wiki_grep` (**not** classic RAG / embeddings) |
-| Point-in-time facts | Temporal revisions (`asOf` / supersede) on Global Wiki |
-| Tools without a second orchestrator | Same `/v1`: sandbox + MCP integrations + wiki tools |
-| Safer agents | Skills & guardrail packs, validators, confirmation keywords, HITL `[CONFIRM:id]` |
-| Cursor / Claude permanent memory fast | MCP wedge: `memory_save` / `memory_search` / `memory_get` (+ `wiki_search`, `session_recall`) |
-| Any LLM per tenant | Ollama, vLLM, LM Studio, OpenAI, Azure-compatible `/v1`, custom |
-| Operate without writing a test client | **Admin** + **Playground** (timeline, todos, artifacts, HITL) |
-| Full control on your infra | Docker/Compose self-host (API + Admin + mcp-runtime + sandbox) |
-| Zero ops | [Kortexio Cloud](https://kortexio.io) (`cmk_live_…`) |
+| Tools without a second orchestrator | Same `/v1`: sandbox + MCP + wiki tools |
+| Safer agents | Skills & guardrail packs, validators, HITL `[CONFIRM:id]` |
+| Cursor / Claude permanent memory fast | MCP wedge: `memory_save` / `memory_search` / `memory_get` |
+| Any LLM per tenant | BYO `/v1` — Ollama, vLLM, LM Studio, ExLlamaSharp, OpenAI, Azure-compatible, custom |
+| Operate without a test client | **Admin** + **Playground** |
+| Full control / zero ops | Docker self-host · [Kortexio Cloud](https://kortexio.io) (`cmk_live_…`) |
---
-## Capabilities (full surface)
-
-### Memory
-
-- **Session wiki** — markdown pages, index, execution log; compaction; update every N turns; optional dedicated maintainer model; **rolling summary** in the system prompt
-- **History** — last N messages (per-app budget); mid-turn **compaction** archives long transcripts as artifacts when over `MaxContextTokens`
-- **Persona & rules** — `basePersona`, `businessRules`, `formatRules`, `wikiSchema` per app
-- **Global Wiki** — app-scoped docs; ingest/batch APIs; digests; FTS; tools `wiki_search` / `wiki_grep`; revisions / audit / `asOf`
-- **No vector RAG** — discovery is digests + lexical/FTS + tools (Cursor-style), not embeddings
-- **Web search** (optional) — enrich turns; can persist into wiki
-
-### Agentic harness (server-side)
-
-When agentic tools are enabled, the gateway runs a tool loop:
-
-- **Iterations / timeout** — max steps, loop timeout, partial answer on timeout; mid-turn compaction phase
-- **Built-in tools** — `wiki_search`, `wiki_grep`; sandbox `shell_execute` / `python_execute` / `node_execute` / `container_execute` (self-hosted or ACA); discovery helpers (`artifact_*`, `skill_*`, `rule_*`, `tool_describe`, `session_log_search`, `delegate_task`, `todo_write`)
-- **Lazy tool schemas** — MCP and built-ins listed with short/open schemas; `tool_describe` for full args
-- **Artifacts** — long outputs (and all sandbox runs) stored per session; loop keeps a short preview + `artifactId`
-- **Subagents** — `delegate_task` (depth 1, isolated child session)
-- **MCP tools** — per-app catalog (`server__tool`), allow/deny, max tools per turn, OAuth/credentials
-- **Validation modes** — `deterministic` · `hybrid` · `llm-judge`
-- **Hooks** — PreToolUse / PostToolUse guardrail kinds
-- **HITL** — pause before destructive tools; `[CONFIRM:id]` / cancel; checkpoint in session log
-- **Progress** — `context_memory.agentic` phases (incl. Compacting / Subagent*) + `context_memory.discovery` counters
-- **Prompt profiles** — `auto` / `ollama` / `openai` / `claude` / `qwen` / `composer`
-- **Network egress policy** — restricted/allowed + host allowlists
-
-### Skills & guardrails
-
-- **Platform catalog** — shared skills/guardrails (Admin → Skills); import `.skill.json` / `.guardrail.json`
-- **Activation** — `skill` | `always_on` | `requestable` (rules loaded via `rule_search` / `rule_read`)
-- **Per-app policies** — additive inventory on top of platform defaults
-- Seeded examples include anti-hallucination, tool-calling discipline, wiki-first-for-docs, privacy/secrets, transparent failures, and more
-- Guardrail kinds include URL fetch, sandbox claims, tool-failure disclosure, blocked patterns, pre/post tool-use hooks
+## Capabilities (summary)
-### MCP (two directions)
+Full detail: [`docs/architecture-and-features.md`](docs/architecture-and-features.md) · Admin: [`docs/admin-ui.md`](docs/admin-ui.md) · HITL: [`docs/hitl.md`](docs/hitl.md).
-| Direction | Role |
+| Area | Highlights |
|---|---|
-| **Outbound wedge** | Cursor/Claude → ContextMemory (`mcp-server/`) for memory tools |
-| **Inbound catalog** | ContextMemory agent → your MCP servers (HTTP/stdio via `mcp-runtime`, OAuth, catalog rebuild) |
-
-### Admin console
-
-Blazor Admin (`:5200`, Master Key auth) — operators configure tenants without touching JSON by hand:
-
-| Area | What you configure / do |
-|---|---|
-| Dashboard | Apps, requests, wiki/web-search stats |
-| New app / credentials | Register tenant, mint/rotate `cm_live_…` |
-| **Playground** | Chat Lab: agentic timeline (Compacting/Subagent), Todos, Artifacts, wiki refs, HITL |
-| LLM | Backend, model, endpoint, API key, history, streaming, think |
-| Memory & wiki | Session budgets, compaction, maintainer model, Global Wiki on/off + char budget |
-| Web search | Provider, mode, persist-to-wiki |
-| Rate limits | RPM/TPM (+ agentic weight) |
-| Persona & rules | Persona, business/format rules, wiki schema |
-| Agentic | Full gateway knobs: tools, MCP, sandbox, validators, HITL, egress |
-| Skills & policies | Platform + per-app skills/guardrails |
-| Settings | API base URL, Master Key, health |
+| **Memory** | Session wiki + rolling summary; history budgets; Global Wiki digests/FTS/revisions (`asOf`); **no vector RAG** |
+| **Agentic** | Server-side tool loop; sandbox; MCP catalog; artifacts; subagents; validators; HITL; egress policy |
+| **Skills** | Platform + per-app skills/guardrails (`skill` / `always_on` / `requestable`) |
+| **MCP** | Outbound wedge (Cursor → CM) · inbound catalog (CM → your MCP servers) |
+| **Ops** | Admin UI · File or Postgres · Prometheus `/metrics` · Compose (API + Admin + mcp-runtime + sandbox) |
-Guide: [`docs/admin-ui.md`](docs/admin-ui.md) · HITL: [`docs/hitl.md`](docs/hitl.md)
-
-### Self-host stack
-
-Docker Compose brings up a full local platform:
-
-| Service | Role |
-|---|---|
-| **API** (`:5100`) | Gateway `/v1`, admin APIs, metrics |
-| **Admin** (`:5200`) | Operator UI |
-| **mcp-runtime** | Stdio/HTTP MCP sidecar |
-| **sandbox-runtime** | Isolated shell/python/node execution |
-
-Persistence: **File** (single-node) or **Postgres** (HA + FTS). Images: `ghcr.io/kortexio/contextmemory` · `ghcr.io/kortexio/contextmemory-admin`.
+---
-### Observability
+## Quickstart (5 minutes)
-Prometheus `/metrics` · OpenTelemetry (Aspire) · per-app telemetry in Admin.
+**Bring your own LLM.** The gateway talks OpenAI-compatible `/v1` (and Ollama native `/api/chat` when you need `num_ctx`). Compose/GHCR defaults point at Ollama on the host for DX — change anytime in **Admin → Config → LLM** or `PATCH /admin/apps/{id}/config`.
----
+### 1a. Start with any `/v1` engine (vLLM, LM Studio, ExLlamaSharp, OpenAI, …)
-## Quickstart (5 minutes)
+```bash
+docker run --rm -p 5100:8080 \
+ -v contextmemory-data:/app/data \
+ -e ContextMemory__MasterKey=cm_master_dev_key_change_me \
+ -e ContextMemory__Apps__demo-dev__ApiKey=cm_live_dev_key_change_me \
+ -e ContextMemory__Apps__demo-dev__LlmBackend=openai-compatible \
+ -e ContextMemory__Apps__demo-dev__LlmModel=my-model \
+ -e ContextMemory__Apps__demo-dev__LlmEndpoint=http://host.docker.internal:8000 \
+ -e ContextMemory__OpenAiEndpoint=http://host.docker.internal:8000 \
+ --add-host=host.docker.internal:host-gateway \
+ ghcr.io/kortexio/contextmemory:latest
+```
-### 1. Start the gateway
+Prefer a host-level default for all apps: set `ContextMemory__LlmEndpoint` (alias; falls back to `OllamaEndpoint` for older Compose files).
-Default demo points at Ollama on the host. Swap the backend anytime in **Admin → Config → LLM** or `PATCH /admin/apps/{id}/config`.
+### 1b. Or start with Ollama on the host (zero-friction DX)
```bash
docker run --rm -p 5100:8080 \
@@ -195,14 +141,12 @@ docker run --rm -p 5100:8080 \
-e ContextMemory__MasterKey=cm_master_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__ApiKey=cm_live_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__LlmModel=qwen3.5:9b \
- -e ContextMemory__OllamaEndpoint=http://host.docker.internal:11434 \
+ -e ContextMemory__LlmEndpoint=http://host.docker.internal:11434 \
--add-host=host.docker.internal:host-gateway \
ghcr.io/kortexio/contextmemory:latest
```
-Full stack (API + Admin + MCP + sandbox): see [`docs/self-host.md`](docs/self-host.md) / `docker-compose.yml`.
-
-Admin UI: typically `http://localhost:5200`.
+Full stack (API + Admin + MCP + sandbox): [`docs/self-host.md`](docs/self-host.md) / `docker-compose.yml`. Admin: `http://localhost:5200`.
No Docker? Use **[Kortexio Cloud](https://kortexio.io)** (`cmk_live_…`) and set `CONTEXTMEMORY_BASE_URL` to the cloud API.
@@ -222,7 +166,7 @@ Paste into **Cursor → Settings → MCP** (or `~/.cursor/mcp.json`). Same snipp
| **A** | `Remember: staging DB is postgres-staging-01` | `memory_save` |
| **B** (new) | `What is our staging DB?` | `memory_search` + answer |
-CLI: `./scripts/aha-demo.sh` or `.\scripts\aha-demo.ps1` · storyboard: [`docs/aha-demo.html`](docs/aha-demo.html)
+CLI: `./scripts/aha-demo.sh` or `.\scripts\aha-demo.ps1` · storyboard (for GIF recording): [`docs/aha-demo.html`](docs/aha-demo.html)
### Cloud vs self-host
@@ -231,6 +175,7 @@ CLI: `./scripts/aha-demo.sh` or `.\scripts\aha-demo.ps1` · storyboard: [`docs/a
| Best for | Zero ops | Full control (API + Admin + MCP + sandbox) |
| Key | `cmk_live_…` (no `X-App-Id`) | `cm_live_…` + `X-App-Id` |
| Chat body | Identical OpenAI `/v1` | Identical OpenAI `/v1` |
+| LLM | BYO provider in dashboard | BYO engine in Admin / env |
Guides: [Cloud](docs/cloud.md) · [Self-host](docs/self-host.md)
@@ -253,7 +198,8 @@ Thin header helpers (**not** full SDKs): [`@kortexio/contextmemory`](https://www
| Doc | Topic |
|---|---|
| [docs/compare.md](docs/compare.md) | Why it exists · vs Mem0 / Zep / Letta · **why we are not RAG** |
-| [docs/architecture-and-features.md](docs/architecture-and-features.md) | Wiki, temporal facts, agentic, skills |
+| [docs/show-hn.md](docs/show-hn.md) | Suggested Show HN title + blurb |
+| [docs/architecture-and-features.md](docs/architecture-and-features.md) | Wiki, temporal facts, agentic, skills, **LLM backends** |
| [docs/admin-ui.md](docs/admin-ui.md) | Admin UI map |
| [docs/hitl.md](docs/hitl.md) | Human-in-the-loop |
| [docs/api.md](docs/api.md) | HTTP API |
diff --git a/docker-compose.ghcr.yml b/docker-compose.ghcr.yml
index e84bc5d..2433644 100644
--- a/docker-compose.ghcr.yml
+++ b/docker-compose.ghcr.yml
@@ -20,7 +20,10 @@ services:
ASPNETCORE_URLS: http://+:8080
ContextMemory__PersistenceProvider: ${PERSISTENCE_PROVIDER:-File}
ContextMemory__DataPath: /app/data
+ ContextMemory__LlmEndpoint: ${LLM_ENDPOINT:-}
ContextMemory__OllamaEndpoint: ${OLLAMA_ENDPOINT:-http://host.docker.internal:11434}
+ ContextMemory__OpenAiEndpoint: ${OPENAI_ENDPOINT:-https://api.openai.com}
+ ContextMemory__OpenAiApiKey: ${OPENAI_API_KEY:-}
ContextMemory__DefaultLlmModel: ${DEFAULT_LLM_MODEL:-qwen3.5:9b}
ContextMemory__MasterKey: ${MASTER_KEY:-cm_master_dev_key_change_me}
ContextMemory__AdminCorsOrigins__0: ${ADMIN_PUBLIC_URL:-http://localhost:5200}
diff --git a/docker-compose.network.yml b/docker-compose.network.yml
index a47bb7c..80adaf4 100644
--- a/docker-compose.network.yml
+++ b/docker-compose.network.yml
@@ -27,6 +27,7 @@ services:
Username=${POSTGRES_USER:-contextmemory};Password=${POSTGRES_PASSWORD:-contextmemory_dev}
ContextMemory__WikiPath: /app/wikis
ContextMemory__OllamaEndpoint: ${OLLAMA_ENDPOINT:-https://server.kortexio.io}
+ ContextMemory__LlmEndpoint: ${LLM_ENDPOINT:-}
ContextMemory__DefaultLlmModel: ${DEFAULT_LLM_MODEL:-qwen3.5:9b}
ContextMemory__MasterKey: ${MASTER_KEY:-cm_master_local_dev_change_me}
ContextMemory__McpRuntimeUrl: http://mcp-runtime:8080
diff --git a/docker-compose.yml b/docker-compose.yml
index 731445b..471502a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -70,7 +70,10 @@ services:
ASPNETCORE_URLS: http://+:8080
ContextMemory__PersistenceProvider: ${PERSISTENCE_PROVIDER:-File}
ContextMemory__DataPath: /app/data
+ ContextMemory__LlmEndpoint: ${LLM_ENDPOINT:-}
ContextMemory__OllamaEndpoint: ${OLLAMA_ENDPOINT:-http://host.docker.internal:11434}
+ ContextMemory__OpenAiEndpoint: ${OPENAI_ENDPOINT:-https://api.openai.com}
+ ContextMemory__OpenAiApiKey: ${OPENAI_API_KEY:-}
ContextMemory__DefaultLlmModel: ${DEFAULT_LLM_MODEL:-qwen3.5:9b}
ContextMemory__MasterKey: ${MASTER_KEY:-cm_master_dev_key_change_me}
ContextMemory__McpRuntimeUrl: http://mcp-runtime:8080
diff --git a/docs/README.md b/docs/README.md
index 58b295e..a476266 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -16,5 +16,6 @@ Short entry point. Start at the [README](../README.md) for the 5-minute Cursor w
| [ops.md](ops.md) | Persistence, security, troubleshooting |
| [license-and-support.md](license-and-support.md) | License & contributing |
| [blueprint-01-upstream.md](blueprint-01-upstream.md) | Blueprint 01 upstream enrichments (CM-1…CM-7) |
-| [aha-demo.html](aha-demo.html) | 20s GIF storyboard |
+| [show-hn.md](show-hn.md) | Suggested Show HN title + blurb (BYO LLM) |
+| [aha-demo.html](aha-demo.html) | 20s GIF storyboard (not a live sandbox) |
diff --git a/docs/admin-ui.md b/docs/admin-ui.md
index 6a7c5f8..f37e0fc 100644
--- a/docs/admin-ui.md
+++ b/docs/admin-ui.md
@@ -46,7 +46,7 @@ Per-app pages (from Applications):
- Docker Compose / GHCR Admin container: `http://api:8080` (server-side calls stay on the Docker network; the browser still opens Admin on `localhost:5200`)
3. Paste the **Master Key** (demo: `cm_master_dev_key_change_me`).
4. Click **Save**, then **Test connection**.
-5. Confirm Health: Ollama, Persistence, Apps loaded, optional Database.
+5. Confirm Health: Persistence, Apps loaded, default model / LLM endpoint, optional Database.
**Save** stores values in this browser. **Reset to defaults** clears browser overrides and restores host defaults (`Admin__DefaultApiBaseUrl`, `Admin__DefaultMasterKey`). A link to public Prometheus metrics (`GET /metrics`) appears when connected.
diff --git a/docs/architecture-and-features.md b/docs/architecture-and-features.md
index 54190b3..32fc4c8 100644
--- a/docs/architecture-and-features.md
+++ b/docs/architecture-and-features.md
@@ -133,11 +133,11 @@ Theory: minimize tokens in the big chat model. Prefer **lazy discovery** over st
| Mechanism | Role |
|---|---|
| **Static (every turn)** | Persona + budgeted session wiki + rolling summary + top-K Global Wiki digests |
-| **On demand** | `wiki_search`, `wiki_grep`, `skill_search`/`skill_read`, `rule_search`/`rule_read`, `tool_describe`, artifacts |
+| **On demand** | `wiki_search`, `wiki_grep`, `skill_search`/`skill_read`, `tool_search`/`tool_describe`, artifacts |
| **Models** | `LlmModel` (answer/agent) + `WikiLlmModel` (digests, rolling summary, mid-turn compaction) |
| **Mid-turn compaction** | If estimated tokens > `MaxContextTokens`: archive transcript as `history:…` artifact, summarize with `WikiLlmModel`, shrink messages; emit phase `Compacting` |
| **Artifacts** | Long tool/MCP outputs stored per session; sandbox/terminal **always** archived with a short preview + `artifactId`. Tools: `artifact_tail` / `artifact_read` |
-| **Lazy tools[]** | Built-ins and MCP expose name + one-line description + open schema; call `tool_describe` before first use of an unfamiliar tool |
+| **Lazy tools[]** | Built-ins in `tools[]`; MCP discovered via `tool_search` → `tool_describe` (pin) → call; open stubs omit params until pin |
| **Lazy skills** | Prompt lists up to 3 default skill ids; `skill_search` → snippets; `skill_read` → body |
| **Rules** | `always_on` injected into system; `requestable` via `rule_search` / `rule_read` (`Activation` on catalog skills) |
| **Hooks** | Guardrail kinds `pre-tool-use` / `post-tool-use` (deny/allow patterns, require confirm, redact) |
@@ -154,7 +154,7 @@ Theory: minimize tokens in the big chat model. Prefer **lazy discovery** over st
| `artifact_read` / `artifact_tail` | Recover truncated / archived tool output |
| `skill_search` / `skill_read` | Find and load skill bodies |
| `rule_search` / `rule_read` | Requestable rules |
-| `tool_describe` | Full tool schema/description |
+| `tool_search` / `tool_describe` | Find MCP tools by keyword; load full schema and pin into turn catalog |
| `session_log_search` | Grep session log when summary misses a detail |
| `delegate_task` | Spawn isolated subagent (depth 1) |
| `todo_write` | Session todo list for the Admin timeline |
@@ -298,12 +298,14 @@ Discovery telemetry also reports `harness_mode`, `resolved_prompt_profile`, `pro
### Supported LLM backends
+ContextMemory is **engine-agnostic**: it never embeds weights. Point each tenant (or the host defaults) at any OpenAI-compatible `/v1` server — Ollama, vLLM, LM Studio, [ExLlamaSharp](https://github.com/Kortexio/ExLlamaSharp), OpenAI, Azure OpenAI-compatible gateways, LiteLLM, custom.
+
| Backend value | Wire protocol | Host default URL |
|---|---|---|
-| `ollama` (default) | OpenAI `/v1` on `OllamaEndpoint` — **auto-switches to native when `numCtx` is set** | `ContextMemory:OllamaEndpoint` |
+| `ollama` (DX default) | OpenAI `/v1` on `LlmEndpoint` / `OllamaEndpoint` — **auto-switches to native when `numCtx` is set** | `ContextMemory:LlmEndpoint` (fallback `OllamaEndpoint`) |
| `vllm` / `openai-compatible` / `openai` / `custom` | OpenAI `/v1/chat/completions` | `ContextMemory:OpenAiEndpoint` or per-app `llmEndpoint` |
| `lmstudio` | OpenAI `/v1` | `ContextMemory:LmStudioEndpoint` |
-| `ollama-native` | Ollama `/api/chat` (respects `options.num_ctx`) | `ContextMemory:OllamaEndpoint` |
+| `ollama-native` | Ollama `/api/chat` (respects `options.num_ctx`) | `ContextMemory:LlmEndpoint` / `OllamaEndpoint` |
**Per-app overrides** (Admin → app → Config, or `PATCH /admin/apps/{id}/config`):
@@ -314,7 +316,7 @@ Discovery telemetry also reports `harness_mode`, `resolved_prompt_profile`, `pro
| `llmEndpoint` | Optional base URL for this app only. Empty = host default above. For OpenAI-compatible URLs, `/v1` is appended if missing. |
| `llmApiKey` | Optional API key for this app. Empty = host `OpenAiApiKey`. |
-Example — point one tenant at a remote vLLM / LiteLLM server:
+Example — point one tenant at a remote vLLM / LiteLLM / ExLlamaSharp server:
```json
{
diff --git a/docs/ops.md b/docs/ops.md
index e9c9d32..82d39c8 100644
--- a/docs/ops.md
+++ b/docs/ops.md
@@ -59,8 +59,8 @@ The seed app in `appsettings.json` uses `en-US`. Tenants can set `DefaultLanguag
| Symptom | Likely cause | Fix |
|---|---|---|
-| `docker pull` / health OK but chat hangs or 502/503 | Ollama not reachable from the container | On the **host**: `ollama serve` + `ollama pull qwen3.5:9b`. Keep `ContextMemory__OllamaEndpoint=http://host.docker.internal:11434` (Compose/scripts already set `host-gateway`). On Linux without Docker Desktop, confirm `host.docker.internal` resolves. |
-| Health shows Ollama unhealthy | Wrong endpoint or firewall | From the API container or host, `curl http://host.docker.internal:11434/api/tags`. Override with `-e ContextMemory__OllamaEndpoint=...` or `OLLAMA_ENDPOINT` in `.env`. |
+| `docker pull` / health OK but chat hangs or 502/503 | LLM not reachable from the container | Point `ContextMemory__LlmEndpoint` (or legacy `OllamaEndpoint` / `OLLAMA_ENDPOINT`) at a reachable engine. For Ollama on the **host**: `ollama serve` + `ollama pull qwen3.5:9b`, keep `http://host.docker.internal:11434` (Compose already sets `host-gateway`). For vLLM / ExLlamaSharp / LM Studio, set `LLM_ENDPOINT` or per-app `llmBackend=openai-compatible` + `llmEndpoint`. On Linux without Docker Desktop, confirm `host.docker.internal` resolves. |
+| Health shows apps/persistence unhealthy | Wrong Master Key path or empty seed | From the API container or host, `curl http://localhost:5100/health` and check `checks.defaultLlmEndpoint`. Override with `-e ContextMemory__LlmEndpoint=...` or `LLM_ENDPOINT` / `OLLAMA_ENDPOINT` in `.env`. |
| Agentic prompt exceeds context (`n_ctx=4096`) | Playground sent `options.num_ctx=4096` (old default) **or** Ollama `/v1` ignored `num_ctx` | Leave Playground `num_ctx` empty (uses app LLM config) or set 8192/32768. Also set `llmOptions.numCtx` on the app (gateway auto-uses `ollama-native`) **or** `OLLAMA_CONTEXT_LENGTH` / Modelfile `PARAMETER num_ctx`. |
| Jinja `No user query found in messages` | Strict Qwen/Bonsai chat_template | Patch model `TEMPLATE` (drop `raise_exception`); gateway already merges compaction into one system + ensures a user message. |
| Model invents Zuora accounts / `0 tool(s)` | Weak model / missing evidence guardrail | Enable `live-data-evidence`; use `harnessMode=weak` or Qwen profile; smoke with `scripts/smoke-multi-model.ps1`. |
diff --git a/docs/self-host.md b/docs/self-host.md
index 1f892e7..534cfa7 100644
--- a/docs/self-host.md
+++ b/docs/self-host.md
@@ -13,19 +13,38 @@ Public images (published on every push to `main`):
| `ghcr.io/kortexio/contextmemory` | [API](https://github.com/users/Kortexio/packages/container/package/contextmemory) |
| `ghcr.io/kortexio/contextmemory-admin` | [Admin UI](https://github.com/users/Kortexio/packages/container/package/contextmemory-admin) |
-**API only** (needs [Docker](https://docs.docker.com/get-docker/) + Ollama on the host):
+**API only** (needs [Docker](https://docs.docker.com/get-docker/) + any reachable LLM):
+
+**Bring-your-own `/v1` engine** (vLLM, LM Studio, [ExLlamaSharp](https://github.com/Kortexio/ExLlamaSharp), OpenAI, LiteLLM, …):
+
+```bash
+docker run --rm -p 5100:8080 \
+ --add-host=host.docker.internal:host-gateway \
+ -v contextmemory-data:/app/data \
+ -e ContextMemory__MasterKey=cm_master_dev_key_change_me \
+ -e ContextMemory__Apps__demo-dev__ApiKey=cm_live_dev_key_change_me \
+ -e ContextMemory__Apps__demo-dev__LlmBackend=openai-compatible \
+ -e ContextMemory__Apps__demo-dev__LlmModel=my-model \
+ -e ContextMemory__Apps__demo-dev__LlmEndpoint=http://host.docker.internal:8000 \
+ -e ContextMemory__OpenAiEndpoint=http://host.docker.internal:8000 \
+ ghcr.io/kortexio/contextmemory:latest
+```
+
+**Ollama on the host** (zero-friction DX default):
```bash
docker run --rm -p 5100:8080 \
--add-host=host.docker.internal:host-gateway \
-v contextmemory-data:/app/data \
- -e ContextMemory__OllamaEndpoint=http://host.docker.internal:11434 \
+ -e ContextMemory__LlmEndpoint=http://host.docker.internal:11434 \
-e ContextMemory__MasterKey=cm_master_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__ApiKey=cm_live_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__LlmModel=qwen3.5:9b \
ghcr.io/kortexio/contextmemory:latest
```
+`ContextMemory__LlmEndpoint` is the preferred host default; `OllamaEndpoint` remains a legacy alias when `LlmEndpoint` is empty.
+
Then: http://localhost:5100/health
**API + Admin from GHCR (no local build):**
@@ -48,7 +67,7 @@ Or the helper scripts:
### Build from source: Docker Compose
-Builds and starts the **API** (`:5100`), **Admin** (`:5200`), **mcp-runtime** (stdio MCP host), and **sandbox-runtime** (shell/python/node) locally. Requires [Docker](https://docs.docker.com/get-docker/) and an LLM reachable from the containers (Ollama on the host by default).
+Builds and starts the **API** (`:5100`), **Admin** (`:5200`), **mcp-runtime** (stdio MCP host), and **sandbox-runtime** (shell/python/node) locally. Requires [Docker](https://docs.docker.com/get-docker/) and an LLM reachable from the containers (Compose DX default = Ollama on the host; point `LLM_ENDPOINT` / `OPENAI_ENDPOINT` at any `/v1` engine instead).
```bash
git clone https://github.com/Kortexio/ContextMemory.git
@@ -77,19 +96,23 @@ Ops triage (Azure Monitor / GitHub): see [inbound-mcp-guide.md](inbound-mcp-guid
For a Postgres-backed network overlay (shared Docker network, extra tenants), see `docker-compose.network.yml`.
-**Ollama on the host**
+**LLM on the host**
```bash
+# Example: Ollama DX default
ollama pull qwen3.5:9b
-# Compose default: ContextMemory__OllamaEndpoint=http://host.docker.internal:11434
+# Compose: OLLAMA_ENDPOINT=http://host.docker.internal:11434
+# Or any /v1 engine: LLM_ENDPOINT=http://host.docker.internal:8000 + Admin backend openai-compatible
```
-**Useful Compose env vars** (see [`.env.example`](.env.example)):
+**Useful Compose env vars** (see [`.env.example`](../.env.example)):
| Variable | Default | Meaning |
|---|---|---|
| `API_PORT` / `ADMIN_PORT` | `5100` / `5200` | Host ports |
-| `OLLAMA_ENDPOINT` | `http://host.docker.internal:11434` | LLM from inside the API container |
+| `LLM_ENDPOINT` | _(empty)_ | Preferred host LLM base URL → `ContextMemory__LlmEndpoint` |
+| `OLLAMA_ENDPOINT` | `http://host.docker.internal:11434` | Legacy local default when `LLM_ENDPOINT` is empty |
+| `OPENAI_ENDPOINT` / `OPENAI_API_KEY` | OpenAI cloud / empty | Host defaults for `openai-compatible` / `vllm` / `openai` |
| `DEFAULT_LLM_MODEL` | `qwen3.5:9b` | Seed + default model |
| `MASTER_KEY` | `cm_master_dev_key_change_me` | Admin Master Key |
| `DEMO_APP_API_KEY` | `cm_live_dev_key_change_me` | Seed app API key |
@@ -111,7 +134,7 @@ curl -X POST http://localhost:5100/v1/chat/completions \
### Prerequisites (dotnet run)
- .NET 9 SDK
-- Ollama (or another configured backend) reachable on the network
+- Ollama, [ExLlamaSharp](https://github.com/Kortexio/ExLlamaSharp), vLLM, LM Studio, or any OpenAI-compatible `/v1` backend reachable on the network
- Optional: PostgreSQL 14+ for production / multi-instance HA
### 1. Configure
@@ -136,7 +159,9 @@ For **PostgreSQL** in production or multi-instance HA:
"ContextMemory": {
"PersistenceProvider": "Postgres",
"DataPath": "../../data",
+ "LlmEndpoint": "",
"OllamaEndpoint": "http://localhost:11434",
+ "OpenAiEndpoint": "https://api.openai.com",
"MasterKey": "your-master-key",
"Apps": {
"my-app": {
@@ -158,7 +183,10 @@ Use `"Postgres"` exactly (not `Postgresql`). Relative `DataPath` values resolve
| `ConnectionStrings:ContextMemory` | PostgreSQL connection string when `PersistenceProvider` is `Postgres` |
| `ContextMemory:PersistenceProvider` | `File` (default) or `Postgres` |
| `ContextMemory:DataPath` | Root for file-based persistence (apps, sessions, wiki) |
-| `ContextMemory:OllamaEndpoint` | Default Ollama (or Ollama-compatible) backend base URL |
+| `ContextMemory:LlmEndpoint` | Preferred host default LLM base URL (any OpenAI- or Ollama-compatible engine). Empty = use `OllamaEndpoint` |
+| `ContextMemory:OllamaEndpoint` | Legacy alias for the local/Ollama-compatible default when `LlmEndpoint` is empty |
+| `ContextMemory:OpenAiEndpoint` | Host default for `openai` / `openai-compatible` / `vllm` / `custom` when the app has no `llmEndpoint` |
+| `ContextMemory:LmStudioEndpoint` | Host default for `lmstudio` |
| `ContextMemory:DefaultLlmModel` | Fallback model when an app has no `LlmModel` |
| `ContextMemory:MasterKey` | Secret for Admin API / admin dashboard |
| `ContextMemory:AdminCorsOrigins` | Allowed browser origins for Admin UI CORS |
diff --git a/docs/show-hn.md b/docs/show-hn.md
new file mode 100644
index 0000000..c01ebee
--- /dev/null
+++ b/docs/show-hn.md
@@ -0,0 +1,44 @@
+> Part of the ContextMemory docs. [Back to README](../README.md).
+
+# Show HN — suggested blurb
+
+One product only. Link the GitHub repo. Keep Cloud / other Kortexio apps as a single closing line.
+
+## Title
+
+```
+Show HN: ContextMemory – OpenAI-compatible gateway with wiki memory (not RAG)
+```
+
+## Body
+
+```
+Agents forget. Most “memory” products hide facts in vector stores you can’t open.
+
+ContextMemory is an open-source agentic memory gateway: your app keeps calling a
+normal OpenAI-compatible /v1/chat/completions URL. The gateway attaches a session
+markdown wiki + history, can run a server-side tool loop (sandbox, MCP, wiki search),
+and returns a standard chat.completions response.
+
+Bring your own LLM — Ollama, vLLM, LM Studio, ExLlamaSharp, OpenAI, Azure-compatible,
+or any /v1 host. Compose defaults to Ollama only for local DX; swap the backend per
+tenant in Admin.
+
+Not classic RAG inject. Not a client agent framework. Memory you can open like a wiki.
+
+Aha with the Cursor MCP wedge (two chats):
+ A) “Remember: staging DB is postgres-staging-01” → memory_save
+ B) new chat “What is our staging DB?” → memory_search
+
+Repo: https://github.com/Kortexio/ContextMemory
+Self-host / Cloud: see README. GIF storyboard: docs/aha-demo.html
+```
+
+## Do / don’t
+
+| Do | Don’t |
+|---|---|
+| Lead with wiki memory + `/v1` drop-in | List CompanyBrain / TriageHub / Fincheck in the title |
+| Say BYO LLM explicitly | Imply the product is “an Ollama app” |
+| Point to Compose + Admin LLM picker | Promise a public live chat sandbox unless it exists |
+| One soft line on Kortexio Cloud at the end | Dump six repos into the first paragraph |
diff --git a/mcp-server/README.md b/mcp-server/README.md
index 750de43..1adc486 100644
--- a/mcp-server/README.md
+++ b/mcp-server/README.md
@@ -6,13 +6,17 @@
### 1. Start the gateway (Docker)
+**Any `/v1` engine** — set `LlmBackend=openai-compatible` + `LlmEndpoint` (see [main README](../README.md)).
+
+**Ollama DX default:**
+
```bash
docker run --rm -p 5100:8080 \
-v contextmemory-data:/app/data \
-e ContextMemory__MasterKey=cm_master_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__ApiKey=cm_live_dev_key_change_me \
-e ContextMemory__Apps__demo-dev__LlmModel=qwen3.5:9b \
- -e ContextMemory__OllamaEndpoint=http://host.docker.internal:11434 \
+ -e ContextMemory__LlmEndpoint=http://host.docker.internal:11434 \
--add-host=host.docker.internal:host-gateway \
ghcr.io/kortexio/contextmemory:latest
```
diff --git a/src/ContextMemory.Adapters/LlmAdapterResolver.cs b/src/ContextMemory.Adapters/LlmAdapterResolver.cs
index 38ffe50..020e18b 100644
--- a/src/ContextMemory.Adapters/LlmAdapterResolver.cs
+++ b/src/ContextMemory.Adapters/LlmAdapterResolver.cs
@@ -78,10 +78,10 @@ private ILlmAdapter Resolve(
_serviceProvider.GetRequiredService()
.WithConnection(endpointOverride, apiKeyOverride),
- // Default: OpenAI-compatible /v1 (Ollama /v1, vLLM, OpenAI, Azure-compatible, custom).
+ // Default: OpenAI-compatible /v1 (Ollama /v1, vLLM, ExLlamaSharp, OpenAI, Azure-compatible, custom).
"ollama" =>
_serviceProvider.GetRequiredService()
- .WithConnection(endpointOverride ?? _options.OllamaEndpoint, apiKeyOverride),
+ .WithConnection(endpointOverride ?? _options.ResolveLocalLlmEndpoint(), apiKeyOverride),
"vllm" or "openai" or "openai-compatible" or "custom" =>
_serviceProvider.GetRequiredService()
@@ -89,7 +89,7 @@ private ILlmAdapter Resolve(
_ =>
_serviceProvider.GetRequiredService()
- .WithConnection(endpointOverride ?? _options.OllamaEndpoint, apiKeyOverride)
+ .WithConnection(endpointOverride ?? _options.ResolveLocalLlmEndpoint(), apiKeyOverride)
};
}
}
diff --git a/src/ContextMemory.Adapters/OllamaAdapter.cs b/src/ContextMemory.Adapters/OllamaAdapter.cs
index 457d804..f7921c0 100644
--- a/src/ContextMemory.Adapters/OllamaAdapter.cs
+++ b/src/ContextMemory.Adapters/OllamaAdapter.cs
@@ -11,7 +11,14 @@ namespace ContextMemory.Adapters;
public sealed class OllamaAdapterOptions
{
public const string SectionName = "ContextMemory";
+
+ /// Legacy bind target; prefer / .
public string OllamaEndpoint { get; set; } = "http://localhost:11434";
+
+ public string LlmEndpoint { get; set; } = string.Empty;
+
+ public string ResolveBaseUrl() =>
+ !string.IsNullOrWhiteSpace(LlmEndpoint) ? LlmEndpoint.Trim() : OllamaEndpoint.Trim();
}
public sealed class OllamaAdapter : ILlmAdapter
@@ -27,7 +34,7 @@ public sealed class OllamaAdapter : ILlmAdapter
private readonly string? _apiKey;
public OllamaAdapter(HttpClient httpClient, IOptions options)
- : this(httpClient, options.Value.OllamaEndpoint, apiKey: null)
+ : this(httpClient, options.Value.ResolveBaseUrl(), apiKey: null)
{
}
diff --git a/src/ContextMemory.Admin.UI/Components/AgenticConfigEditor.razor b/src/ContextMemory.Admin.UI/Components/AgenticConfigEditor.razor
index d5c69dd..0d3c67b 100644
--- a/src/ContextMemory.Admin.UI/Components/AgenticConfigEditor.razor
+++ b/src/ContextMemory.Admin.UI/Components/AgenticConfigEditor.razor
@@ -197,10 +197,10 @@
-
+
- Hard ceiling for how many MCP tools are offered to the model each turn (top-K by query relevance).
- Applies to Weak and Strong harness alike. Set ≥ catalog size (e.g. 100) to expose all tools.
+ Top-K MCP tools offered to the model each turn (by query relevance). Hard-capped at 12
+ by the gateway so oversized catalogs cannot blow the context window (Qwen dumps the selection into the system prompt).