Skip to content

Commit a38ca19

Browse files
Merge pull request #4 from softmaxdata/fix/core-correctness-and-mcp
Fix core correctness, storage atomicity, and MCP setup
2 parents b0b9158 + 7d5943f commit a38ca19

62 files changed

Lines changed: 7650 additions & 675 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Regression and package checks
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
env:
12+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
13+
LITELLM_LOCAL_MODEL_COST_MAP: "True"
14+
DO_NOT_TRACK: "1"
15+
16+
jobs:
17+
sqlite-and-package:
18+
runs-on: ubuntu-latest
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
python-version: ["3.11", "3.13"]
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: actions/setup-python@v5
26+
with:
27+
python-version: ${{ matrix.python-version }}
28+
cache: pip
29+
- name: Install test and build dependencies, including the MCP extra
30+
run: python -m pip install -e '.[dev,mcp]' build
31+
- name: Check recorded shared-core contracts
32+
run: python scripts/check-shared-core.py --checkout .
33+
- name: Run full SQLite suite
34+
run: python scripts/check-ci.py --backend sqlite --output reports/sqlite.xml
35+
- name: Run controlled retrieval regression benchmark
36+
run: python -m evals.retrieval --output reports/retrieval.json
37+
- name: Build sdist, then wheel; certify installed package and MCP
38+
run: python scripts/check-package.py --output-dir reports/package
39+
- name: Retain verification reports and package artifacts
40+
if: always()
41+
uses: actions/upload-artifact@v4
42+
with:
43+
name: sqlite-package-python-${{ matrix.python-version }}
44+
path: reports/
45+
retention-days: 14
46+
47+
postgres:
48+
runs-on: ubuntu-latest
49+
strategy:
50+
fail-fast: false
51+
matrix:
52+
python-version: ["3.11", "3.13"]
53+
services:
54+
postgres:
55+
image: pgvector/pgvector:pg16
56+
env:
57+
POSTGRES_DB: engram_test
58+
POSTGRES_USER: engram_test
59+
POSTGRES_PASSWORD: engram-test-only
60+
ports:
61+
- 5432:5432
62+
options: >-
63+
--health-cmd "pg_isready -U engram_test -d engram_test"
64+
--health-interval 5s
65+
--health-timeout 5s
66+
--health-retries 12
67+
env:
68+
ENGRAM_TEST_POSTGRES_DSN: postgresql://engram_test:engram-test-only@127.0.0.1:5432/engram_test
69+
steps:
70+
- uses: actions/checkout@v4
71+
- uses: actions/setup-python@v5
72+
with:
73+
python-version: ${{ matrix.python-version }}
74+
cache: pip
75+
- run: python -m pip install -e '.[dev,mcp]'
76+
- name: Require ready pgvector and run full suite with PostgreSQL tests enabled
77+
run: python scripts/check-ci.py --backend postgres --output reports/postgres.xml
78+
- name: Retain PostgreSQL verification report
79+
if: always()
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: postgres-python-${{ matrix.python-version }}
83+
path: reports/
84+
retention-days: 14

‎README.md‎

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,9 @@ There are two storage options:
9191
- SQLite (local) — zero setup, tables auto-created on first run
9292
- PostgreSQL (local/remote) — requires creating the database first
9393

94-
For PostgreSQL, create the database and enable pgvector before starting the server:
94+
For PostgreSQL, install pgvector on the database server and create the database first:
9595
```sql
9696
CREATE DATABASE engram;
97-
\c engram
98-
CREATE EXTENSION IF NOT EXISTS vector;
9997
```
10098

10199
Then set the environment variables:
@@ -104,7 +102,14 @@ ENGRAM_STORAGE_BACKEND=postgres
104102
ENGRAM_POSTGRES_DSN=postgresql://user:password@host:5432/engram
105103
```
106104

107-
All tables are auto-created on first server start for both backends.
105+
Engram enables the `vector` extension before opening its connection pool and creates
106+
tables on first startup. The Docker Compose pgvector image already includes the
107+
extension files, so `docker compose up` works with a fresh volume.
108+
109+
On managed PostgreSQL, an administrator may need to run
110+
`CREATE EXTENSION IF NOT EXISTS vector` in the Engram database before the first
111+
startup if the application role cannot install extensions. Existing extensions and
112+
data are preserved across restarts; do not delete your database volume to upgrade.
108113

109114
### 1. Configure the Server
110115

@@ -149,15 +154,11 @@ ENGRAM_CURATOR_SLOW_PATH_MODEL=claude-haiku-4-5 # For complex conflicts
149154
# ─── Embedding ──────────────────────────────────────────────
150155
ENGRAM_EMBEDDING_MODEL=text-embedding-3-small
151156

152-
# ─── Consolidation ──────────────────────────────────────────
153-
ENGRAM_CONSOLIDATION_TRIGGER=every_10_commits # or "daily", "manual"
154-
ENGRAM_FAST_DECAY_RATE=0.97 # Per-day salience decay for normal bullets
155-
ENGRAM_SLOW_DECAY_RATE=0.995 # Per-day decay for decisions/schemas (slower)
157+
# Consolidation is invoked through the SDK or REST API.
158+
# This server does not schedule consolidation from environment variables.
156159

157-
# ─── Auth (disabled for local dev, enable for production) ───
158-
ENGRAM_AUTH_ENABLED=false
159-
# COGNITO_USER_POOL_ID=us-west-2_xxx
160-
# COGNITO_APP_CLIENT_ID=xxx
160+
# The OSS server has no built-in account authentication.
161+
# Cloud account/API-key authentication belongs to the hosted edition.
161162
```
162163

163164
### 2. Start the Server
@@ -769,33 +770,59 @@ Capacity management with pressure levels:
769770

770771
### MCP Server (Claude)
771772

773+
For **Engram Cloud**, add `https://api.engram.so/mcp/sse` in Claude's
774+
**Settings → Connectors → Add custom connector**, then sign in to Engram and
775+
authorize access. See [Cloud setup](https://app.engram.so/setup).
776+
777+
For a **local stdio bridge**, install from a checkout containing this fix using
778+
Python 3.11 or later. The published `engram-contextdb` 0.4.5 wheel lacks the
779+
module entrypoint; use the corrected source checkout until a fixed release is
780+
available. The distribution is `engram-contextdb`; imports remain `engram`.
781+
772782
```bash
773-
pip install engram[mcp]
774-
python -m engram.integrations.mcp_server
783+
cd /absolute/path/to/engram
784+
python3 -m venv .venv
785+
.venv/bin/python -m pip install ".[mcp]"
775786
```
776787

777-
Configure in Claude Code:
788+
Start your self-hosted Engram HTTP server separately as described in Quick Start.
789+
The MCP process connects to that server; it does not start the database API.
790+
791+
Configure Claude Code with the absolute path to the installed interpreter:
792+
778793
```bash
779-
claude mcp add engram -- python -m engram.integrations.mcp_server
794+
claude mcp add --transport stdio \
795+
--env ENGRAM_API_URL=http://localhost:5820 \
796+
engram -- /absolute/path/to/engram/.venv/bin/python -m engram.integrations.mcp_server
780797
```
781798

782-
Or in `claude_desktop_config.json`:
799+
For Claude Desktop, open **Settings → Developer → Edit Config** and merge the
800+
`engram` entry into the existing `mcpServers` object. Keep one JSON root object:
801+
783802
```json
784803
{
785804
"mcpServers": {
786805
"engram": {
787-
"command": "python",
806+
"command": "/absolute/path/to/engram/.venv/bin/python",
788807
"args": ["-m", "engram.integrations.mcp_server"],
789808
"env": {
790-
"ENGRAM_API_URL": "http://localhost:5820",
791-
"ENGRAM_API_KEY": "eng_sk_..."
809+
"ENGRAM_API_URL": "http://localhost:5820"
792810
}
793811
}
794812
}
795813
}
796814
```
797815

798-
Tools: `engram_list_contexts`, `engram_recall`, `engram_commit`, `engram_decide`, `engram_health`, `engram_create_context`
816+
Replace the executable path with your actual absolute path (on Windows, use the
817+
venv's `Scripts\\python.exe` and escape backslashes in JSON). Restart Claude
818+
Desktop after saving. For Cloud access through this bridge, set `ENGRAM_API_URL`
819+
to `https://api.engram.so` and add `ENGRAM_API_KEY` with your Engram API key to
820+
`env`. Self-hosted OSS has no account authentication and does not require a key.
821+
The bridge forwards a configured key as an HTTP Bearer token.
822+
823+
Tools: `engram_list_contexts`, `engram_recall`, `engram_commit`, `engram_decide`,
824+
`engram_health`, `engram_create_context`; the original unprefixed tools remain
825+
available. Ask Claude to list your contexts to verify an actual API call.
799826

800827
### OpenAI Function Calling
801828

@@ -880,14 +907,6 @@ All settings via environment variables or `.env` file:
880907
| `ENGRAM_VALIDITY_GATE_MODEL` | `claude-haiku-4-5` | Model used by the validity gate when enabled |
881908
| **Embedding** | | |
882909
| `ENGRAM_EMBEDDING_MODEL` | `text-embedding-3-small` | For bullet similarity search **and** worked-example retrieval over the activity ledger |
883-
| **Consolidation** | | |
884-
| `ENGRAM_CONSOLIDATION_TRIGGER` | `every_10_commits` | When to run consolidation |
885-
| `ENGRAM_FAST_DECAY_RATE` | `0.97` | Per-day salience decay (normal bullets) |
886-
| `ENGRAM_SLOW_DECAY_RATE` | `0.995` | Per-day decay (decisions/schemas) |
887-
| **Auth** | | |
888-
| `ENGRAM_AUTH_ENABLED` | `false` | Enable multi-tenant auth |
889-
| `COGNITO_USER_POOL_ID` | — | AWS Cognito user pool |
890-
| `COGNITO_APP_CLIENT_ID` | — | AWS Cognito app client |
891910

892911
## Development
893912

@@ -969,3 +988,4 @@ please email logos@engram.so (company + use case).
969988
For large-scale deployments (>20,000 external users), we offer paid support
970989
and an enterprise agreement — contact logos@engram.so.
971990

991+
Upgrade requirements and preserved interfaces are documented in [Compatibility and upgrade notes](docs/backward-compatibility.md).

‎docs/backward-compatibility.md‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Compatibility and upgrade notes
2+
3+
The supported upgrade target is the existing REST, MCP and Python SDK API with
4+
populated SQLite or PostgreSQL databases. The compatibility audit compares the
5+
previous Git revision with the corrected source: all 34 existing HTTP operations,
6+
40 schema definitions and 33 public SDK methods retain their existing fields,
7+
enum values, documented responses and method signatures. All 353 existing public Python definitions remain present. Database upgrades add
8+
columns and indexes without replacing account-independent context, memory,
9+
activity or receipt IDs. No developer or customer database was upgraded during
10+
validation.
11+
12+
Built-in SQLite and PostgreSQL support explicit transaction-scoped storage.
13+
Out-of-tree storage implementations must implement `StorageBackend.transaction`
14+
before adopting these atomic mutation paths; unsupported implementations fail
15+
clearly. This is an extension-interface requirement. Existing renderers and
16+
completion adapters without a per-call model argument remain supported. OSS v0.5
17+
core memory, worked examples, validity checks and reconsolidation remain intact.
18+
19+
Incorrect or unsafe behavior is intentionally corrected: cross-context writes
20+
are rejected, active capacity is checked against the actual committed state,
21+
failed mutations cannot leave partial audit/activity records, and unavailable or
22+
invalid similarity comparisons cannot justify destructive matching. Configured
23+
reflector models and deduplication thresholds are now honored.
24+
25+
Historical audit rows that lack a generated target ID or required inverse state
26+
cannot be reliably undone. Rollback refuses those batches before a partial write
27+
rather than claiming success. An old content update without a saved embedding
28+
clears the mismatched vector on rollback; it cannot recreate that historical
29+
embedding. Old receipt rows cannot recover consumption history never recorded.
30+
See [embedding-backfill.md](embedding-backfill.md) for the optional, explicit,
31+
bounded repair of missing vectors using the correct historical embedding model.
32+
33+
The distribution is `engram-contextdb`; the Python import and CLI remain `engram`.
34+
MCP 1.x is the supported dependency range. Installed-package checks build from an
35+
sdist, install the resulting wheel outside the checkout, run the CLI, and exercise
36+
the stdio protocol. These checks do not publish a corrected package or validate
37+
native third-party sign-in. All fixes remain local until released.

‎docs/embedding-backfill.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Repair missing embeddings
2+
3+
Use the corrected source checkout to repair existing active memories that have no embedding. This does not repeat extraction, change text, or create new memories. The current database schema uses 1536-dimensional embeddings; choose the same embedding model used for the existing vectors.
4+
5+
Preview one context in an existing SQLite database:
6+
7+
```bash
8+
.venv/bin/python -m engram.maintenance.backfill_embeddings \
9+
--sqlite /absolute/path/to/engram.db \
10+
--context-id CONTEXT_UUID --limit 100
11+
```
12+
13+
For PostgreSQL, put the existing database DSN in an environment variable and pass its **name**:
14+
15+
```bash
16+
.venv/bin/python -m engram.maintenance.backfill_embeddings \
17+
--postgres-dsn-env ENGRAM_MAINTENANCE_DSN \
18+
--context-id CONTEXT_UUID --limit 100
19+
```
20+
21+
Preview opens the existing database read-only. It does not initialize schemas, create the vector extension, change SQLite journal mode, load `.env`, or call an embedding provider. SQLite may create WAL coordination sidecars when opening an existing WAL database; its database content and journal mode remain unchanged. There is no default database target. A missing database or context fails instead of creating one.
22+
23+
To save embeddings, add the explicit apply and provider options to either command:
24+
25+
```bash
26+
--apply --embedding-model text-embedding-3-small \
27+
--embedding-key-env ENGRAM_EMBEDDING_API_KEY
28+
```
29+
30+
Set the named provider-key environment variable before running apply. Apply makes provider requests and may incur usage charges. The limit is between 1 and 1000 and bounds attempted memories per invocation; it defaults to 100. The command is an operator tool using database access, not a new unauthenticated HTTP endpoint.
31+
32+
The JSON summary reports eligible, selected, attempted, updated, skipped, and failed counts. Exit status is 0 for a successful preview/apply, 1 for an operational or per-memory failure, and 2 for invalid command options. Errors identify a memory and a reason without printing provider messages, credentials, or memory text.
33+
34+
Each vector is computed outside a database transaction. Before saving, the command rechecks context, content, lifecycle, and whether a vector is still missing. Concurrent edits or an already-completed repair are skipped. Only the embedding column changes; IDs, text, timestamps, metadata, counters, and audit history remain intact. Archived and inactive memories are excluded. Both backends validate the float32 representation used by PostgreSQL, rejecting empty, nonfinite, zero, underflow-to-zero, overflowing, and wrong-dimension vectors. Squared norms must also remain finite and nonzero in float32 so repaired vectors work with cosine search; vector scale is preserved.
35+
36+
Repairs commit one memory at a time. If a later provider request fails, earlier successful repairs remain saved. Re-run the same command to repair remaining items; completed items are skipped. This tool cannot determine the model used for historical vectors, so model compatibility must be selected by the operator.
37+
38+
Validation is isolated: `pytest tests/test_embedding_backfill.py` uses temporary SQLite databases and fake providers. Setting `ENGRAM_TEST_POSTGRES_DSN` to a disposable test admin database also exercises PostgreSQL in newly created random databases. No repair was run against an existing developer or customer database during implementation.

0 commit comments

Comments
 (0)