Files
brain-of-reese/.env.example
T
ducoterra 7fce6572d0
Build and Push Containers / build-and-push-app (push) Successful in 1m45s
Build and Push Containers / build-and-push-db (push) Successful in 13s
feat: phases 77–80 — navbar view refresh, static background, API tokens, history suggestion chips
Single consolidated commit for four completed, validated phases (77, 78,
79, 80). The pipeline run left all work uncommitted because the harness
commits only with PHASE_COMMIT=1 while child executors are forbidden from
committing; the phases themselves all passed validation and moved to
.agents/phases/complete/.

Phase 77 — navbar view refresh
- router.js dispatches bor:view-refresh on re-show / active re-click /
  popstate (gated on wasMounted; first show and boot exempt)
- History / RAG / Sources / Tuning re-fetch on refresh (admin branch);
  Chat deliberately excluded (stream survival)
- History "Refresh" button (admin-only, in-flight disable + status line)
- New story suite tests/e2e/test_navbar_refresh.py (7 tests)

Phase 78 — static background
- Removed the animated glow layers; static 44px grid over the flat --bg
  canvas; default and reduced-motion renders byte-identical
- Updated background/theme E2E suites; removed bg-glow test pins

Phase 79 — API tokens
- api_tokens model + migration 0012; hash-only token service
- Admin tokens API + Tokens admin view; POST /api/token-auth;
  live-revoking require_user on chat / suggestions / document content
- Frontend token gate with localStorage cache; anonymous E2E suites
  migrated to token login
- New story suite tests/e2e/test_api_tokens.py (9 tests)

Phase 80 — history suggestion chips
- last_questions() endpoint with SEED fallback; startNewChat() refetch
- Seed-semantics docs (config.py, .env.example, README)
- Integration state matrix + E2E suite rewritten to the 4 chip states

Also included: phase-76 report artifacts and the repo restore-test-db
skill (previously untracked), scripts/* ruff fixes from phase 77.

Final gate state (phase 80 final pass, covers everything above):
- uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99%
- uv run ruff check . && uv run pyright → clean, 0 errors
- Per-phase story E2E suites green in isolation
2026-09-07 12:39:01 -04:00

111 lines
6.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_APP_NAME=Brain of Reese # display name on all pages — titles, header brand, status labels, aria text (phase 39)
# BOR_INPUT_PLACEHOLDER=Ask me anything… # composer placeholder, chat page (phase 62)
# BOR_FOOTER_TEXT=Powered by self-hosted models # footer line on every page (phase 62)
# BOR_THEME= # filename under frontend/assets/themes/ (e.g. indigo.css) — overrides the built-in palette; empty = built-in (phase 62)
# 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_RETRIES=3 # retry a dead LLM request before the first token lands (phase 67); 0 = off
# BOR_LLM_RETRY_DELAY=5 # seconds between LLM retries (phase 67)
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 (grounded turns may extend context: ls / read / grep) ---
# BOR_AGENT_MAX_ROUNDS=10 # hard cap on agent tool rounds per turn (0 = no tools)
# --- Import scope (A9 default; ANY well-formed extension is allowed) ---
# Comma-separated file extensions (lowercase, no dot) the importer reads.
# Any extension is allowed — the value below is the built-in default (the
# A9 family: the original seven + the quadlet family + jinja ``j2``); add
# your own (e.g. md,sh,toml) or narrow it (e.g. md). A blank list or a
# malformed token (e.g. md,sh!) fails startup loudly, naming the value.
BOR_IMPORT_EXTENSIONS=md,markdown,txt,yaml,yml,json,py,container,network,volume,image,pod,kube,swap,os,endpoint,j2
# BOR_SUGGESTIONS=["How is my Kubernetes cluster set up?"] # JSON seed chips — shown only before any question has been saved (phase 80)
# --- 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
# Phase 49: uploaded source archives (the Sources page upload form).
# An uploaded .tar / .tar.gz / .tgz / .zip is unpacked to
# BOR_UPLOAD_DIR/<name>/ where <name> is the filename minus the archive
# suffix (homelab.tar.gz -> homelab/). Re-uploading the same name
# replaces the folder's content IN PLACE — one folder, one row, no
# missing window; a failed upload never touches the existing folder,
# row, or KB. Deliberately separate from BOR_SOURCES_DIR (git checkouts).
# BOR_UPLOAD_DIR=~/bor-sources/uploads
# BOR_UPLOAD_MAX_MB=512 # caps BOTH the compressed upload and the total
# extracted bytes (zip-bomb guard); must be > 0
# --- Docs push (phase 59: save a chat answer as documentation) ---
# The git repo chat answers can be committed to — any remote (URL or
# local path). While empty, the "Save as doc" action is hidden and the
# push endpoint 409s. Commits land on BOR_DOCS_BRANCH (push --ff-only);
# open the PR yourself.
# BOR_DOCS_REPO=/path/to/docs-repo
# BOR_DOCS_BRANCH=bor-docs
# BOR_DOCS_BASE_BRANCH=main
# BOR_DOCS_WORK_DIR=~/bor-docs
# --- 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