An existing, non-git directory is now a first-class source alongside
the git repos: one table (git_sources + kind discriminator — A13
reversible migration), one admin page, one Sync button (phase locked
decisions; the phase-35 table is extended, not duplicated). The DB is
the local-source registry — no env var for local paths;
BOR_GIT_SOURCES stays a git-only empty-table fallback.
Migration 0007 (reversible, up/down integration-tested):
git_sources.kind TEXT NOT NULL DEFAULT 'git' + ck_git_sources_kind
(kind IN ('git','local')); git_sources.path TEXT NULL +
uq_git_sources_path (mirrors 0006's uq_git_sources_url). Existing rows
read kind='git', path=NULL.
API (phase-35 contract extended, git byte-identical): POST kind=local
requires path — trimmed, ~-expanded, absolute + an existing server
directory, else 422 naming the path (fail loud at add-time); duplicate
path 409 (named); wrong field combos 422. GET rows carry kind + path
(git and env rows: path null); anonymous still 403 on every route (A10).
Sync + import_docs resolve DB git + local rows together: git →
clone_or_pull (unchanged); local → re-verified .is_dir() AT SYNC TIME
(it may have moved/deleted since add-time) — a missing dir raises
"local source missing: <path>" (sanitized) before anything imports;
one import_sources(..., prune=True) over the single combined list
(pruning covers the union). Both-empty fails loudly ("no sources
configured (git or local)"); --source still wins; the env fallback
stays git-only.
Page: second "Add a local directory" form (the same §7.4 never-stale
button + inline-error lifecycle as the git form; 422/409 details name
the path), Git/Local badges on rows (text + color, never color alone —
WCAG), updated hint (git + local together, union prune); the
anonymous sign-in gate is unchanged.
Tests: 0007 up/down; the API local-kind matrix (403/201/422/409) with
the git-kind suite green unchanged; the sync pipeline local/git/
mixed/missing against a host temp dir (the KB actually updated);
import_docs DB resolution + --source precedence. Story E2E (isolated,
deterministic across runs): add (Local badge) → missing path inline
422 naming it / duplicate 409 → the real Sync button imports the
fixture file (GET /api/docs + sentinel in its content) → file deleted
+ sync prunes it (union prune) → row removed; anonymous gate + 403s
(phase-35 regression). test_git_sources_admin.py (phase 35) green
UNCHANGED — no selector collision with the new form;
test_sync_button.py green.
Docs: README — the two managed kinds (git = clone/pull mirror; local =
direct in-place walk), add-time validation, union pruning, "the DB is
the local-source registry (no env var for local paths)";
.env.example — the env fallback is git-only.
80 lines
4.2 KiB
Bash
80 lines
4.2 KiB
Bash
# Brain of Reese — environment configuration
|
|
# Copy to `.env` and adjust: cp .env.example .env
|
|
# (`.env` is gitignored; never commit secrets.)
|
|
|
|
# --- App ---
|
|
BOR_ENVIRONMENT=development
|
|
# BOR_LOG_LEVEL=INFO
|
|
# BOR_STATIC_DIR=frontend # dev default; container sets /app/static
|
|
|
|
# --- Database (matches `podman compose` db service) ---
|
|
BOR_DATABASE_URL=postgresql+psycopg://reese:reese@localhost:5432/brain_of_reese
|
|
|
|
# --- LLM (self-hosted, OpenAI-compatible "aipi") ---
|
|
BOR_LLM_BASE_URL=https://aipi.reeseapps.com/v1
|
|
BOR_LLM_API_KEY= # falls back to $AIPI_KEY, then "not-needed"
|
|
BOR_LLM_CHAT_MODEL=turbo
|
|
BOR_LLM_EMBED_MODEL=embed
|
|
BOR_LLM_SUMMARY_MODEL=lite # one-shot completions: document summaries (phase 30), KB overview (phase 31)
|
|
BOR_EMBEDDING_DIM=768 # verified 2026-08 via scripts/llm_probe.py
|
|
BOR_STREAM_THINKING=1 # stream the model's thinking as `thinking` SSE events (0 to suppress)
|
|
|
|
# --- RAG tuning ---
|
|
BOR_TOP_N_DOCS=2
|
|
BOR_RELEVANCE_THRESHOLD=0.62 # answer when best cosine >= this OR an FTS hit; else honest deflection
|
|
BOR_MAX_OUTPUT_TOKENS=32768 # max answer length in tokens (answers must not be cut off)
|
|
BOR_STEERING_MAX_CHARS=8000 # char budget for the <tuning> (steering notes) prompt section
|
|
BOR_SUMMARY_MAX_CHARS=12000 # cap on document content sent to the lite summary model (phase 30)
|
|
BOR_KB_OVERVIEW_MAX_CHARS=4000 # char budget for the <knowledge_base> prompt section (phase 31)
|
|
BOR_OVERVIEW_INPUT_MAX_CHARS=40000 # cap on the document list sent to the lite model for the KB outline (phase 31)
|
|
BOR_CHUNK_TARGET_CHARS=2000
|
|
BOR_CHUNK_OVERLAP_CHARS=200
|
|
BOR_EMBED_BATCH_SIZE=16
|
|
|
|
# --- Hybrid retrieval (vector + Postgres FTS, RRF-fused) ---
|
|
BOR_HYBRID_VECTOR_CANDIDATES=100 # cosine list width for the fusion
|
|
BOR_HYBRID_LEXICAL_CANDIDATES=30 # FTS list width for the fusion
|
|
BOR_RRF_K=60 # Reciprocal Rank Fusion damping constant
|
|
|
|
# --- Agent document tools (phase 37: grounded turns may list + read) ---
|
|
BOR_AGENT_LIST_CALLS=1 # per-turn list_documents opportunities (0 disables the tool)
|
|
BOR_AGENT_READ_CALLS=1 # per-turn read_document opportunities (0 disables the tool)
|
|
|
|
# --- Import scope (A9 formats; may only narrow, never widen) ---
|
|
# BOR_IMPORT_EXTENSIONS=md,markdown,txt,yaml,yml,json,py
|
|
# BOR_SUGGESTIONS=["How is my Kubernetes cluster set up?"] # JSON list of onboarding chips
|
|
|
|
# --- Import sources (git; phase 28, admin-managed since phase 35) ---
|
|
# Comma-separated git repo URLs; import_docs clones each (first run) or
|
|
# pulls it (subsequent runs) into BOR_SOURCES_DIR/<repo-name>/ and indexes
|
|
# the result. Auth via URL (e.g. an https token) or SSH keys.
|
|
#
|
|
# Phase 35 (owner permission 2026-08-26): the PRIMARY way to manage the
|
|
# list is the admin Git sources page (http://localhost:8000/git-sources.html)
|
|
# — rows stored in Postgres (git_sources table, migration 0006). This
|
|
# variable is the EMPTY-TABLE FALLBACK: it only applies while the admin
|
|
# list is empty; once the page has stored any source, this variable is
|
|
# ignored (the page is the source of truth). Empty table + empty variable
|
|
# + no local rows = no sources (import_docs falls back to --source / the
|
|
# old ~/Homelab + ~/Deployments defaults; the UI Sync button fails loudly
|
|
# with "no sources configured (git or local)").
|
|
#
|
|
# Phase 38: this env fallback is GIT-ONLY. Local directory sources (an
|
|
# existing, non-git directory on the server) have NO env var — the DB is
|
|
# the local-source registry: add them on the same admin page (the
|
|
# "Add a local directory" form, kind 'local' + path, migration 0007).
|
|
# BOR_GIT_SOURCES=https://github.com/user/homelab.git,https://github.com/user/deployments.git
|
|
# BOR_SOURCES_DIR=~/bor-sources
|
|
|
|
# --- Admin & sign-in (single-admin password login; BOTH required) ---
|
|
# The app refuses to start while either is empty (names the missing
|
|
# variable(s) — README "Admin & sign-in"). Generate the secret with:
|
|
# python -c 'import secrets;print(secrets.token_hex(32))'
|
|
BOR_ADMIN_PASSWORD=
|
|
BOR_SESSION_SECRET=
|
|
# BOR_SESSION_MAX_AGE=43200 # signed-cookie lifetime, seconds (default 12 h, sliding)
|
|
|
|
# --- Debugging (0/1 — 1 enables attach-on-demand debugpy on port 5678) ---
|
|
DEBUGPY=0
|
|
# DEBUGPY_PORT=5678
|