Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,41 @@
# Advanced tuning knobs (decay rates, graph depth, agent turn limits) have
# reasonable defaults — see braindb/config.py for the full list.

# PostgreSQL connection string (psycopg2 format)
DATABASE_URL=postgresql://user:password@host:5432/braindb
# ============================================================
# DATABASE — pick ONE of the two modes
# ============================================================

# --- Mode 1: Internal DB (recommended / default) ------------
# BrainDB runs its own Postgres container — nothing to install.
# This single line is the switch; remove it for Mode 2:
COMPOSE_PROFILES=internal-db

# Optional knobs for the internal DB. They are IGNORED in Mode 2
# (external connections are configured entirely via DATABASE_URL).
# NOTE: user/password/db-name are baked into the data volume on its
# FIRST boot. To reset the internal DB later (wipes its data!):
# docker rm -f braindb_db && docker volume rm braindb_pgdata
# POSTGRES_PORT=5435 # host port, only for inspecting the DB with
# # psql/adminer; the stack itself doesn't need
# # it. Change if 5435 is taken on your machine.
# POSTGRES_DB=braindb
# POSTGRES_USER=braindb
# POSTGRES_PASSWORD=braindb # change for anything beyond local use

# --- Mode 2: External DB (bring your own Postgres) ----------
# Your Postgres needs the pgvector and pg_trgm extensions.
# 1. Remove (or comment out) the COMPOSE_PROFILES line above.
# 2. Put EVERYTHING — host, port, db name, credentials — in this
# one URL. The POSTGRES_* knobs above have no effect here.
# DATABASE_URL=postgresql://user:password@your-host:5432/braindb

# API port (default 8000)
API_PORT=8000

# Frontend (read-only browser UI) host port — served automatically by
# docker compose at http://localhost:8642. Change if 8642 is taken.
FRONTEND_PORT=8642

# HuggingFace token — for faster model downloads (optional but recommended)
# Get yours at https://huggingface.co/settings/tokens
HF_TOKEN=
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ benchmarks/*/answers/
# Bench compose: separate host data dir for the bench watcher, separate env file
data_bench/
.env.bench

# Test stack (docker-compose.test.yml): separate host data dir for test ingests
data_test/
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] — 2026-06-12

### Added

- **Bundled internal Postgres (opt-in by one `.env` line).** A `braindb_db`
service (pgvector/pg17) guarded by the `internal-db` compose profile.
`.env.example` now ships `COMPOSE_PROFILES=internal-db`, so a fresh clone
gets a working database with plain `docker compose up -d` — nothing to
install. Existing setups are untouched: without that line the service does
not exist and `DATABASE_URL` keeps pointing at your own Postgres exactly as
before. Optional knobs: `POSTGRES_PORT` (host inspection port, default
5435, loopback-only), `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`.
Requires Docker Compose >= 2.20. Closes the request in issue #12.
- **Frontend served by the stack.** A `frontend` service (nginx:alpine over
the static `frontend/` directory) joins `docker compose up -d` — the
read-only browser UI is now at `http://localhost:8642` with zero manual
steps (`FRONTEND_PORT` to change it; loopback-only). The manual
static-server route still works as before.
- **Logo + architecture diagrams.** The BrainDB logo tops the README and the
frontend header (plus favicon), and five SVG diagrams — the keyword-graph
mechanism, retrieval pipeline, save/ingest lanes, wiki maintainer loop, and
wiki writer pipeline — illustrate their README sections. Assets live in
`docs/assets/`.

### Fixed

- **Test suite no longer touches your database — at all.** The suite now
runs against an isolated test stack (`docker-compose.test.yml`: its own
API on port 8002 + its own throwaway Postgres), and the session-end
cleanup that deleted pattern-matched rows from the configured database
has been removed entirely. Previously the tests ran against the live
personal stack and their cleanup silently failed from the host, leaking
`_pytest_*` keyword entities into real data (where the wiki maintainer
wasted LLM calls triaging them). Now: `docker compose -f
docker-compose.test.yml up -d && pytest`, and `down -v` wipes everything.

## [0.5.0] — 2026-06-09

### Changed
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ braindb/
├── skills/ # Shipped Claude Code skills
│ ├── braindb/SKILL.md # Direct curl-based skill
│ └── braindb-agent/SKILL.md # Thin wrapper around the agent endpoint
├── frontend/ # Read-only browser UI — vanilla JS, no build
├── docker-compose.yml # api + watcher services (external PostgreSQL)
├── frontend/ # Read-only browser UI — vanilla JS, no build (served at :8642)
├── docker-compose.yml # api + watcher + wiki_scheduler + frontend (+ optional bundled Postgres)
├── .env # Real credentials — DO NOT COMMIT
└── BRAINDB_GUIDE.md # Full API reference with curl examples
```
Expand Down
10 changes: 7 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Thanks for your interest. BrainDB is a small, opinionated project — the bar is

## Dev setup

Prerequisites: Docker Desktop (or any Docker Engine), Python 3.12, a Postgres 16 instance reachable from the container.
Prerequisites: Docker Desktop (or Docker Engine) with Compose v2.20+, Python 3.12. The database is bundled by default (`COMPOSE_PROFILES=internal-db` in `.env.example`); to use your own Postgres instead, remove that line and set `DATABASE_URL` — see the README's Setup section.

```bash
git clone <repo-url> braindb
cd braindb
cp .env.example .env
# edit .env — set DATABASE_URL; recommended LLM_PROFILE=deepinfra + DEEPINFRA_API_KEY (or any other profile)
# edit .env — recommended LLM_PROFILE=deepinfra + DEEPINFRA_API_KEY (or any other profile)

docker network create local-network # one-time; docker-compose expects this
docker compose up -d --build
Expand All @@ -26,10 +26,14 @@ pip install -e ".[dev]"

## Running tests

The suite runs against an **isolated test stack** (its own API on port 8002 + its own throwaway Postgres) — never against your personal BrainDB database:

```bash
pytest # full suite (needs the stack up)
docker compose -f docker-compose.test.yml up -d # start the test stack
pytest # full suite
pytest -k "not agent" # skip the live-LLM smoke tests
pytest tests/test_split_chunks.py # a single file
docker compose -f docker-compose.test.yml down -v # stop + wipe test data
```

See [`tests/README.md`](tests/README.md) for what is and isn't covered.
Expand Down
60 changes: 41 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# BrainDB
<p align="center">
<img src="docs/assets/braindb-logo.svg" width="460" alt="BrainDB">
</p>

[![BrainDB walkthrough](https://img.youtube.com/vi/AJ7iMOj4vvA/maxresdefault.jpg)](https://youtu.be/AJ7iMOj4vvA "Watch the BrainDB walkthrough on YouTube")

Expand All @@ -18,6 +20,10 @@ Inspired by Karpathy's [LLM wiki idea](https://gist.github.com/karpathy/442a6bf5
- **vs. classic graph DBs** (Neo4j, Memgraph). Those are general-purpose graph stores with their own query languages and ops cost. BrainDB is purpose-built for LLM agents: a plain HTTP API designed for tool-calling, semantically meaningful fields (`certainty`, `importance`, `emotional_valence`), built-in text + pgvector search with geometric-mean scoring, always-on rule injection, automatic provenance, and runs on plain PostgreSQL + `pg_trgm` + `pgvector` — no new infrastructure to operate.
- **vs. markdown files as memory.** Markdown wikis are flat and unstructured: the LLM has to grep, read whole files into context, and manage linking by hand. BrainDB's entities are atomic, queryable, ranked, and self-connecting. Facts extracted from a document automatically link back to the source via `derived_from`; recall returns relevant nodes plus their graph neighbourhood; nothing needs to be read in full unless the agent asks for it.

<p align="center">
<img src="docs/assets/braindb-mechanism.svg" width="660" alt="One keyword, one graph — facts, a wiki article, and a derived thought all connect through a central keyword entity that carries the embedding">
</p>

---

## Entity Types
Expand All @@ -37,13 +43,12 @@ Relations connect any two entities with `relation_type`, `relevance_score`, `imp

## Setup

BrainDB runs as three Docker services — `api`, `watcher` (auto-ingests files), and `wiki_scheduler` (auto-maintains wikis) — against an **external** PostgreSQL you provide. The two sidecars are hands-off: you never call the pipeline by hand. The whole setup is six steps.
BrainDB runs as Docker services — `api`, `watcher` (auto-ingests files), `wiki_scheduler` (auto-maintains wikis), and a read-only browser `frontend` — plus a database that is either **bundled** (default, zero install) or **your own PostgreSQL**. The two sidecars are hands-off: you never call the pipeline by hand.

### 1. Prerequisites

- Docker Desktop (or any Docker Engine)
- A PostgreSQL 16 instance reachable from Docker (see step 3 for three common options)
- The PostgreSQL extensions `pg_trgm` and `pgvector` must exist on the target database, and the connecting user must have permission to create them on first connection (migrations will `CREATE EXTENSION IF NOT EXISTS` on startup). If you don't have DB admin rights, ask an admin to pre-install both extensions.
- Docker Desktop (or Docker Engine) with Compose v2.20+
- That's it for the default setup — the database ships with the stack.

### 2. Clone and configure

Expand All @@ -53,9 +58,19 @@ cd braindb
cp .env.example .env
```

### 3. Point `.env` at your PostgreSQL
### 3. Choose your database mode

`.env.example` ships with the **internal DB** enabled — a bundled Postgres (with pgvector) that starts as part of the stack. For most users there is nothing to do in this step.

The switch is one line in `.env`:

```
COMPOSE_PROFILES=internal-db # present = bundled DB starts automatically
```

Optional internal-DB knobs (defaults are fine): `POSTGRES_PORT` (host port for inspecting the DB with psql/adminer, default 5435), `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`. Note: these are baked into the data volume on first boot — to reset later: `docker rm -f braindb_db && docker volume rm braindb_pgdata` (wipes its data).

Edit `.env` and set `DATABASE_URL`. The value depends on **where your Postgres runs**:
**Bring your own Postgres instead?** Remove the `COMPOSE_PROFILES` line and set `DATABASE_URL` — everything (host, port, db name, credentials) lives in that one URL:

**Option A — Postgres running as another Docker container on the same network** (e.g. a `postgres_container`):
```
Expand All @@ -72,7 +87,8 @@ DATABASE_URL=postgresql://postgres:password@host.docker.internal:5432/braindb
```
DATABASE_URL=postgresql://user:password@db.example.com:5432/braindb
```
Any reachable hostname/IP works — the connecting user just needs network access, auth, and the extensions mentioned in step 1.

External databases need the `pg_trgm` and `pgvector` extensions; the connecting user must be able to create them on first connection (migrations run `CREATE EXTENSION IF NOT EXISTS` on startup), or an admin pre-installs both.

### 4. Pick an LLM provider (for the internal agent)

Expand All @@ -91,14 +107,16 @@ Adding a third provider (Together, OpenAI, local vLLM, whatever) is a two-line e

### 5. Create the Docker network, then bring the stack up

`docker-compose.yml` expects an external network called `local-network` so the `api` and `watcher` containers can reach your Postgres (and each other) by DNS name:
`docker-compose.yml` expects an external network called `local-network` so the containers can reach each other (and an external Postgres, if you use one) by DNS name:

```bash
docker network create local-network # one-time, ignore error if it already exists
docker compose up -d --build
```

If your Postgres is a container (Option A in step 3), attach it to this network too:
With the internal DB (default), this also starts the bundled Postgres and waits for it to be healthy before the API boots.

If you brought your own Postgres as a container (Option A in step 3), attach it to this network too:
```bash
docker network connect local-network postgres_container
```
Expand All @@ -110,7 +128,7 @@ curl http://localhost:8000/health
# {"status":"ok","embeddings":true}
```

API at `http://localhost:8000`. Swagger UI at `http://localhost:8000/docs`. Database migrations run automatically on startup.
API at `http://localhost:8000`. Swagger UI at `http://localhost:8000/docs`. Browser UI (read-only frontend) at `http://localhost:8642`. Database migrations run automatically on startup.

Drop a markdown file into `data/sources/` and the watcher sidecar picks it up within ~7 seconds — see [File Ingestion](#file-ingestion) below.

Expand Down Expand Up @@ -150,6 +168,8 @@ See [BRAINDB_GUIDE.md](BRAINDB_GUIDE.md) for full API reference with curl exampl

`POST /api/v1/memory/context` is the main endpoint. **Keywords are the indexing layer** — both the fuzzy and the embedding pathways match the query against keyword-entity content / embeddings, then entities surface via `tagged_with` edges. A keyword tagged on many entities is the hub; you don't need explicit `elaborates` / `refers_to` edges for an entity to be findable, as long as it has the right keywords.

![Retrieval pipeline — queries fan out to parallel fuzzy and semantic keyword matching, merge, graph-expand, and rank; always-on rules join the ranked context](docs/assets/braindb-retrieval.svg)

1. **Multi-query search** — pass `queries: ["topic1", "topic2"]` to search multiple angles at once. Each query is matched against keyword entities by both pg_trgm trigram similarity AND query-embedding-vs-keyword-embedding cosine similarity; results are merged with the geometric mean (configurable `missing_signal_penalty` when only one signal fires).
2. **Per-search-term reservation (L1 diversity quota)** — each query you pass gets a guaranteed share of the result slots filled from THAT query's own top-ranked entities. Bare-keyword queries (`"Petros"`) reliably surface specific facts even when paired with broader semantic angles.
3. **Per-keyword reservation (L2 diversity quota)** — each dominant matched keyword gets a halving slot allowance (50% / 25% / 12.5% ..., floor 1). Stops one popular hub keyword (e.g. `user-profile` tagging 100 facts) from monopolising top-N.
Expand Down Expand Up @@ -269,6 +289,8 @@ Replace `/ABSOLUTE/PATH/TO/braindb` with your repo path. The hook is async (non-

Drop a file in `data/sources/` — the always-on watcher sidecar picks it up within 7s, ingests it, and runs a chunked fact-extraction pipeline that saves atomic facts into the knowledge graph linked back to the source via `derived_from` relations. Processed files move to `data/sources/ingested/`, failures to `data/sources/failed/` with an `.error.txt` sidecar.

![Save and ingest — the direct API lane, the agent lane, and the file-ingest watcher pipeline all converge into the knowledge graph](docs/assets/braindb-ingestion.svg)

```bash
cp ~/some-article.md data/sources/
docker logs braindb_watcher -f # watch the pipeline
Expand Down Expand Up @@ -300,6 +322,10 @@ docker logs braindb_wiki_scheduler -f # the autonomous loop
docker logs braindb_api -f # the agent doing the work
```

![Wiki scheduler and maintainer loop — cron scans for entities not yet covered, the maintainer decides attach / create / consolidate / skip, suggestion jobs hand off to the writer, and the loop repeats](docs/assets/braindb-upkeep-loop.svg)

![Wiki writer pipeline — lock and snapshot, gather context, draft with citations, reconcile relations, validate with retry, then commit a reversible living wiki page](docs/assets/braindb-writer.svg)

You do **not** drive this by hand. The `POST /api/v1/wiki/{cron,maintain,write}`
endpoints exist for **debugging / inspection only** — normal operation is the
sidecar. (Optional read-only review: `docker compose exec api python -m
Expand All @@ -311,15 +337,11 @@ automatically. To run without it, bring the stack up excluding the service or
scale it to 0 (`docker compose up -d --scale wiki_scheduler=0`), exactly as
you would for the watcher; or point `LLM_PROFILE` at a local model.

## Frontend (optional, read-only)

A small vanilla-JS frontend ships under `frontend/` — no build step. Three views (Reader for browsing wikis, Graph for visual exploration, Ops for watching the maintainer/writer pipeline) plus an Ask drawer that talks to the agent endpoint. Talks to the BrainDB API at `http://localhost:8000`. Serve it from the repo root:
## Frontend (read-only browser UI)

```bash
cd frontend && python -m http.server 8090
```
A small vanilla-JS frontend ships under `frontend/` — no build step — and is served by the stack itself: after `docker compose up -d`, open **`http://localhost:8642`** (change the port with `FRONTEND_PORT` in `.env`). Three views (Reader for browsing wikis, Graph for visual exploration, Ops for watching the maintainer/writer pipeline) plus an Ask drawer that talks to the agent endpoint. The UI calls the BrainDB API at `http://localhost:8000` from your browser.

Then open `http://localhost:8090`. See [frontend/README.md](frontend/README.md) for the design notes.
Prefer no extra container? Any static file server over `frontend/` works too, e.g. `cd frontend && python -m http.server 8642`. See [frontend/README.md](frontend/README.md) for the design notes.

## Stack

Expand All @@ -328,4 +350,4 @@ Then open `http://localhost:8090`. See [frontend/README.md](frontend/README.md)
- Alembic migrations
- `sentence-transformers` + `Qwen/Qwen3-Embedding-0.6B` for keyword embeddings
- `openai-agents[litellm]` + LiteLLM for the internal agent (DeepInfra / NIM / others pluggable via `LLM_PROFILE`)
- Docker Compose — `api` + `watcher` + `wiki_scheduler` services, external PostgreSQL
- Docker Compose — `api` + `watcher` + `wiki_scheduler` + `frontend` services, with a bundled Postgres (default) or your own
Loading
Loading