Releases: scops/engrama
engrama v0.13.0
[0.13.0] — 2026-05-28
Breaking — pre-1.0 minor bump (semver
0.x). This release introduces
fail-closed multi-tenant scoping. Per Constitution P9 it is a
MAJOR-equivalent change; while the package is pre-1.0 that is expressed as a
minor bump with migration notes (OQ-10, resolved 2026-05-28).0.12.0was the
earlier non-breaking docs/latency release, so the breaking work takes0.13.0.
1.0.0is reserved for when the tenancy/sharing model is declared stable.
Breaking (Spec 001 — multi-tenant identity-scoped memory)
All reads and writes are now keyed by
(org_id, user_id). A node or relation
that doesn't carry both is invisible to every scope. Existing installations
MUST run the migration command below before serving traffic on the new
release — otherwise pre-existing data appears to have vanished. Detailed
design lives underspecs/001-tenant-scoped-memory/.
- Fail-closed read filter. Every read path (
engrama_search,
engrama_context,engrama_reflect,engrama_surface_insights,
engrama_approve_insight,engrama_write_insight_to_vault, the seven
reflect detectors, both backends'count_labels/lookup_node_label/
Insight queries /list_existing_nodes) filters by the resolved scope.
Incomplete scope ⇒ zero results, never "see-all" (FR-5). - Write-identity guard.
EngramaEngine.merge_nodeand
EngramaEngine.merge_relationraiseScopeIncompletewhen the effective
scope is missing or partial; the MCP boundary translates the request-time
ScopeUnresolvedinto an explicitstatus: "error"for every write tool
(engrama_remember,engrama_relate,engrama_ingest,engrama_sync_note,
engrama_sync_vault,engrama_reflect,engrama_approve_insight,
engrama_write_insight_to_vault,engrama_reindex). Identity-less writes
no longer touch the graph or the vault. - Per-request scope resolver.
resolve_scope(ctx)reads
X-Engrama-Org-Id/X-Engrama-User-Idrequest headers; both headers
present resolves to that scope, both absent falls back to a stable
per-installsub_localidentity (zero-config standalone, FR-7), exactly
one present raisesScopeUnresolved(no silent broadening, FR-3). - Relations carry identity. Every edge persists
(org_id, user_id)at
write time, so a future relation-scoped read can filter without re-walking
endpoints (FR-1). SQLite gains nullableedges.org_id/edges.user_id
columns plusidx_edges_scope— an idempotentALTER TABLEruns on
connect for pre-Spec-001 databases. - SDK scope completion.
Engrama(...)zero-config resolves to
(sub_local, sub_local). A loneuser_id(or loneorg_id) is mirrored
into the other dimension. A provenance-only override
(session_id="…"/agent_id="…"without identity) still falls back to
sub_localfor(org_id, user_id)while keeping the provenance through. - Composite Neo4j indexes. A per-label range index on
(n.org_id, n.user_id)for every queried label, in both
engrama/backends/neo4j/schema.cypherandscripts/init-schema.cypher
(R-6 / NFR-3). - Scoped logging context. A
contextvars.ContextVarplus
logging.Filterinjects hashed (sha256[:8])scope_org/scope_user
fields onto every log record emitted inside a request, so an aggregator
can group lines by tenant without leaking raw IDs (T009a / NFR-2). - CI scope guard.
scripts/check_scoped_queries.pywalks the backend
modules and flags anyMATCH (/FROM nodes/FROM edgesliteral
whose enclosing function neither routes through the scope helper, sits in
an auto-exempt write/admin family, nor carries an inline
# scope-exempt: <reason>comment. Wired into CI as a warn-only
scope-guardjob — flipped to blocking once Phases 5–7 land. - Migration CLI.
engrama migrate tenancy --owner-sub <sub> [--dry-run|--apply]backfills(owner_sub, owner_sub)onto every
identity-less node and relation and purges true orphans. Mandatory
dry-run first;--report path.jsonwrites an auditable record. Required
on any installation that pre-dates this release.
How to upgrade
- Stop the MCP server.
- Optional: back up the SQLite DB (
~/.engrama/engrama.db) or Neo4j volume. - Pick the identity to stamp pre-existing data with — usually the value of
ENGRAMA_LOCAL_SUB(or the contents of~/.engrama/local_sub). engrama migrate tenancy --owner-sub <sub> --dry-run— review the
counts. Re-run with--applyonce they look right.- Restart. Reads return data again.
Added
- Streamable HTTP transport for the MCP server. Switchable via
ENGRAMA_TRANSPORT=http(default staysstdio, so existing Claude
Desktop setups are untouched). Binds loopback127.0.0.1:8000with the
MCP endpoint at/mcp, runs stateful so conversational clients keep an
Mcp-Session-Id, and validatesOrigin/Host(DNS-rebinding guard,
configurable viaENGRAMA_ALLOWED_ORIGINS). Adds a/healthprobe and
a/.well-known/oauth-protected-resource(RFC 9728) stub wired for the
upcoming OAuth phase viaENGRAMA_AUTH_ISSUER. No authentication yet —
intended for local use only. See the Streamable HTTP guide. engrama_reindextool — repairs nodes that are missing their vector
embedding (e.g. written while the embedder was unreachable, or predating
embeddings). Three phases run as separate calls:detect(scan, read-only),
classify(which candidates have embeddable text), andapply
(dry_rundefaults totrue). Heals legacy/corrupted nodes so they become
semantically searchable again.
Fixed
- Silent embedding corruption on write. When an embedder was configured
but unreachable at write time (Ollama / OpenAI-compatible cold-start,
restart, or network blip),engrama_rememberpersisted the node without a
vector yet still returnedstatus: "ok"— leaving it permanently invisible
to semantic search with no signal to anyone. The write path is now honest:
the response reportsembedded: true|false(with anembedding_notewhen a
vector was deferred), failures log a WARNING that includes theengrama_id,
and a vector-less node is healed automatically by an opportunistic sweep on
the next successful write — or on demand viaengrama_reindex. Proactive
writes are still never lost: the node always persists. - Inline relations report rejected relation types.
engrama_remember's
inlinerelations={...}used to skip a relation type that isn't in the schema
with only a server-side log, returningstatus: "ok"/relations_created: 0
— so the caller couldn't tell a relation had been dropped (the explicit
engrama_relatealready errors loudly on the same input). The response now
includesrelations_rejected(and a note) listing the skipped types, while
still persisting the node and any valid relations.