# Brain of Reese — Master Plan (minimal) > **Status:** Minimal re-land (2026-09-10). Phases 01–92 shipped; > `todo/` holds 93–95 (see §12). Section numbers and anchor IDs match the > previous full plan (in git history, commit `dac4a3e`) so the codebase's > `PLAN §…` / anchor comments stay valid — consult that revision or the > phase records in `.agents/phases/complete/` for full detail and the > complete owner-permission revision log. > > **Rule:** Every agent reads this file first. `LOCKED` anchors in §2 are > settled — a change requires explicit owner permission, recorded as a > dated revision note under the table. Never a silent deviation > (AGENTS.md rule 3). > > **This is deliberately minimal:** it captures the tech stack, the > architecture an agent needs to work, and the locked decisions. It does > NOT recap the shipped work — the git log, the README, and the completed > phase directories are the history. --- ## 1. Mission A **knowledge-base chatbot** ("Brain of Reese") over the owner's homelab documentation — git repos, local directories, and uploaded archives registered on the admin Sources page — embedded into Postgres + pgvector. Product feel: chippy, upbeat, and **radically honest** — if retrieval surfaces nothing relevant it says *"I haven't done anything like that"* and offers alternatives instead of hallucinating. Self-hosted LLMs only. **Deliberately out of scope:** binary/non-text ingestion; real-time file watching (the import script / Sync button is the refresh loop); PR tooling for docs push (the owner opens the PR); multi-admin/per-user accounts (one admin + hand-out tokens is the model). --- ## 2. Tech Stack & Architectural Anchors (LOCKED) | # | Component | Decision | |---|-----------|----------| | A1 | Runtime | Python 3.12+, `uv` for all package management (API + scripts + tests, one venv) | | A2 | Web framework | FastAPI + Pydantic v2 + Uvicorn (async, SSE-friendly) | | A3 | Database | **PostgreSQL 17 + pgvector** (local build of official `postgres:17`, `db/Containerfile`), cosine (`<=>`) search; one system for relational + vectors | | A4 | Orchestration | `compose.yaml`, `podman compose up -d` (dev: `db` only; `--profile prod` adds the app container) | | A5 | LLM backend | OpenAI-compatible self-hosted endpoint `https://aipi.reeseapps.com/v1` via the `openai` async client: **`turbo`** (chat, streams `reasoning_content` thinking), **`embed`** (embeddings), **`lite`** (one-shot: document summaries, KB overview; phase 94 adds sync-time folder summaries) | | A6 | Embedding dim | **768** (verified against the live endpoint); `chunks.embedding` is fixed at table creation — a dim mismatch must **fail loudly**, never silently re-embed | | A7 | Retrieval→context | **Hybrid:** cosine top-100 ∪ Postgres FTS top-30 (OR tsquery, `ts_rank`), fused with **RRF (k=60)** → parent docs ranked by best fused chunk → **full text of top-N=2 documents, never truncated** on the retrieval path (owner 2026-08-24: "this should never happen"; the agent `read`-tool cap is a separate owner-permitted path — phase 95) | | A8 | Honesty gate | Deflect (LOW mode) **only when** best cosine < `BOR_RELEVANCE_THRESHOLD` (0.62) **and** zero FTS hits; LOW prompt carries weak-hit *titles only* + the `DEFLECT_MODE` marker (the E2E mock keys on its presence) + the plain-text no-tools line | | A9 | Content scope | Default `md, markdown, txt, yaml, yml, json, py` + Podman quadlet family + `j2`; `BOR_IMPORT_EXTENSIONS` may name **any** well-formed extension or narrow the set; hidden (dot) paths + exclusion list + per-source `ignore_paths` (raw prefixes, no globs) always apply | | A10 | State & auth | `POST /api/chat` is **stateless** (client-provided `history` only, budget-trimmed — nothing stored per conversation). Auth = single admin, signed `bor_session` cookie (Starlette `SessionMiddleware` + itsdangerous; no server-side session store); admin-issued SHA-256-hashed access tokens are the only other identity; **only** shared chats stay anonymous. Fail-loud at boot while `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` are empty | | A11 | Frontend | Vanilla HTML/CSS/JS in git, **no CDN** — everything served by FastAPI `StaticFiles`; system font stack; the navbar views are views of ONE shell document (`frontend/index.html` + `router.js` deep-links, phase 76) | | A12 | Aux services | **None** (no Valkey/queue/SeaweedFS): sync runs in-process, the login rate limit is in-memory per-process, sessions are the signed cookie. Restart clearing in-memory state is accepted | | A13 | Migrations | Alembic + SQLAlchemy 2.0 (sync) + psycopg 3; **every migration ships a tested downgrade** (A13 — reversible) | | A14 | Debugging | `debugpy` imported **only when `DEBUGPY=1`** (`app/core/debugging.py`); never imported otherwise (unit-tested) | | A15 | Chat transport | **SSE** from `POST /api/chat`; event types `thinking`, `delta`, `tool`, `retry`, `done`, `error` (+ `tool_result`, owner-permitted extension — phase 95, not yet landed); no proxy buffering, no client caching on the stream | | A16 | Testing | Per phase: unit + integration (pytest) with **>90% coverage on `app/`** + one dedicated **Playwright E2E file**, run **in isolation** (`--no-cov`); E2E uses a deterministic mock LLM by default (`E2E_REAL_LLM=1` opts into live aipi) | | A17 | Git | Conventional Commits, **always `--no-gpg-sign`**, one atomic commit per completed phase | | A18 | Docs push | Save-a-answer-as-doc: server-side draft (`doc_drafts`, long body never in a URL — unguessable `uuid4` token is the URL credential) pushed to a **generic git remote** (`BOR_DOCS_REPO`, URL or local path; no `gh`) on a dedicated branch, `--ff-only`, re-cut per push; **no PR tooling**; inert (hidden + 409) while the repo is unset | | A19 | Deploys & caching | HTML pages `Cache-Control: no-cache` (no etag/last-modified); `/assets/*` versioned (`?v=`) + `immutable, max-age=1y`; **the token is the deploy** (git `HEAD` short SHA; content-hash fallback) — a deploy is a commit the browser sees without a hard refresh | | A20 | Security headers | Every response: `Content-Security-Policy: default-src 'self'` (+ the theme's inline-`