Environment variables
Every setting the bot reads, what it defaults to, and when you would change it.
All of them live in one module — config.py — so a setting
that is not listed here does not exist.
Settings are read once, when the process starts. Changing .env while the
server is running changes nothing until it is restarted. kb-reload is the one
exception, and it reloads the knowledge base rather than the settings.
Copy .env.example to .env and edit that. .env is not committed.
What you must set
Only one, and only to answer questions.
| Variable | Default | Notes |
|---|---|---|
GROQ_API_KEY |
empty | Needed to write a reply. Empty is a real state, not an error: parsing, importing, embedding and evaluating all run without it. |
The pipeline — kb-parse, kb-db, kb-ingest, kb-eval — needs no key at
all. Embeddings run locally.
What you will probably change
| Variable | Default | Notes |
|---|---|---|
DECIPLE_EMBEDDING_MODEL |
BAAI/bge-small-en-v1.5 |
A HuggingFace id downloads from the Hub; a path like models/bge-small-en-v1.5 uses the local copy scripts/fetch_models.py puts there. Point it at the local copy on a machine where TLS interception breaks the download. |
DECIPLE_HANDOVER_TO |
empty | Who reads handover emails. No default on purpose — a wrong address here mails a member's question to a stranger. See Handover. |
DECIPLE_MAIL_FROM |
empty | Sender, e.g. Deciple <bot@yourdomain.com>. |
RESEND_API_KEY |
empty | Delivers the handover email over HTTPS. Empty means no email is attempted. |
DECIPLE_API_BASE_URL |
http://127.0.0.1:8000 |
Where kb-reload looks for a running server. Change it if you run uvicorn on another port. |
Where content and data live
| Variable | Default | Notes |
|---|---|---|
DECIPLE_DATABASE_URL |
sqlite:///data/kb.db |
The records the bot answers from. Any URL SQLAlchemy understands. |
DECIPLE_KB_DIR |
data/kb |
Where knowledge_base.csv is read from and written to. See Updating the knowledge base. |
DECIPLE_KB_ARCHIVE_DIR |
data/kb_archive |
The mentor's original markdown batches, read only by kb-csv. |
DECIPLE_CHROMA_DIR |
data/chroma |
The vector store. |
DECIPLE_EVAL_QUESTIONS |
data/eval/questions.jsonl |
The gold questions kb-eval scores against. |
MLFLOW_TRACKING_URI |
sqlite:///mlflow.db |
Where runs and traces are logged. See MLflow. |
Retrieval and the reply
| Variable | Default | Notes |
|---|---|---|
DECIPLE_SCOPE_FLOOR |
0.55 |
Cosine similarity below which a question is called off-topic and gets the canned scope reply. Raising it rejects more real questions; see the note below. |
DECIPLE_RERANK_MODEL |
models/ms-marco-MiniLM-L-6-v2 |
Cross-encoder that re-scores the shortlist. Local folder, no API key. |
DECIPLE_RERANK_POOL |
10 |
How many records the cross-encoder re-scores. |
DECIPLE_QUERY_INSTRUCTION |
Represent this sentence for searching relevant passages: |
Prepended to queries only, never to stored text. What the embedding model expects of a question. |
DECIPLE_MODEL |
groq/openai/gpt-oss-120b |
Full LiteLLM model id, provider prefix included. |
DECIPLE_LLM_TEMPERATURE |
0.3 |
Low, not zero — the only prose the model writes is the empathy sentences. |
DECIPLE_LLM_MAX_TOKENS |
1500 |
Far above what the answer needs, because reasoning tokens fill it. |
DECIPLE_LLM_REASONING_EFFORT |
low |
How much thinking gpt-oss does first. |
DECIPLE_LLM_NUM_RETRIES |
5 |
Retries on a transient LLM failure before the request becomes a 503. |
DECIPLE_DISCLAIMER |
"This is not medical advice…" | Carried on every reply, on every branch. |
DECIPLE_SUPPORT_PHONE |
empty | Offered alongside a handover. Empty is safe — the sentence is left out. |
DECIPLE_CRISIS_RESOURCES |
empty | Named 24/7 lines, e.g. "Samaritans on 116 123". |
TOP_K (4 records shown to the model) and COLLECTION_NAME (kb_records) are
constants in config.py, not environment variables. They change retrieval
behaviour that the eval numbers were measured against, so changing them is a
code change with a re-run attached.
On raising DECIPLE_SCOPE_FLOOR. It is one absolute threshold on the best
cosine score, and it answers "is anything near this question?" — not "is what I
found the right thing?" Measured across all 64 linked lessons, deleting a
lesson still left its gold question above the floor in 64 cases out of 64. At
0.75 the floor would catch 91% of those, and reject 55% of correct answers.
It is the wrong dial for that problem.
The server
| Variable | Default | Notes |
|---|---|---|
DECIPLE_CORS_ORIGINS |
http://localhost:5173,http://127.0.0.1:5173 |
Origins the browser may call the API from. Listed explicitly rather than *. |
DECIPLE_UPLOAD_LIMIT |
2097152 |
Largest upload accepted, in bytes. Two megabytes. |
DECIPLE_LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING or ERROR. |
Disciple
All four are empty by default, and an empty DECIPLE_DISCIPLE_BASE_URL means
the adapter is off. See Disciple.
| Variable | Notes |
|---|---|
DECIPLE_DISCIPLE_BASE_URL |
Root of the Disciple API. Empty turns the adapter off. |
DECIPLE_DISCIPLE_API_KEY |
Reads posts and writes comments as the bot. |
DECIPLE_DISCIPLE_BOT_AUTHOR |
The bot's own author id, so it never answers itself. |
DECIPLE_DISCIPLE_WEBHOOK_SECRET |
Shared secret the incoming webhook is signed with. |
Front end
The chat reads one variable, and it is a Vite variable rather than a
DECIPLE_ one. Vite only exposes names beginning VITE_ to the browser.
| Variable | Default | Notes |
|---|---|---|
VITE_API_URL |
http://localhost:8000 |
Set in web/.env. A web/.env.local overrides it and is not committed — check both when the chat cannot reach the API. |