- Status: Accepted (revised after Codex dissent + Gemini Q2 dispute, 2026-04-18)
- Date: 2026-04-18
memory-hall is initially built for one user (the author), but the intent is to release it for others to self-host — and for some users to grant access to friends, teammates, or their own bots.
Adding tenancy after the fact to a memory layer is expensive: every row, every index, every query, every cache key, every test fixture changes. Worse, leaks during retrofit can expose data across tenants in ways that are hard to detect.
Codex (decision dissent) challenged that committing to multi-tenant runtime in v0.1 dilutes the core goal of "stop the daily mem0 wall." Gemini (architecture review Q2) challenged that strict middleware-only resolution blocks legitimate admin / cross-tenant audit workflows.
Both points are valid. This revision separates schema commitment from runtime enforcement scope.
tenant_id is on every table, every index, every query. No schema migration when multi-tenant runtime is enabled.
v0.1 does not ship a tenant-creation API or per-tenant key issuance. Every authenticated principal maps to the default tenant. The middleware enforces this — no caller can write into another tenant.
Rationale: the engineering complexity of multi-tenant runtime (key issuance, ACL surface, tenant isolation testing breadth) is real, and adding it before the engine is proven on the author's own use case dilutes the core goal. v0.1 ships single-tenant; v0.2 lifts the lid.
For batch imports, audit, and cross-tenant maintenance, principals with role: admin may pass an X-Tenant-Override header. The middleware honors it only when the principal claim contains admin; otherwise the override is silently ignored and the principal's own tenant_id is used.
This addresses Gemini's Q2 dispute without compromising the principal-derived-tenant safety property.
- No schema migration when multi-tenant is enabled in v0.2.
- Tenant isolation testable from day one (
test_tenant_isolation.pyis the first integration test, per Max's recommendation). - v0.1 stays focused on the engine's core promise.
- Admin override is a clean future extension, not a retrofit.
- ~10–15% extra code in v0.1 that does no functional work yet (every CRUD path threads
tenant_id, but only one value is ever used). tests/test_tenant_isolation.pywill run against a synthetic second tenant in v0.1 to validate isolation, even though the runtime doesn't accept user-defined tenants.
- Single-tenant deployments incur trivial overhead (one fixed string in queries).
| Option | Why rejected |
|---|---|
| No tenant in v0.1 schema, retrofit later | Schema and middleware retrofit is the most error-prone path for memory layers. Cross-tenant leaks during retrofit are catastrophic. |
| Per-tenant SQLite database files | Operationally expensive (many SQLite files), no good answer for shared vector store. |
URL path tenancy (/v1/tenants/{tid}/memory/...) |
Caller-supplied tenant in URL is easy to spoof. Auth-principal-derived tenant is safer. |
| Strict middleware-only resolution, no admin override | Blocks legitimate admin workflows (Gemini Q2). Forces hacks (direct DB access, separate admin daemon) that are worse than a controlled override. |
- Tenant resolution lives in
server/middleware/tenant.py. It runs before route handlers. - Routes never see raw
tenant_idfrom the request body; they receive a validated value fromrequest.state.tenant_id. - v0.1 middleware: principal →
defaulttenant, always.X-Tenant-Overrideheader rejected with 400 if present. - v0.2 middleware: principal → claim tenant.
X-Tenant-Overridehonored only when claim hasrole: admin.
- Codex Dissent (private):
~/Documents/agent-council/codex-answer-mh-dissent.md— "multi-tenant from day one in v0.1 over-promises" - Gemini Architecture Review (private):
~/Documents/agent-council/gemini-answer-mh-architecture.mdQ2 — admin override proposal - Max Best Practice (private):
~/Documents/agent-council/max-answer-mh-research.mdQ4 — JWT claim pattern