Quickstart¶
Lorebase is pre-alpha. This page takes you from a fresh clone to a running Lorebase with the
sample supply Domain loaded, the web app open, a first query answered, an agent connected over
MCP and a model answering, then shows how to stop and clean up. Every command runs from the
repository root unless the step says otherwise.
| Step | What you get | Time measured on a fresh clone |
|---|---|---|
| 2 | Postgres, Keeper, the web app, the sample Domain, a first query | 1.5 minutes measured; a few minutes more on a machine that has never pulled the base images |
| 3 | An agent reading the Domain through the MCP server (Herald) | under a minute |
| 2.7 | Guide and Scribe with make up PROFILES=assistant: query embeddings, semantic search, schema suggestions from the example documents |
a few minutes for the second image |
| 4 | A chat model for document extraction: Ollama, or a hosted endpoint | seconds with Ollama already on the machine or a hosted endpoint; about 11 minutes the first time for Ollama in Docker |
| 5 | Stop the stack, keep or delete its data | seconds |
| 6 | What to do when a step fails | |
| Manual setup | The same path step by step: the core engine without services, tests, compose by hand, the REST API with curl and the CLI, the web app in mock mode |
The measured times of each command are in Timings and in the fresh-clone check.
1. Prerequisites¶
| Tool | Version | Check | Needed for |
|---|---|---|---|
| git | any recent | git --version |
the clone |
| uv | a recent release; it installs Python 3.12 by itself | uv --version |
the CLI, Herald, the tests |
| Docker with the compose plugin | compose v2 with up --wait and --profile "*" (checked with Docker 29 and compose 5.1) |
docker compose version |
Postgres, Keeper, Scribe, Ollama |
| Node.js and pnpm | Node 24 or later; pnpm 11 (corepack enable installs the version of apps/web/package.json) |
node --version, pnpm --version |
only the web app dev server (pnpm dev, contributors); make up serves the web app from a container |
| curl, openssl, make | any | the helper commands |
Resources: give Docker at least 4 GB of memory (8 GB with the Ollama container), and keep
about 3 GB of disk free for the images (10 GB more for the Ollama image and a model). The
stack publishes these ports on 127.0.0.1 only:
| Port | Service | Variable in .env |
|---|---|---|
| 8080 | Keeper (REST API) | KEEPER_HOST_PORT |
| 5432 | Postgres | POSTGRES_HOST_PORT |
| 11434 | Ollama (profile local-models) |
OLLAMA_HOST_PORT |
| 8090 | web app (container started by make up) |
LOREBASE_WEB_PORT |
| 8095 | Guide (profile assistant) |
GUIDE_HOST_PORT |
| 8091 | Herald, Streamable HTTP (profile mcp) |
HERALD_HOST_PORT |
| 8088, 5433 | load balancer in front of the Keepers and read replica (profile scale-out, deployment) |
PROXY_HOST_PORT, POSTGRES_REPLICA_HOST_PORT |
| 5173 | web app dev server (pnpm dev, contributors) |
picks the next free port by itself |
make up checks every port the chosen profiles publish before it starts anything and names the
variable to change (lsof -nP -iTCP:8080 -sTCP:LISTEN shows who holds a port); if one is taken,
change the variable in .env after step 2.2. It also refuses to start while a *_BIND value in
.env would publish the Keeper of this stack, which runs with authentication off, beyond this
machine (KEEPER_PUBLISH_INSECURE=true, in .env or the shell, starts it anyway). The compose project name comes from
COMPOSE_PROJECT_NAME in .env (default lorebase): give a second checkout on the same
machine another name, so that the two stacks never share containers or volumes.
2. Five-minute path¶
2.1 Clone and install¶
uv sync installs Python 3.12 if needed and every workspace package, among them the lorebase
command line and Herald.
2.2 Create the environment file¶
It copies .env.example to .env and fills POSTGRES_PASSWORD and LOREBASE_APP_PASSWORD
with random values (openssl rand -hex 24), and LOREBASE_WEBHOOK_KEY, the key that seals
webhook signing keys, with openssl rand -hex 32; an existing .env keeps its values. Without
make: cp .env.example .env, then set the three yourself (URL-safe characters only for the
passwords: they are embedded in connection strings). The other values have working defaults; this is where you
change ports and COMPOSE_PROJECT_NAME.
2.3 Start Postgres, Keeper and the web app¶
This runs docker compose --env-file .env -f deploy/compose/docker-compose.yaml up -d --build
--wait postgres migrate keeper web. The first run builds the backend and web images (a few
minutes; later runs reuse the layers). Compose starts Postgres 16, creates the application role Keeper connects
as (db-roles), runs the migrations once (migrate exits when done), starts the web app and returns when Keeper
and the web app are healthy. It ends by printing the Keeper URL (Keeper is ready at http://127.0.0.1:8080 with the
default KEEPER_HOST_PORT), the web app URL (http://127.0.0.1:8090) and the export line that
points the CLI at them (make shell-env prints it again). Point the commands of this page at that
Keeper and check it:
eval "$(make shell-env)" # export LOREBASE_URL=http://127.0.0.1:<KEEPER_HOST_PORT> LOREBASE_DOMAIN=supply, the line `make up` printed
curl "$LOREBASE_URL/readyz"
$LOREBASE_URL/api/v1/docs serves the interactive OpenAPI docs. The web app answers on
http://127.0.0.1:8090, and http://127.0.0.1:8090/api/v1/auth/whoami reaches Keeper through it.
Credentials. There are none to type. The example configuration
(config/lorebase.example.yaml) sets keeper.auth.mode: none: Keeper answers every request as
an anonymous installation administrator, and the web app needs no sign-in. That is for a laptop
only: compose publishes the port on 127.0.0.1 and Keeper refuses that mode on any other
address. The database passwords are the ones in .env; you never need them for the steps on
this page. Turning authentication on is in
The first administrator key.
2.4 Load the example data¶
[quickstart] creating Domain supply (required_approvals 0)
[quickstart] applying the supply schema to branch work
[quickstart] importing the sample data
batch 1/1: rows 1-426 committed, 426 edits
[quickstart] merging Proposal prp_... into main
[quickstart] loaded the supply sample into Domain supply (branches main and work)
It uses the lorebase CLI against the Keeper of .env: creates the Domain supply, applies
examples/supply/schema.yaml
to a branch work, imports the CSV files of examples/supply/data (185 Records with their
Links), opens a Proposal from work to main and merges it. main is protected, so changes
reach it only through a Proposal; the quickstart Domain is created with required_approvals: 0
so that you, the only principal of a stack without authentication, can merge your own
Proposal. Real Domains keep the default of one approval from someone else. Running it again
does nothing once the sample is on main.
2.5 First query¶
# LOREBASE_URL and LOREBASE_DOMAIN=supply as exported in step 2.3
uv run lorebase record list Part -n 5 \
--where '{"attr": "category", "op": "eq", "value": "bearing"}' --select category --select unit_cost
uv run lorebase search bearing --kind Part
key title category unit_cost
P-10001 Tapered roller bearing 6305 bearing 10.79
P-10010 Needle roller bearing 6305 bearing 40.48
P-10013 Needle roller bearing NK25 bearing 25.62
P-10023 Needle roller bearing NK25 bearing 37.59
P-10029 Needle roller bearing 6001 bearing 45.94
The same over REST, with no CLI:
curl -s -X POST "$LOREBASE_URL/api/v1/domains/supply/query" \
-H 'Content-Type: application/json' \
-d '{"kind": "Part", "aggregate": {"group_by": "category"}}'
The answer lists the Parts and ends with "aggregate":{"counts":[{"kind":"Part","value":"bearing","count":6}, ...]}.
Reads never call a model; every answer names the Revision it was served from in the
Lorebase-Revision header. Manual setup
goes through the API one call at a time.
2.6 Open the web app¶
make up already started it. Open http://127.0.0.1:8090 (LOREBASE_WEB_PORT in .env). The
app opens on the supply Domain and its main branch: Explorer lists the six Kinds, Canvas
draws the graph around a Record, Review lists Proposals (the one that loaded the sample is under
the Merged filter), Reconciliation lists the values on which Sources disagree. There is no sign-in with authentication off; with an API key, paste it in
Settings.
The page and the API share one origin: the web service of the compose file
(deploy/docker/web.Dockerfile, nginx) serves the built app and forwards /api/ to Keeper on
the compose network, event streams included, so Keeper needs no CORS setting. The Content
Security Policy allows connections to that origin only. With make up PROFILES=assistant
(2.7) /guide/ is forwarded to Guide the same way.
The web
settings are in .env.example;
apps/web/README.md
lists the variables of the app itself, and a mock mode with built-in sample data and no Keeper is
in Manual setup.
Browser sign-in (WEB_AUTH_MODE=session) can be tried on this stack with a mock OpenID
Connect provider, the overlay deploy/compose/oidc-mock.yaml (it signs in anyone: local use
only). Add a session key to .env, then start Keeper, the web app and the provider with it:
echo "LOREBASE_SESSION_KEY=$(openssl rand -base64 48)" >> .env
docker compose --env-file .env -f deploy/compose/docker-compose.yaml \
-f deploy/compose/oidc-mock.yaml up -d --wait keeper web oidc-mock
Keeper switches to auth.mode: oidc with auth.sessions (client lorebase-web, PKCE, callback
http://127.0.0.1:8090/api/v1/auth/callback) and the app to session mode. Open
http://127.0.0.1:8090 and sign in: the provider's page asks for any user name (and optional
claims such as {"name": "Alice"}), then returns to the app with a session cookie. The provider
runs in Keeper's network namespace and is published on 127.0.0.1:8096 (OIDC_MOCK_PORT), so
the browser and Keeper use the same issuer URL, http://localhost:8096/lorebase. The user is
created at its first sign-in (keeper.auth.oidc.auto_provision) and, like any principal, reads
what its grants allow. To go back to authentication off, remove the provider (the same command
with rm -sf oidc-mock instead of up ...) and run make up. Settings and cookies:
deployment.
Contributors who change the app run the dev server instead (Node.js 24 and pnpm), with hot reload, on the Keeper of the stack:
cd apps/web
pnpm install
printf 'VITE_API_MODE=live\nVITE_KEEPER_BASE_URL=/\n' > .env.local
LOREBASE_KEEPER_PROXY="$LOREBASE_URL" pnpm dev # the Keeper URL of step 2.3
Open the URL it prints (http://localhost:5173, or the next free port). The dev server forwards
/api to Keeper, so no CORS setting is needed here either.
2.7 Guide, Scribe and the example documents¶
Guide (the exploration assistant, /embed for query vectors) and Scribe (the worker: extraction,
embeddings) are not part of the plain make up. Start them with the assistant profile:
It builds a second image, lorebase-ml (target backend-ml of the backend Dockerfile: the
backend image plus fastembed and the embedding model BAAI/bge-small-en-v1.5, downloaded at
build time so nothing is fetched at run time; 844 MB against 453 MB for the backend image, which
is why Keeper, the migrations and Herald stay on the small one), starts Guide and Scribe next to
the default services, and adds the overlay deploy/compose/assistant.yaml: the web app learns
its Guide at /guide and Guide accepts calls without credentials, which is only right while
Keeper runs with authentication off as here. make down stops every profile make up can
start. EMBEDDING_MODEL in .env names another model to bake in (empty: none, and the model is
downloaded on first use into a writable inference.embeddings.cache_dir).
Semantic search. The supply schema gives Part and Supplier an embedding template, so
Scribe embeds them on its next pass (every scribe.scheduler.embeddings_interval_s, 60 s).
Guide embeds the query, and the vector goes to the search:
curl -s http://127.0.0.1:8090/guide/embed -H 'Content-Type: application/json' \
-d '{"text": "protects a rotating shaft from oil leaks", "mode": "query"}' \
| python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["vector"]))' > "${TMPDIR:-/tmp}/query.json"
uv run lorebase search "protects a rotating shaft from oil leaks" --kind Part \
--mode semantic --vector-file "${TMPDIR:-/tmp}/query.json" -n 3
kind key title score snippet
Part P-10028 Radial shaft seal 25x42x7 0.732 Radial shaft seal 25x42x7 Stock item for machine tool spind...
The first hits are radial shaft seals although the query shares no word with the name. /embed
needs no chat model. Guide's questions (/explore, the Explore panel) do: set LLM_* as in
section 4.
Schema suggestions. Extraction reports what the documents hold that the schema has no place
for, and lists a suggestion once schema.suggestions.min_support observations (2) support it. A
single document supports each of its suggestions once, so uploading only the contract C-3099
lists nothing (lorebase schema suggestions list --weak shows the one it made, an Attribute
buyer of Contract). The step below uploads that contract and the four documents of
examples/supply/documents, rules tier only, so it needs no model, and prints the suggestions:
id type name of support docs status samples
ssg_... attribute ends Supplier 3 1 pending 2026-02-07, 2026-02-09, 2026-04-12
ssg_... relation sole_source_parts Supplier 3 1 pending
ssg_... attribute buyer Contract 2 2 pending Corvane Industries, purchasing entity of the Distribution d...
buyer is supported by two documents (C-3099 and C-3025), ends and sole_source_parts by the
three rows of the supplier review. The contract C-3099 also opens a Proposal with its new
Contract Record; the other documents describe Records that make example-data loaded. Review
the suggestions in the web app (Review, Schema suggestions) or with
uv run lorebase schema suggestions accept ID; see extraction.
3. Connect an agent over MCP¶
Herald, the MCP server, turns the Domain into tools an MCP host can call (lorebase_search,
lorebase_get, lorebase_query, lorebase_context and more, listed in mcp.md).
It runs from the checkout; the host starts it over stdio. Add this entry to the MCP
configuration of your host (most hosts read an mcpServers map; mcp.md
has the file for each host), with the absolute path of your clone and the Keeper URL make up
printed:
{
"mcpServers": {
"lorebase": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/lorebase", "run", "lorebase-herald"],
"env": {
"LOREBASE_URL": "http://127.0.0.1:8080",
"LOREBASE_DOMAIN": "supply"
}
}
}
}
Restart the host and ask it, for example, "which parts depend on a single supplier?". With
authentication off no key is needed. Once authentication is on, give the agent its own key with
only the grants it needs, and keep it out of the file with
"LOREBASE_API_KEY": "secret://file//absolute/path/to/agent.key"
(mcp.md).
To check Herald without a host, list its tools and run one search from Python (the mcp
package is installed by uv sync):
LOREBASE_DOMAIN=supply uv run python - <<'EOF' # LOREBASE_URL as exported in step 2.3
import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server = StdioServerParameters(command="uv", args=["run", "lorebase-herald"], env=dict(os.environ))
async with stdio_client(server) as (read, write), ClientSession(read, write) as session:
await session.initialize()
print([tool.name for tool in (await session.list_tools()).tools])
result = await session.call_tool("lorebase_search", {"query": "bearing", "limit": 2})
print(result.content[0].text)
asyncio.run(main())
EOF
Herald logs its requests to Keeper on standard error; standard output shows:
['lorebase_schema', 'lorebase_get', 'lorebase_query', 'lorebase_search', ...]
rev bb30d34bebf7... (lexical): 2 hits
1. Part/P-10001 "Tapered roller bearing 6305" (2.4) - Tapered roller <b>bearing</b> 6305 ...
For agent frameworks that speak Streamable HTTP, run one shared Herald with
LOREBASE__HERALD__TRANSPORT=streamable_http (mcp.md), or start it in the
compose stack:
Agents send Authorization: Bearer <api key>; with the stack's authentication off, set
HERALD_ALLOW_ANONYMOUS=true in .env to let them call without one, and HERALD_DOMAIN=supply
so that calls naming no domain read the sample Domain (without it every call must pass
domain) (mcp.md).
4. Use a real model¶
Reads never call a model. Models are used by Scribe, the worker, to extract documents into
Proposals (extraction.md). Lorebase talks to any OpenAI-compatible endpoint,
selected by three variables of .env: LLM_BASE_URL, LLM_MODEL (default: OLLAMA_MODEL) and
LLM_API_KEY. inference-check sends one short prompt and prints the model, the latency and
the token counts. Pick one of the three options.
Ollama in Docker (the default). The local-models profile starts Ollama and pulls
OLLAMA_MODEL. The image is about 7 GB and the default model qwen2.5:7b-instruct 4.7 GB; it
runs on the CPU inside Docker. A small model is enough to try it:
# in .env: OLLAMA_MODEL=qwen2.5:1.5b-instruct (and OLLAMA_HOST_PORT=11435 if Ollama already runs on the host)
docker compose --env-file .env -f deploy/compose/docker-compose.yaml \
--profile local-models run --rm inference-check
chat ok provider=openai_compatible model=qwen2.5:1.5b-instruct latency_ms=36015 tokens=30/9 structured_output=json_schema
The model download prints nothing while it runs; follow it with
docker compose --env-file .env -f deploy/compose/docker-compose.yaml logs -f ollama-pull.
Ollama already on your machine (faster on a Mac, where Docker has no GPU). Point the stack at
it and skip the Ollama container with --no-deps:
ollama pull qwen2.5:7b-instruct
# in .env: LLM_BASE_URL=http://host.docker.internal:11434/v1 and LLM_MODEL=qwen2.5:7b-instruct
docker compose --env-file .env -f deploy/compose/docker-compose.yaml run --rm --no-deps inference-check
Any instruct model works. Prefer one that answers without a long reasoning phase: a reasoning
model such as qwen3:4b passes the check but can run past the 120-second request timeout on a
whole document.
On Linux, Ollama must listen beyond loopback for containers to reach it (OLLAMA_HOST=0.0.0.0).
A hosted provider. Any OpenAI-compatible endpoint works. Keep the key in a file, never in
.env or on the command line: secrets/ is ignored by git, and the overlay
deploy/compose/hosted-model.yaml mounts the file as a compose secret that the services read
through secret://file//run/secrets/llm_api_key.
mkdir -p secrets && (umask 077 && cat > secrets/llm_api_key) # paste the key, then Ctrl+D
# in .env: LLM_BASE_URL=https://api.example-provider.com/v1, LLM_MODEL=<model name>, LLM_API_KEY empty
docker compose --env-file .env -f deploy/compose/docker-compose.yaml \
-f deploy/compose/hosted-model.yaml run --rm --no-deps inference-check
Providers that do not speak the OpenAI protocol go through the LiteLLM proxy (profile litellm);
per-task routing, prices and embeddings are in inference-providers.md.
Keep the overlay for make. COMPOSE_OVERLAYS lists extra compose files, separated by
spaces and relative to the repository root, that make up, make logs, make down,
make clean-stack and scripts/quickstart.sh add after their own. Set it in .env so that every
command starts Scribe with the hosted key:
Your own overlay (another secret, a model setting of your own) goes next to the key, under the
ignored secrets/ directory, and is listed after it:
COMPOSE_OVERLAYS=deploy/compose/hosted-model.yaml secrets/my-model.yaml. A listed file that does
not exist stops the command before compose runs. COMPOSE_OVERLAYS=... make up on the command line
overrides .env for one run.
Extract a document. Start Scribe with the same files (make up PROFILES=assistant starts it
with the files of COMPOSE_OVERLAYS; by hand, add -f deploy/compose/hosted-model.yaml for a
hosted key) and upload a contract that the sample does not hold yet; tier 0 uses rules
only, tier 2 adds the chat model:
docker compose --env-file .env -f deploy/compose/docker-compose.yaml up -d --no-deps scribe
uv run lorebase documents upload examples/five_day_demo/documents/contract-c-3099-wexrix-filtration.md --tiers 0,2
uv run lorebase documents list # then: uv run lorebase documents get <id>
uv run lorebase proposal list --status open
When the job has succeeded, the extraction is an open Proposal from branch extract/<job> to
main, with one item per Record, value and Link and the evidence of each (here the Contract
C-3099 and its with_supplier Link). Review it under Review in the web app, or with
uv run lorebase proposal items <id> and uv run lorebase proposal decide. A value a document
states differently from the current one is also held for review as a reconciliation candidate
(Reconciliation in the web app, uv run lorebase reconcile list; Scribe). The documents of
examples/supply/documents describe Records that make example-data already loaded, so their
jobs succeed with no items and open no Proposal. With lfm2.5:8b on the host the job took 91
seconds; a small model on the CPU inside Docker takes several minutes.
5. Stop and clean up¶
make logs # follow the logs (SERVICES="scribe guide" narrows them); Ctrl+C stops
make down # stop the containers of every profile `make up` can start, keep the data
make clean-stack # stop them and delete the volumes: database, pulled models
make clean-stack runs docker compose ... --profile "*" down -v --remove-orphans, so the
Ollama and observability containers go too. (If you started the contributor dev server, stop it with Ctrl+C.) To remove the built images as
well: docker image rm lorebase:dev lorebase-ml:dev lorebase-web:dev (the LOREBASE_IMAGE,
LOREBASE_ML_IMAGE, LOREBASE_WEB_IMAGE and LOREBASE_TAG of .env). Delete the clone and nothing else is left on the machine.
6. Troubleshooting¶
| Symptom | Cause and fix |
|---|---|
port 5432 on 127.0.0.1 (POSTGRES_HOST_PORT) is already in use from make up, or Bind for 127.0.0.1:5432 failed: port is already allocated from compose run by hand (or 8080, 8090, 8091, 8095, 11434) |
Another Postgres, service or Ollama holds the port. Set POSTGRES_HOST_PORT, KEEPER_HOST_PORT, LOREBASE_WEB_PORT, HERALD_HOST_PORT (profile mcp), GUIDE_HOST_PORT (profile assistant) or OLLAMA_HOST_PORT in .env, run make up again, and use the Keeper URL it prints in LOREBASE_URL (and LOREBASE_KEEPER_PROXY of the dev server). |
refusing to start: KEEPER_BIND=0.0.0.0 publishes Keeper beyond this machine (or WEB_BIND, GUIDE_BIND, HERALD_BIND) |
The stack runs with authentication off, so make up keeps its ports on 127.0.0.1. Keep the *_BIND values there, turn authentication on first (KEEPER_ALLOW_INSECURE=false and an auth mode, manual setup), or set KEEPER_PUBLISH_INSECURE=true to publish it anyway. |
required variable POSTGRES_PASSWORD is missing a value, set LOREBASE_APP_PASSWORD in the environment file or set LOREBASE_WEBHOOK_KEY in the environment file |
.env is missing or a password or the webhook key is empty: make env fills them. |
Keeper unhealthy, make up fails with dependency failed to start |
docker compose --env-file .env -f deploy/compose/docker-compose.yaml logs migrate keeper names the error. A password changed after the first start does not change the database: run make clean-stack (deletes the data) and make up. |
curl: (52) Empty reply from server right after up -d without --wait |
Keeper is still starting; make up waits for its health check. |
| The stack runs code older than the checkout | The image tag lorebase:dev is shared by every checkout on the machine. Compose rebuilds the images from the checkout on every up (LOREBASE_PULL_POLICY=build, the default; docker compose ... build builds them alone); check that .env does not set LOREBASE_PULL_POLICY=missing, or set LOREBASE_TAG per checkout. |
migrate fails with the database is at revision ..., which this code does not know |
A newer Lorebase migrated this database and older code is running. Rebuild from the newer checkout (make up, or docker compose ... build), or make clean-stack to start over without the data (deployment). |
| Containers or volumes of another Lorebase stack are reused or replaced | Both use the project name lorebase. Set COMPOSE_PROJECT_NAME in .env. |
docker compose hangs on pull or build with a credential helper error |
The Docker credential helper is stuck. Run the command with an empty Docker config: DOCKER_CONFIG=$(mktemp -d) make up (public images need no login). |
no configuration file provided: not found |
The command left out -f deploy/compose/docker-compose.yaml, or did not run from the repository root. |
make example-data: Domain supply exists ... but main does not hold the sample |
An earlier load stopped half way, or the Domain was created by hand (manual setup). uv run lorebase domain delete supply -y, then make example-data. |
make example-data: no Keeper at http://127.0.0.1:8080 |
The stack is not running, or Keeper runs on another port: make up, or LOREBASE_URL=http://127.0.0.1:PORT make example-data. |
| The web app shows "This Branch has no Kinds yet" | It is on a branch without the sample: pick main or work in the top bar, or run make example-data. |
The web app at http://127.0.0.1:8090 shows a network error or 502 Bad Gateway on /api/ |
Keeper is not up (or restarting): docker compose --env-file .env -f deploy/compose/docker-compose.yaml ps keeper web and logs keeper web. The web service reaches Keeper at keeper:KEEPER_PORT, so KEEPER_PORT must be the port Keeper listens on. |
| The web app does not load, or the browser blocks requests (CSP) | Open it at the address make up printed (LOREBASE_WEB_PORT), not through another host name or a tunnel: the policy allows connections to the origin that served the page. Add other origins with LOREBASE_WEB_CSP_CONNECT_SRC on the web service. |
| The assistant (Guide) does not answer from the web app | Start it with make up PROFILES=assistant. A 502 on /guide/ means Guide is not running. With plain compose, add the profile and -f deploy/compose/assistant.yaml, or set WEB_GUIDE_URL=/guide and GUIDE_ALLOW_ANONYMOUS=true in .env. |
/guide/embed answers 502 |
The service runs on the plain backend image: build the lorebase-ml image (make up PROFILES=assistant does) or point inference.embeddings at an OpenAI-compatible endpoint. |
make example-documents waits and gives up |
Scribe is not running: make up PROFILES=assistant. |
| Schema suggestions are empty after one upload | A pending suggestion is listed from schema.suggestions.min_support (2) observations: run make example-documents, or lorebase schema suggestions list --weak. |
The contributor dev server (pnpm dev) cannot reach Keeper |
LOREBASE_KEEPER_PROXY must be set when pnpm dev starts and must name Keeper's port; .env.local must hold VITE_API_MODE=live and VITE_KEEPER_BASE_URL=/. Restart pnpm dev after a change. |
| The MCP host shows no Lorebase tools | The host could not start uv: give its absolute path (which uv) in command, and the absolute path of the clone after --directory. Check Herald with the script of section 3. |
Herald answers [not-found] |
LOREBASE_DOMAIN names a Domain that does not exist, or LOREBASE_BRANCH a branch without the Record. |
inference-check: connection refused or timeout |
Ollama is not reachable from the containers: in Docker the address is http://ollama:11434/v1 (profile local-models), on the host http://host.docker.internal:11434/v1 with --no-deps. |
An extraction job fails with EndpointError: /chat/completions: request failed (ReadTimeout) |
The model needs more than inference.llm.timeout_s (120 s) per call: use a smaller or non-reasoning instruct model, or raise the timeout in a copy of config/lorebase.example.yaml named by LOREBASE_CONFIG_FILE in .env. |
inference-check: model ... not found |
The model is not pulled: ollama pull NAME on the host, or wait for ollama-pull (docker compose ... logs -f ollama-pull). |
inference-check with the hosted overlay: bind source path does not exist: .../secrets/llm_api_key |
Create secrets/llm_api_key, or point LLM_API_KEY_FILE at the key file (relative paths start from deploy/compose). |
| A step is slow the first time | The backend image build, the Ollama image (about 7 GB) and model downloads happen once; Timings shows what to expect. |
Timings¶
Measured on 2026-10-22 on an Apple silicon laptop (Docker Desktop with Docker 29.5 and compose
5.1, 15 CPUs and 7.7 GB of memory for Docker), from a fresh copy of the repository, following
this page. The Docker build cache and the pgvector and Python base images were already on the
machine; a machine that has never pulled them spends a few more minutes in make up.
| Step | Command | Time |
|---|---|---|
| 2.1 | git clone |
6 s |
| 2.1 | uv sync --all-packages, empty uv cache |
29 s (1 s with a warm cache) |
| 2.2 | make env |
under 1 s |
| 2.3 | make up (backend and web images, Postgres, migrations, Keeper and web healthy) |
38 s (the web image adds a pnpm install and a build) |
| 2.4 | make example-data |
5 s |
| 2.5 | first query, CLI and curl | 1 s |
| 2.6 | open the web app (already running); contributor path: pnpm install, empty store; pnpm dev until ready |
0 s; 11 s; 2 s |
| 3 | Herald check script (start, list tools, one search) | 3 s |
| 4 | inference-check, Ollama in Docker, first time (7 GB image, qwen2.5:1.5b-instruct, CPU) |
645 s; 209 s with the image already pulled |
| 4 | inference-check, Ollama on the host (lfm2.5:8b) |
9 s |
| 4 | inference-check with the hosted overlay and a key file |
5 s |
| 4 | extraction of contract C-3099, tiers 0 and 2, lfm2.5:8b on the host |
91 s; tier 0 alone under 1 s |
| 5 | make down; make clean-stack |
1 s; under 1 s |
make up PROFILES=assistant adds the lorebase-ml image: 844 MB against 453 MB for the backend
image (fastembed with ONNX Runtime, and a 65 MB model).
The five-minute path (2.1 to 2.6) took about 80 seconds end to end.
Manual setup¶
The steps that used to follow here are on their own page, Manual setup:
- Run the core demo, with no service and no network;
- Run the tests;
- Start Postgres and Keeper by hand, and the first administrator key;
- Create a Domain and apply the supply schema with curl and the CLI;
- Import the sample data and read it back;
- The web app in mock mode.
Next steps¶
- Model your own Domain: the modeling guide explains every field of the schema with a complete example.
- Decide who approves what and what may merge on its own: governance and ingestion.
- Plug in an agent: examples/agent reads the
supplyDomain through Herald and proposes changes as Proposals, with Ollama or a hosted model.
Coming next¶
The roadmap tracks what is planned, milestone by milestone.