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
69 changes: 69 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
services:
postgres:
image: pgvector/pgvector:pg16
Expand Down Expand Up @@ -70,3 +72,70 @@ jobs:
/tmp/openbrain-smoke/bin/openbrain install-hermes --hermes-home /tmp/hermes-smoke
test -f /tmp/hermes-smoke/plugins/openbrain/plugin.yaml
test -f /tmp/hermes-smoke/plugins/openbrain/__init__.py

release:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install build tooling
run: python -m pip install --upgrade build
- name: Read package version
id: version
shell: bash
run: |
VERSION="$(python - <<'PY'
import tomllib
from pathlib import Path

project = tomllib.loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']
print(project['version'])
PY
)"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
- name: Check whether release already exists
id: existing
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
shell: bash
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Build package
if: steps.existing.outputs.exists == 'false'
run: python -m build
- name: Smoke-test release wheel
if: steps.existing.outputs.exists == 'false'
env:
EXPECTED_VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
python -m venv /tmp/openbrain-release-smoke
/tmp/openbrain-release-smoke/bin/pip install dist/*.whl
ACTUAL_VERSION="$(/tmp/openbrain-release-smoke/bin/openbrain --version | awk '{print $3}')"
test "$ACTUAL_VERSION" = "$EXPECTED_VERSION"
- name: Publish GitHub release
if: steps.existing.outputs.exists == 'false'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
gh release create "$TAG" dist/*.whl dist/*.tar.gz \
--target "$GITHUB_SHA" \
--title "Open Brain $VERSION" \
--generate-notes
43 changes: 31 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,38 @@ All notable changes to Open Brain are documented in this file. The project follo

## [Unreleased]

## [1.0.2] - 2026-09-07

### Fixed
- Capped the MCP Python SDK dependency to `mcp>=1.0.0,<2.0.0`, preventing fresh installs from resolving the incompatible 2.x API while Open Brain still uses the 1.x server decorators.
- Added bounded chunking, pooling, and validation for oversized embedding inputs so provider context limits no longer make memory storage fail outright.
- Added shared embedding regeneration for REST and MCP, with safe NULL-only replacement by default and an explicit force mode for embedding-model migrations.
- Made migration `015_embedding_dim_change.sql` safe on databases where the legacy `memory` table is not present yet.
- Added `001_base_schema.sql` to the packaged migration chain so an empty PostgreSQL + pgvector database can be initialized from a pip/pipx install.
- Added `openbrain migrate` as the supported installed-package database bootstrap/update command.
- Fixed e2e pytest collection so the standalone API runner skips cleanly when the API stack is unavailable instead of aborting the full test suite during module import.

### Changed
- Default docker-compose stack now ships an `ollama` service running the `nomic-embed-text` model (768-dim). The api no longer requires an external embeddings API key for a working local stack.
- Default API host port changed from `8000` to `8765`. Port 8000 is held by the Windows IP Helper service (`iphlpsvc`) on many Windows hosts, which prevents the api container from binding. Override with `API_PORT` in `.env`.
- `scripts/setup_db.py` now honours `DB_HOST`/`DB_PORT`/`DB_NAME`/`DB_USER` environment variables, matching the pattern used by `check_db.py`. The api container's compose-injected values (e.g. `postgres:5432`) now apply correctly on first run.
- `scripts/startup.sh` now invokes `scripts/migrate.py` after `setup_db.py`. The README always claimed the API applied migrations on startup; the code now matches the documentation.
- `src/db/connection.py` registers `psycopg2.extras.UUID_adapter` at import time. Queries that bind `uuid.UUID` parameters (`get_memory_by_id`, etc.) no longer raise `ProgrammingError: can't adapt type 'UUID'`.
- Documented the standalone PostgreSQL + pgvector bootstrap flow in the README and installation guide.
- Aligned package and Hermes plugin release metadata on version `1.0.2`.
- Added release automation that publishes a tagged GitHub release with wheel and source-distribution artifacts only after `Verify` succeeds on `master`.

### Added
- `scripts/quickstart.sh` brings up the docker stack, waits for the api to become healthy, ensures the embedding model is available, and (with `--with-hermes`) wires Open Brain into a locally-installed Hermes as the active memory provider. Idempotent.
- Migration `015_embedding_dim_change.sql` alters `memory.embedding` from `vector(1536)` to `vector(768)` and rebuilds the HNSW index to match the new Ollama-backed default embedder.
- `.gitattributes` forces LF line endings for `*.sh`, `*.py`, `*.sql`, and similar source files so Windows checkouts no longer reintroduce CRLF into shell scripts.
- `.gitignore` now excludes `.env`; `.env.example` is the single source of truth for non-secret defaults.
## [1.0.1] - 2026-07-24

### Fixed
- Made the local Docker stack work from a fresh clone by enforcing LF shell-script line endings, honoring database environment variables, running migrations during API startup, and registering psycopg2 UUID adaptation.
- Updated NLTK resource handling for the 3.8.2+ resource-name changes and baked the required data into the API image.

### Security
- Removed the previously-checked-in `.env` from version control. Operators should `cp .env.example .env` (or let `openbrain configure --project-root .` generate the secrets) before first run.
### Changed
- Switched the default local embedding service to Ollama with `nomic-embed-text` (768 dimensions), removing the need for an external embeddings API key for the default stack.
- Changed the default host API port from `8000` to `8765` to avoid common Windows port conflicts.
- Added migration `015_embedding_dim_change.sql` to align `memory.embedding` with the 768-dimensional default model and rebuild the HNSW index.
- Removed the checked-in `.env`; `.env.example` is now the non-secret source of defaults and generated credentials remain private.
- Added `.gitattributes` so Windows checkouts do not reintroduce CRLF into scripts and source files.

### Added
- Added `scripts/quickstart.sh` for idempotent local stack bootstrap and optional Hermes wiring.
- Added the API e2e suite covering health, memory, continuity, context, review workflows, maintenance, imports, and authentication behavior.

## [1.0.0] - 2026-07-22

Expand Down Expand Up @@ -51,4 +68,6 @@ All notable changes to Open Brain are documented in this file. The project follo

A production deployment is ready only after automatic checks pass and operators explicitly attest that TLS, backups, restore drills, monitoring, and migration records have been verified. Open Brain deliberately does not infer these external controls from configuration alone.

[1.0.2]: https://github.com/benclawbot/open-brain/releases/tag/v1.0.2
[1.0.1]: https://github.com/benclawbot/open-brain/releases/tag/v1.0.1
[1.0.0]: https://github.com/benclawbot/open-brain/releases/tag/v1.0.0
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

[![Verify](https://github.com/benclawbot/open-brain/actions/workflows/verify.yml/badge.svg)](https://github.com/benclawbot/open-brain/actions/workflows/verify.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/mit)
[![Version](https://img.shields.io/badge/version-1.0.0-blue)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-1.0.2-blue)](CHANGELOG.md)

## Overview

Expand Down Expand Up @@ -124,6 +124,14 @@ curl -fsSL https://raw.githubusercontent.com/benclawbot/open-brain/master/instal

The installer uses `pipx`, keeping Open Brain isolated from system Python packages. It generates a private OpenBrain API key once in `~/.config/openbrain/.env`; upgrades reuse that key. This path is intended for users who already have a Postgres+pgvector instance elsewhere — the docker stack on this repo is the supported local-stack path.

Point `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, and `DB_PASSWORD` at that database, then initialize it directly from the installed package:

```bash
openbrain migrate
```

The packaged migration chain starts at `001_base_schema.sql`, so a clean pipx install can create the core schema without cloning the repository or manually downloading `src/db/schema.sql`.

Verify:

```bash
Expand Down Expand Up @@ -213,6 +221,7 @@ openbrain stats
openbrain import file ./notes.md
openbrain report --days 7
openbrain serve --host 127.0.0.1 --port 8000
openbrain migrate
openbrain install-hermes
openbrain update
```
Expand Down Expand Up @@ -345,12 +354,12 @@ src/

## Release status

**Open Brain 1.0.0 is the first production/stable release.** The coordinated production-readiness train delivered deployment hardening, database resilience, real adapter host wiring, durable reconciliation, staged imports, unified proposal review workflows, and a machine-readable release gate.
**Open Brain 1.0.2 is the current production/stable release.** This patch includes embedding resilience and recovery, MCP SDK compatibility, packaged database bootstrap for pip/pipx installs, and release automation that publishes verified wheel and source-distribution artifacts.

Production readiness still depends on the target environment. Operators must run the readiness gate and verify external controls such as TLS, backups, restore drills, and monitoring before serving real data.

See [`CHANGELOG.md`](CHANGELOG.md) for release details.

## License

MIT.
MIT.
10 changes: 9 additions & 1 deletion docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ The installer verifies Python 3.11+, installs or upgrades Open Brain through `pi

Set `OPENBRAIN_INSTALL_HERMES=0` to skip automatic Hermes wiring. Set `OPENBRAIN_REPO_URL` to install from a fork.

For a standalone CLI install backed by an existing PostgreSQL + pgvector server, configure the database connection through `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, and `DB_PASSWORD`, then bootstrap the schema from the installed package:

```sh
openbrain migrate
```

The migration chain now begins with `001_base_schema.sql`, so an empty database can be initialized without cloning the repository or locating `src/db/schema.sql` manually. The command is idempotent and uses the same checksum-protected migration ledger as `openbrain update`.

## Hermes

Automatic installation copies the packaged provider into `${HERMES_HOME:-~/.hermes}/plugins/openbrain`. Manual repair is available with:
Expand Down Expand Up @@ -38,7 +46,7 @@ openbrain version-check
openbrain update
```

`version-check` is offline-safe. `update` upgrades the pipx installation and applies additive migrations. When package upgrade succeeds but migration fails, it exits non-zero and reports that existing data was not deleted.
`version-check` is offline-safe. `update` upgrades the pipx installation and applies additive migrations. When package upgrade succeeds but migration fails, it exits non-zero and reports that existing data was not deleted. You can also run `openbrain migrate` directly whenever you need to initialize or reconcile the database schema without upgrading the package.

## Diagnostics

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "openbrain"
version = "1.0.0"
version = "1.0.2"
description = "Shared personal memory, agent continuity, and actionable context"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
19 changes: 19 additions & 0 deletions src/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ def _run(command: list[str]) -> None:
subprocess.run(command, check=True)


def migrate_cmd() -> int:
"""Apply packaged database migrations, including the base schema."""
try:
from src.db.migrate import apply_migrations
applied = apply_migrations()
except Exception as exc:
print(f"Database migration failed: {exc}", file=sys.stderr)
return 1
if applied:
print("Applied migrations: " + ", ".join(applied))
else:
print("Database schema is up to date.")
return 0


def update_cmd(skip_migrations: bool = False) -> int:
"""Upgrade a pipx-managed installation and apply additive migrations."""
try:
Expand Down Expand Up @@ -106,6 +121,8 @@ def main() -> int:
serve_parser.add_argument("--port", "-p", type=int, default=8000)
serve_parser.add_argument("--reload", action="store_true")

subparsers.add_parser("migrate", help="Create or update the database schema")

update_parser = subparsers.add_parser("update", help="Upgrade Open Brain and apply additive migrations")
update_parser.add_argument("--skip-migrations", action="store_true")

Expand Down Expand Up @@ -157,6 +174,8 @@ def main() -> int:
if args.command == "serve":
from .serve import serve_cmd
return serve_cmd(args)
if args.command == "migrate":
return migrate_cmd()
if args.command == "update":
return update_cmd(skip_migrations=args.skip_migrations)
if args.command == "configure":
Expand Down
79 changes: 79 additions & 0 deletions src/db/migrations/001_base_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
-- Open Brain Database Schema
-- PostgreSQL + pgvector

-- Enable extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "vector";

-- Core memory table
CREATE TABLE IF NOT EXISTS memory (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
source VARCHAR(50) NOT NULL,
source_id VARCHAR(255),
captured_by TEXT,
content TEXT NOT NULL,
raw_content TEXT,
embedding vector(768),
entities JSONB DEFAULT '{}',
tags TEXT[] DEFAULT '{}',
tag_sources JSONB DEFAULT '{}',
importance FLOAT DEFAULT 0.5,
created_at TIMESTAMP DEFAULT NOW(),
original_date TIMESTAMP,
language VARCHAR(10),
metadata JSONB DEFAULT '{}'
);

-- Indexes for columns present in the original base schema. The captured_by
-- index is intentionally left to 014_agent_attribution.sql so an older
-- existing memory table can reach the migration that adds captured_by first.
CREATE INDEX IF NOT EXISTS idx_memory_embedding ON memory USING hnsw (embedding vector_cosine_ops);
CREATE INDEX IF NOT EXISTS idx_memory_entities ON memory USING gin (entities);
CREATE INDEX IF NOT EXISTS idx_memory_tags ON memory USING gin (tags);
CREATE INDEX IF NOT EXISTS idx_memory_source ON memory (source);
CREATE INDEX IF NOT EXISTS idx_memory_created ON memory (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_memory_original_date ON memory (original_date DESC);

-- Full-text search index
CREATE INDEX IF NOT EXISTS idx_memory_content_fts ON memory USING GIN (to_tsvector('english', content));

-- Function to get memories from today
CREATE OR REPLACE FUNCTION get_today_memories(limit_count INTEGER DEFAULT 10)
RETURNS TABLE (
id UUID,
source VARCHAR,
content TEXT,
tags TEXT[],
created_at TIMESTAMP,
importance FLOAT
) AS $$
BEGIN
RETURN QUERY
SELECT m.id, m.source, m.content, m.tags, m.created_at, m.importance
FROM memory m
WHERE m.created_at >= CURRENT_DATE
ORDER BY m.importance DESC, m.created_at DESC
LIMIT limit_count;
END;
$$ LANGUAGE plpgsql;

-- Function to get memory statistics
CREATE OR REPLACE FUNCTION get_memory_stats()
RETURNS TABLE (
total_count BIGINT,
source_name VARCHAR,
source_count BIGINT,
tag_name TEXT,
tag_count BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*)::BIGINT as total_count,
NULL::VARCHAR as source_name,
NULL::BIGINT as source_count,
NULL::TEXT as tag_name,
NULL::BIGINT as tag_count
FROM memory;
END;
$$ LANGUAGE plpgsql;
2 changes: 1 addition & 1 deletion src/openbrain_hermes_plugin/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: openbrain
version: 1.0.0
version: 1.0.2
description: Shared personal memory and cross-agent continuity through Open Brain
hooks:
- on_session_end
Expand Down
5 changes: 5 additions & 0 deletions tests/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

import pytest

# test_api.py distinguishes pytest from direct script execution using this
# environment marker. Pytest normally sets it only while running a test, which
# is too late for module collection; set a harmless collection value here.
os.environ.setdefault("PYTEST_CURRENT_TEST", "collection")


def _api_url() -> str:
return os.environ.get("OPENBRAIN_E2E_URL", "http://127.0.0.1:8765").rstrip("/")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_embedding_resilience.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def test_rest_and_mcp_expose_regeneration_controls():

assert EmbeddingRegenerationRequest(force=True).force is True
assert any(
route.path == "/memories/{memory_id}/regenerate-embedding"
getattr(route, "path", None) == "/memories/{memory_id}/regenerate-embedding"
for route in app.routes
)

Expand Down
Loading
Loading