Enable Permission System
Set the environment variable to enable access control:Auto-enable behavior
WhenENABLE_BACKEND_ACCESS_CONTROL is not explicitly set, Cognee automatically enables multi-user mode if the configured graph and vector setup passes the runtime compatibility checks.
At a high level, that means both of the following must be true:
- The configured graph dataset handler is supported and matches the selected graph provider.
- The configured vector dataset handler is supported and matches the selected vector provider.
ENABLE_BACKEND_ACCESS_CONTROL=false to keep single-user mode regardless of which databases are configured.
For the supported backend combinations and handler details, see Security & Privacy and Dataset Database Handlers.
Dataset Queue
When backend access control is enabled, Cognee can limit the number of dataset-level operations that run concurrently. This caps overall concurrent dataset work and can reduce contention when many tasks access datasets at the same time. The queue is enabled by default and is automatically disabled whenENABLE_BACKEND_ACCESS_CONTROL=false.
Engine cache pinning
Cognee caches per-dataset graph and vector engines in a fixed-size LRU cache sized byDATABASE_MAX_LRU_CACHE_SIZE. While a dataset holds a queue slot, its graph and vector engines are pinned and are not evicted by capacity pressure — even if they are the least-recently-used entries. This prevents a dataset that an admitted pipeline is still using (for example, one idling on an LLM call mid-cognify) from having its engine closed underneath it, which was a source of DB lock and cache-overflow errors.
As a result, when every cached entry is pinned, the engine cache can briefly exceed DATABASE_MAX_LRU_CACHE_SIZE. This overflow is bounded by DATASET_QUEUE_MAX_CONCURRENT (no more datasets than the queue admits can be pinned at once).
When the queue is disabled (DATASET_QUEUE_ENABLED=false, or ENABLE_BACKEND_ACCESS_CONTROL=false), nothing is pinned and eviction falls back to plain least-recently-used recency.
Subprocess engine teardown coordination
When subprocess-mode databases are in use (graph_database_subprocess_enabled=true or vector_db_subprocess_enabled=true), the queue also coordinates the release of the cached per-dataset graph and vector engines on dataset-context exit. This only runs once the exiting task is the last holder of that dataset’s queue slot — so an in-flight task that still has the dataset open will not observe a torn-down engine.
Releasing hands the engines back to the engine cache, which then either keeps them warm for the subprocess idle keep-alive window or evicts them. Eviction itself is synchronous, so a dying engine leaves the cache before the queue slot is freed and the next caller can never fetch it. The engine’s close() is not synchronous: for subprocess-backed adapters the cache dispatches it onto its own dedicated close threads. A search or recall response therefore returns without waiting for the worker process to exit and drop its file lock.
Safety is preserved by a pending-close latch rather than by blocking the caller. From the moment a close starts until it has fully completed (worker exited, lock released), the cache records it as pending for that engine, and the next creation of the same engine — through the queue or through a direct get_graph_engine() / vector equivalent — waits for it: async callers suspend, and synchronous callers on a thread with no running event loop block. Every such wait is bounded at 300 seconds; on timeout Cognee logs a warning, stops waiting, and falls back to the worker’s own open retries (SUBPROCESS_OPEN_LOCK_RETRIES and SUBPROCESS_OPEN_LOCK_BACKOFF, see Graph Stores). One case deliberately does not wait — a synchronous caller already running on an event loop, since the close may need that same loop to make progress — and there the open retries are the only backstop.
Teardown problems are logged, never surfaced to the caller: a close() that raises is logged as a warning with its traceback, an overrun wait logs the warning above, broken close bookkeeping is logged at ERROR, and no engine creation, eviction, or idle sweep fails over any of it. If you see repeated lock-contention errors when opening a database, check the logs for these teardown warnings first.
What that last-holder release does is controlled by SUBPROCESS_IDLE_TTL_SECONDS:
- TTL greater than
0(default600) — the engine is kept warm: instead of being closed, its idle timestamp is refreshed, and a background reaper closes it only after a full TTL with no use. A request for the same dataset arriving inside that window reuses the live worker and skips both the close and the respawn. - TTL
0— the engine is evicted and force-closed at the last-holder release. This is the behavior that applied before the keep-alive existed, and setting0is how you restore it.
0, and fractional values are accepted.
The reaper is a daemon thread (subprocess-idle-reaper) that starts lazily on the first kept-alive release and then sweeps every engine cache every max(5, min(60, TTL / 4)) seconds. The sweep skips two kinds of entries:
- Datasets that currently hold a queue slot — the same pin that protects them from capacity eviction (see Engine cache pinning above), so an operation running longer than the TTL cannot have its engine closed underneath it.
- Engines that are not subprocess-backed. Remote stores such as Neo4j, Postgres, and PGVector hold no worker process and no file lock, so they are never reaped and keep plain LRU behavior.
DATABASE_MAX_LRU_CACHE_SIZE). Lower the TTL, or set it to 0, if you need locks and memory released promptly — for example when another tool needs to open the same data directory, or on memory-tight multi-tenant deployments.
If you set DATASET_QUEUE_ENABLED=false while leaving subprocess mode on, there is no teardown at all, and SUBPROCESS_IDLE_TTL_SECONDS has no effect: the release path — and with it both the keep-alive refresh and the reaper — never runs. Subprocess engines are not closed when a dataset context exits, and the database file’s flock() will remain held until the cached engine is closed or evicted, or until the worker process shuts down. Keep the queue enabled when running with subprocess databases under concurrent multi-dataset workloads.
Database Setup
Choose your relational database:- SQLite — Local development (auto-creates files)
- Postgres — Production (requires manual setup)
Authentication
API Server
Start the server with authentication:- Username:
default_user@example.com - Password:
default_password
Programmatic Access
See Permission Snippets for complete programmatic examples.Data Organization
Data is automatically organized by user and dataset. Each user gets isolated storage:Troubleshooting
Permission denied
Permission denied
If a request fails with a permission error:
- Confirm the request is authenticated as the expected user.
- Confirm the target dataset belongs to that user, or has been shared with them.
- If you are testing locally, verify
REQUIRE_AUTHENTICATION=trueandENABLE_BACKEND_ACCESS_CONTROL=truematch the mode you expect.
Data isolation
Data isolation
With access control enabled, Cognee stores graph and vector data per user and per dataset. If data appears to leak across users or is missing unexpectedly:Different users should have different database paths and dataset files.
- Verify
ENABLE_BACKEND_ACCESS_CONTROL=true. - Verify you are reading and writing as the intended authenticated user.
- Check that separate user-specific database files exist on disk:
401/403 on add or search
401/403 on add or search
When access control is enabled,
VECTOR_DB_PROVIDER and VECTOR_DATASET_DATABASE_HANDLER must resolve to a compatible pair. For the built-in providers below, leaving the handler at its default lets Cognee auto-select the matching handler. Requests fail when you explicitly choose an incompatible handler.Example — using PGVector with access control:
Local Neo4j + multi-user mode: provider/handler mismatch error
Local Neo4j + multi-user mode: provider/handler mismatch error
Symptom: Cognee raises an
EnvironmentError about a graph provider/handler mismatch when GRAPH_DATABASE_PROVIDER=neo4j and ENABLE_BACKEND_ACCESS_CONTROL=true.Root cause: A plain Neo4j connection is not supported for multi-user mode. Cognee’s runtime check validates that the configured graph provider matches a supported dataset database handler (supported_dataset_database_handlers); providers that pass with their default handler are ladybug/kuzu, postgres_demo (or its accepted alias postgres), and turso. Neo4j is only supported in multi-user mode through a dataset database handler — neo4j_aura_dev (provisions a Neo4j Aura cloud instance per dataset) or neo4j_community (runs a local Neo4j Community Docker container per dataset) — so enabling ENABLE_BACKEND_ACCESS_CONTROL=true with GRAPH_DATABASE_PROVIDER=neo4j and no handler leads to this error.- Single-User Local Neo4j
- Multi-User with Neo4j Aura
- Multi-User with Local Docker
Recommended for self-hosted Neo4j deployments:
Permission System
Learn about users, tenants, roles, and ACL
Usage Guide
How to use permission features