OpenJCockpit’s agents call large language models through configurable model aliases. In the local stack, three providers are involved:
| Route | Provider | Used for |
|---|---|---|
Default/coding LLM and agent model aliases (agent-chat, customer-chat) |
Local Ollama model (qwen3.6:27b), reached through the LiteLLM gateway |
Every agent’s main LLM calls. |
best / cheapest aliases and embeddings |
OpenAI (called directly, not proxied) | Remaining OpenAI-hosted calls. OPENAI_API_KEY is mandatory to boot the stack. |
| Direct Ollama (optional, Revert B) | Host Ollama, bypassing LiteLLM | Incident fallback, configuration only. |
flowchart LR
E[embabel-agent-runtime] -- "default / coding LLM<br/>(virtual key)" --> L[LiteLLM gateway<br/>localhost:4100]
L --> O[(Ollama on the host<br/>qwen3.6:27b)]
E -- "best / cheapest, embeddings" --> OA[(OpenAI API)]
E -. "Revert B: LLM_CODING_ROUTE=direct" .-> O
This is not an “all-local” mode, and not an “everything through one gateway” setup: both OpenAI and the local model are in use.
The agent pipeline’s default LLM (embabel-agent-runtime) runs on a locally
hosted Qwen model served by Ollama on your host machine. Ollama is
not part of the Docker Compose stack. You install and run it yourself,
before docker compose up:
ollama pull qwen3.6:27b.Networking defaults: host-run vs. Compose-run. The default Ollama base
URL depends on how embabel-agent-runtime itself is running:
| Mode | Default OLLAMA_BASE_URL |
|---|---|
Host-run (e.g. mvn spring-boot:run, no Docker) |
http://localhost:11434 |
Compose-run (inside docker compose up) |
http://host.docker.internal:11434 |
Two environment variables control this, both overridable in .env:
| Variable | Default | Meaning |
|---|---|---|
OLLAMA_BASE_URL |
http://localhost:11434 (host-run) / http://host.docker.internal:11434 (Compose) |
Base URL of the Ollama daemon. |
OLLAMA_MODEL |
qwen3.6:27b |
The Ollama model tag to use; must exist in ollama list. |
In docker-compose.yml both values are defined once (YAML anchors) and shared by the litellm and
embabel-agent-runtime containers.
Caveat: OLLAMA_BASE_URL in .env applies to the container, not your
host. Setting OLLAMA_BASE_URL=http://localhost:11434 in .env breaks the
containerized embabel-agent-runtime, because nothing listens on the
container’s own loopback interface. Only override it if your Ollama daemon
listens somewhere other than the Compose default.
Warning: Linux hosts. The Compose container reaches Ollama via
host.docker.internal. On Docker Desktop (macOS/Windows) this resolves to the host’s default127.0.0.1-bound Ollama automatically. On Linux,host.docker.internalarrives on a non-loopback host interface, so Ollama must be bound wider than127.0.0.1, e.g.OLLAMA_HOST=0.0.0.0. Doing this exposes an unauthenticated LLM endpoint to your local network: anyone on the same network could submit prompts and consume your GPU. Prefer a narrower bind (thedocker0/bridge interface address) or firewall port11434instead of binding to all interfaces.
Health is not readiness for inference. /actuator/health reports UP
even when Ollama is unreachable. No model pull or availability probe is
attempted at startup, by design. The first sign of a problem will be an LLM
call failing at run time, not a failing health check.
The agent pipeline’s default/coding LLM routes through a LiteLLM
gateway container at host URL http://localhost:4100. The container
internally listens on port 4000, but the host mapping is 4100 because
port 4000 is already used by the dashboard service. The host Ollama
prerequisite is unchanged: Ollama is still not a Compose service, still
installed and run on the host, still ollama pull qwen3.6:27b.
Besides model routing, the same LiteLLM instance also acts as:
git-mcp-server (see MCP tools).infrastructure/litellm/config.yaml is the single source of truth for the model list and runtime
policy (timeouts, no retries, message-logging redaction). See
infrastructure/litellm/README.md for what it pins and why.
The following environment variables control the LiteLLM gateway. All are
defined in .env.example (commented out by default) and overridable in
.env:
| Variable | Default | Meaning |
|---|---|---|
LITELLM_MASTER_KEY |
sk-openjcockpit-litellm-local |
The LiteLLM proxy admin/master API key. |
LITELLM_SALT_KEY |
sk-openjcockpit-litellm-salt-local |
LiteLLM’s encryption salt key. Changing it invalidates stored credentials. |
LITELLM_VIRTUAL_KEY |
sk-openjcockpit-embabel-local |
The virtual key embabel-agent-runtime presents to the gateway. |
LITELLM_A2A_KEY |
sk-openjcockpit-a2a-local |
The one LiteLLM A2A client key (catalog, agent cards, calling agents). |
LITELLM_AI_CONTROL_KEY |
sk-openjcockpit-ai-control-local |
LiteLLM key of ai-control-service: MCP REST bridge and the Git MCP server only. |
LITELLM_UI_USERNAME |
ricky |
LiteLLM admin UI username. |
LITELLM_UI_PASSWORD |
Welkom01! |
LiteLLM admin UI password. |
LLM_CODING_ROUTE |
unset (commented out) | Set to direct to bypass the gateway and call the host Ollama daemon directly (Revert B, see below). |
The non-admin virtual keys are created by the idempotent local-seed one-shot on every
docker compose up.
Verifying the gateway path end to end. An automated, live Playwright
check proves a real agent LLM call actually transits this gateway to host
Ollama and is attributed to the expected virtual key and model: run
npm run e2e:llm from frontend/e2e/. It requires the full stack running (LiteLLM,
embabel-agent-runtime, host Ollama with the configured model pulled, plus
Keycloak/ai-control-service/dashboard). See frontend/e2e/README.md’s
“The agent ⇄ LiteLLM ⇄ Ollama live check” section for the exact
preconditions, strict-mode flag, and what it does and does not prove.
Warning: local-only credentials.
ricky/Welkom01!and every LiteLLM gateway key listed above are local-testing-only seed credentials. They must never be reused in CI, staging, or production.
Non-Keycloak auth gap. The LiteLLM admin UI on port 4100 is a second
authentication surface backed by LiteLLM’s own internal credential store,
not Keycloak. This is an accepted, documented local-development-only gap,
the same posture already used elsewhere in this repo for docker-socat
and git-mcp-server. Vendor admin-UI accessibility is explicitly out of
scope.
Worst-case call durations. One default-LLM call has a worst-case
duration of up to 600 seconds; one full agent action including its
data-binding re-ask has a worst-case duration of up to 1200 seconds,
identical to the pre-gateway direct-Ollama path, because the gateway adds
no additional retry multiplication. If the gateway container itself is
down, a call fails in under 45 seconds per attempt (about 90 seconds
including the re-ask). A wrong or missing virtual key fails closed in well
under 1 second total, but as two gateway requests, not one, bounded
by embabel.agent.platform.llm-operations.data-binding.max-attempts=2 (not
an unbounded retry loop). See Known limitations for
why this is two requests instead of one.
A whole workflow run can take much longer than one call. Locally, ai-control-service waits up to
WORKFLOW_AGENT_TIMEOUT (default 3600 seconds in Compose) for a run, because several agents on the
local model with Git tool calls take well over the 600-second service default.
Revert A: to OpenAI. There is no automatic fallback between providers.
To move the default LLM back to gpt-4.1-mini, use either form:
EMBABEL_MODELS_DEFAULT_LLM=gpt-4.1-miniapplication.yml edit: embabel.models.default-llm: gpt-4.1-miniThis is unchanged and still works precisely because OpenAI is not proxied through the gateway. This revert is not automatic; it requires a manual step.
Revert B: to direct host Ollama. Add LLM_CODING_ROUTE=direct to
.env, then run docker compose up -d embabel-agent-runtime. No rebuild,
no code edit and no docker-compose.yml edit is needed. This revert is not
automatic; it requires a manual step. Caveat: the litellm service’s
depends_on: ... service_healthy condition on embabel-agent-runtime
still applies, so the litellm container must still start even though it
will not be called when LLM_CODING_ROUTE=direct. Removing that dependency
would itself be a docker-compose.yml edit, which is explicitly out of
scope here.
Verification note: check that the variable reaches the container. Both reverts are configured through environment variables read by
embabel-agent-runtime’sapplication.yml(openjcockpit.llm.coding.route: ${LLM_CODING_ROUTE:gateway}, and Spring’s relaxed binding forEMBABEL_MODELS_DEFAULT_LLM). In the currentdocker-compose.yml, theembabel-agent-runtimeservice has noenv_fileand does not listLLM_CODING_ROUTEorEMBABEL_MODELS_DEFAULT_LLMunderenvironment, and.envis only used by Compose for variable interpolation. As the files stand, adding these variables to.envalone therefore does not appear to reach the container; the variable must be present in the service’s environment (which is adocker-compose.ymlchange) or set when running the service on the host. Verify withdocker compose exec embabel-agent-runtime env | grep -E "LLM_CODING_ROUTE|EMBABEL_MODELS"before relying on either revert during an incident.
Release note (local Ollama default). This changes the default LLM for every agent in the
pipeline (at the time of that change: requirement analysis, impact, test design, implementation,
review, realisation, evidence, prompt-to-definition), not only code generation. The
best/cheapest aliases and embeddings remain OpenAI-hosted; this is
not an “all-local” mode. OPENAI_API_KEY remains mandatory to boot the
stack. This change makes the pipeline cheaper to run, not cheaper to
start.
The list of agents in the release note above describes the built-in pipeline as it existed when the change was made. That pipeline has since been replaced by catalogue agents; see Agents and workflows and Historical notes.
Release note (LiteLLM gateway). The default/coding LLM now has a hard
dependency on the litellm gateway container. A developer who previously
ran the stack with direct host Ollama must now also run the litellm
gateway container. The best and cheapest LLM aliases and embeddings
still call OpenAI directly (not proxied), so this is explicitly not an
“everything through one gateway” change. OPENAI_API_KEY remains mandatory
to boot the stack. Both revert paths (A and B above) are manual, never
automatic. The Micrometer/metrics provider tag for default-LLM calls changes
from the Ollama naming convention to the OpenAI naming convention now that
calls are proxied through LiteLLM’s OpenAI-compatible endpoint, so
/actuator/metrics series names/tags for that call path shift accordingly.
All LiteLLM gateway credentials listed in the variable table above are
local-testing-only.
docker compose down -vdocker compose stop/start and a plain docker compose down (without
-v) preserve the LiteLLM ricky admin account, the virtual key, and
usage history in the litellm Postgres database. docker compose down -v
additionally destroys the LiteLLM database state along with Keycloak state,
since the postgres service declares no separately-scoped named volume for
litellm data.