Persistent, queryable memory for AI coding agents
Project description
Enyal
Persistent, queryable memory for AI coding agents.
Enyal gives AI agents like Claude Code durable context that survives session restarts. Every conversation becomes accumulated institutional knowledge—facts, preferences, decisions, and conventions that persist and grow.
Features
- Persistent Memory: Context survives restarts, crashes, and process termination
- Semantic Search: Find relevant context using natural language queries (768-dim embeddings via nomic-embed-text-v1.5)
- Knowledge Graph: Link related entries with relationships (supersedes, depends_on, conflicts_with, relates_to)
- Validity Tracking: Automatically filter superseded entries and flag conflicts
- Entry Versioning: Full history of changes with automatic version creation
- Usage Analytics: Track how context is accessed and used over time
- Health Monitoring: Get insights into stale, orphan, and conflicting entries
- Hierarchical Scoping: Global → workspace → project → file context inheritance
- Fully Offline: Zero network calls during operation
- Cross-Platform: macOS (Intel + Apple Silicon), Linux, and Windows
- MCP Compatible: Works with Claude Code, Cursor, Windsurf, Kiro, and any MCP client
Quick Start
Get up and running in under 2 minutes:
1. Install
# Using uv (recommended)
uv pip install enyal --system
# Or using pip
pip install enyal
2. Configure Your MCP Client
Universal configuration (works with Claude Code, Cursor, Windsurf, Kiro):
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"]
}
}
}
For macOS Intel users (requires Python 3.11 or 3.12):
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["--python", "3.12", "enyal", "serve"]
}
}
}
3. Start Using
You: Remember that this project uses pytest for all testing
Assistant: [calls enyal_remember] Stored context about testing framework
You: What testing framework should I use?
Assistant: [calls enyal_recall] Based on stored context, this project uses pytest.
Platform Support
| Platform | Python 3.11 | Python 3.12 | Python 3.13 |
|---|---|---|---|
| macOS Apple Silicon | Supported | Supported | Supported |
| macOS Intel | Supported | Supported | Not supported* |
| Linux | Supported | Supported | Supported |
| Windows | Supported | Supported | Supported |
*macOS Intel + Python 3.13 is not supported due to PyTorch ecosystem constraints.
Installation Methods
Method 1: uv (Recommended)
# Install globally
uv pip install enyal --system
# Run server
enyal serve
# With model preloading for faster first query
enyal serve --preload
Method 2: pip
# Install globally
pip install enyal
# Run server
enyal serve
Method 3: pipx
# Install in isolated environment
pipx install enyal
# Run server
enyal serve
Method 4: uvx (Run without installing)
For ephemeral execution without permanent installation:
# Most platforms (auto-selects Python)
uvx enyal serve
# macOS Intel (explicit Python version)
uvx --python 3.12 enyal serve
Note: uvx runs the package in a temporary environment each time. For persistent installation, use Method 1, 2, or 3.
MCP Integration
Enyal works with any MCP-compatible client. The configuration is the same across platforms—only the command may vary for macOS Intel.
Claude Code
File locations:
- Project:
.mcp.json(in project root) - User:
~/.claude/.mcp.json
Standard configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
}
}
}
}
macOS Intel configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["--python", "3.12", "enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
}
}
}
}
CLI setup:
# Standard
claude mcp add-json enyal '{"command":"uvx","args":["enyal","serve"]}'
# macOS Intel
claude mcp add-json enyal '{"command":"uvx","args":["--python","3.12","enyal","serve"]}'
Claude Desktop
File locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
}
}
}
}
Cursor
File locations:
- Global:
~/.cursor/mcp.json - Project:
.cursor/mcp.json
Configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
}
}
}
}
UI setup: File → Preferences → Cursor Settings → MCP
Windsurf
File location: ~/.codeium/windsurf/mcp_config.json
Configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
}
}
}
}
UI setup: Windsurf Settings → Cascade → MCP, or use the Plugin Store
Kiro
File locations:
- Global:
~/.kiro/settings/mcp.json - Project:
.kiro/settings/mcp.json
Configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_DB_PATH": "~/.enyal/context.db"
},
"autoApprove": ["enyal_recall", "enyal_stats", "enyal_get"]
}
}
}
UI setup: Click the Kiro ghost tab → MCP Servers → "+"
See docs/INTEGRATIONS.md for detailed platform-specific guides.
Available Tools
Core Tools
| Tool | Description |
|---|---|
| enyal_remember | Store new context with optional duplicate detection, conflict detection, and auto-linking |
| enyal_recall | Semantic search with validity filtering (excludes superseded entries by default) |
| enyal_recall_by_scope | Scope-aware search that automatically finds context relevant to the current file/project |
| enyal_forget | Remove or deprecate context (soft-delete by default, hard-delete optional) |
| enyal_update | Update existing entries (content, confidence, tags) - automatically creates version |
| enyal_get | Retrieve a specific entry by ID with full metadata |
| enyal_stats | Get usage statistics and health metrics |
Knowledge Graph Tools
| Tool | Description |
|---|---|
| enyal_link | Create relationships between entries (relates_to, supersedes, depends_on, conflicts_with) |
| enyal_unlink | Remove a relationship between entries |
| enyal_edges | Get all relationships for an entry |
| enyal_traverse | Walk the knowledge graph from an entry |
| enyal_impact | Find all entries that depend on a given entry |
Intelligence Tools
| Tool | Description |
|---|---|
| enyal_health | Get graph health metrics (stale, orphan, conflicting entries) |
| enyal_review | Get entries needing review (stale, orphan, or conflicted) |
| enyal_history | Get version history for an entry |
| enyal_analytics | Get usage analytics (recall frequency, top accessed entries) |
Content Types
| Type | Use For | Example |
|---|---|---|
fact |
Objective information | "The database uses PostgreSQL 15" |
preference |
User/team preferences | "Prefer tabs over spaces" |
decision |
Recorded decisions | "Chose React over Vue for frontend" |
convention |
Coding standards | "All API endpoints follow REST naming" |
pattern |
Code patterns | "Error handling uses Result<T, E> pattern" |
Scope Levels
| Scope | Applies To | Example Path |
|---|---|---|
global |
All projects | (none) |
workspace |
Directory of projects | /Users/dev/projects |
project |
Single project | /Users/dev/myproject |
file |
Specific file | /Users/dev/myproject/src/auth.py |
Relationship Types
| Type | Use For | Example |
|---|---|---|
relates_to |
General semantic relationship | "Testing guide" relates to "pytest conventions" |
supersedes |
Entry A replaces entry B | New decision supersedes old decision |
depends_on |
Entry A requires entry B | Feature depends on architecture decision |
conflicts_with |
Entries contradict each other | "Use tabs" conflicts with "Use spaces" |
CLI Usage
Enyal provides a command-line interface for direct interaction:
# Store context
enyal remember "Always use pytest for testing" --type convention --scope project
# Search context
enyal recall "testing framework" --limit 5
# Get entry details
enyal get <entry-id>
# View statistics
enyal stats
# Remove context
enyal forget <entry-id>
# Run MCP server
enyal serve --preload
Options:
--db PATH— Custom database path--json— Output in JSON format
See docs/CLI.md for complete CLI reference.
Python Library
from enyal.core.store import ContextStore
from enyal.core.retrieval import RetrievalEngine
from enyal.models.context import ContextType, ScopeLevel
# Initialize store
store = ContextStore("~/.enyal/context.db")
retrieval = RetrievalEngine(store)
# Remember something
entry_id = store.remember(
content="Always use pytest for testing in this project",
content_type=ContextType.CONVENTION,
scope_level=ScopeLevel.PROJECT,
scope_path="/Users/dev/myproject",
tags=["testing", "pytest"]
)
# Remember with duplicate detection
result = store.remember(
content="Use pytest for all testing", # Similar to existing
check_duplicate=True, # Enable duplicate checking
duplicate_threshold=0.85, # Similarity threshold
on_duplicate="reject" # "reject", "merge", or "store"
)
# Returns dict: {"entry_id": "...", "action": "existing", "similarity": 0.92}
# Recall relevant context (hybrid semantic + keyword search)
results = retrieval.search(
query="how should I write tests?",
limit=5,
min_confidence=0.5
)
for result in results:
print(f"{result.score:.2f}: {result.entry.content}")
# Scope-aware search (file → project → workspace → global)
results = retrieval.search_by_scope(
query="testing conventions",
file_path="/Users/dev/myproject/src/auth.py",
limit=5
)
# Find similar entries (useful for deduplication checks)
similar = store.find_similar(
content="pytest testing conventions",
threshold=0.8,
limit=3
)
# Update context (automatically creates a version record)
store.update(entry_id, confidence=0.9, tags=["testing", "pytest", "unit-tests"])
# Get specific entry
entry = store.get(entry_id)
# Get statistics
stats = store.stats()
print(f"Total entries: {stats.total_entries}")
Knowledge Graph
from enyal.models.context import EdgeType
# Create entries
old_decision = store.remember(content="Use Python 3.10", content_type="decision")
new_decision = store.remember(content="Use Python 3.13", content_type="decision")
# Link entries (new supersedes old)
store.link(new_decision, old_decision, EdgeType.SUPERSEDES)
# Search with validity filtering (superseded entries excluded by default)
results = retrieval.search("Python version", exclude_superseded=True)
# Include superseded entries with metadata
results = retrieval.search("Python version", exclude_superseded=False)
for r in results:
if r.is_superseded:
print(f"SUPERSEDED: {r.entry.content} (by {r.superseded_by})")
# Traverse the graph
related = store.traverse(new_decision, max_depth=2)
# Find what depends on an entry
dependents = store.traverse(new_decision, direction="incoming",
edge_types=[EdgeType.DEPENDS_ON])
Versioning & History
# Every remember() creates an initial version
entry_id = store.remember(content="Initial approach", content_type="decision")
# Every update() creates a new version
store.update(entry_id, content="Revised approach")
store.update(entry_id, content="Final approach")
# Get version history
history = store.get_history(entry_id)
for version in history:
print(f"v{version['version']}: {version['change_type']} - {version['content']}")
# Output:
# v3: updated - Final approach
# v2: updated - Revised approach
# v1: created - Initial approach
Analytics & Health
# Track usage (called automatically during recall)
store.track_usage(entry_id, "recall", query="approach", result_rank=1)
# Get analytics
analytics = store.get_analytics(days=30)
print(f"Top recalled: {analytics['top_recalled']}")
# Health check
health = store.health_check()
print(f"Health score: {health['health_score']:.0%}")
print(f"Stale entries: {health['stale_entries']}")
print(f"Orphan entries: {health['orphan_entries']}")
print(f"Conflicts: {health['unresolved_conflicts']}")
# Get entries needing review
stale = store.get_stale_entries(days_old=180)
orphans = store.get_orphan_entries()
conflicts = store.get_conflicted_entries()
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
ENYAL_DB_PATH |
~/.enyal/context.db |
Database file location |
ENYAL_PRELOAD_MODEL |
false |
Pre-load embedding model at startup |
ENYAL_LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
ENYAL_SSL_CERT_FILE |
(system) | Path to CA certificate bundle (for corporate networks) |
ENYAL_SSL_VERIFY |
true |
Enable/disable SSL verification (set false only as last resort) |
ENYAL_MODEL_PATH |
(none) | Path to local pre-downloaded model |
ENYAL_HF_ENDPOINT |
(none) | Custom HuggingFace Hub endpoint URL (e.g., Artifactory proxy) |
ENYAL_OFFLINE_MODE |
false |
Prevent network calls (use with cached/local model) |
Database Location
The default database is stored at ~/.enyal/context.db. This single SQLite file contains:
- All context entries and metadata
- Vector embeddings for semantic search
- Full-text search index
Troubleshooting
Installation Fails on macOS Intel
Symptom: Error about torch/PyTorch wheels not found
Cause: PyTorch doesn't provide wheels for macOS Intel + Python 3.13
Solution: Use Python 3.11 or 3.12:
# Install with specific Python version
uv pip install enyal --python 3.12 --system
MCP Server Not Connecting
-
Check uvx is available:
uvx --version -
Test server manually:
uvx enyal serve # Should start without errors, waiting for MCP protocol
-
Enable debug logging:
{ "mcpServers": { "enyal": { "command": "uvx", "args": ["enyal", "serve", "--log-level", "DEBUG"] } } }
-
Check server status:
- Claude Code:
/mcpcommand - Cursor: Settings → MCP → check status
- Windsurf: Cascade → Plugins
- Kiro: Ghost tab → MCP Servers
- Claude Code:
Slow First Query
The first query loads the embedding model (~80MB). This takes ~1-2 seconds. Subsequent queries are fast (~34ms).
To pre-load the model at startup:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve", "--preload"]
}
}
}
Database Locked Error
If you see "database is locked" errors, ensure only one MCP server instance is running per database file. Use different ENYAL_DB_PATH values for different projects if needed.
Permission Errors
On macOS/Linux, ensure the database directory exists and is writable:
mkdir -p ~/.enyal
chmod 755 ~/.enyal
SSL Certificate Errors (Corporate Networks)
Symptom: Error containing "SSL: CERTIFICATE_VERIFY_FAILED" or "self signed certificate in certificate chain"
Cause: Corporate networks with SSL inspection (Zscaler, BlueCoat, etc.) inject enterprise CA certificates that Python doesn't recognize by default.
Quick Fix:
# Option 1: Point to your corporate CA bundle (recommended)
export ENYAL_SSL_CERT_FILE=/path/to/corporate-ca-bundle.crt
enyal model download
# Option 2: Pre-download model on unrestricted network, then use offline
export ENYAL_OFFLINE_MODE=true
For MCP configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_SSL_CERT_FILE": "/path/to/corporate-ca-bundle.crt"
}
}
}
}
Check your SSL configuration:
enyal model status
See docs/SSL_TROUBLESHOOTING.md for detailed troubleshooting guide.
Custom Model Registry (Artifactory)
If your organization uses Artifactory or another proxy to mirror HuggingFace models, set ENYAL_HF_ENDPOINT to redirect all model downloads:
export ENYAL_HF_ENDPOINT=https://artifactory.corp.com/artifactory/api/huggingface
enyal model download
For MCP configuration:
{
"mcpServers": {
"enyal": {
"command": "uvx",
"args": ["enyal", "serve"],
"env": {
"ENYAL_HF_ENDPOINT": "https://artifactory.corp.com/artifactory/api/huggingface",
"ENYAL_SSL_CERT_FILE": "/path/to/corporate-ca-bundle.crt"
}
}
}
}
When ENYAL_HF_ENDPOINT is set, Enyal automatically:
- Sets
HF_ENDPOINTbeforehuggingface_hubis imported - Disables HF Xet storage (
HF_HUB_DISABLE_XET=1) since Xet bypasses HTTP proxies - Increases download timeouts for first-fetch latency through the proxy
No additional authentication configuration is needed — Artifactory handles upstream auth transparently.
Architecture
Enyal uses a unified SQLite database with:
- Relational storage for metadata and attributes
- sqlite-vec for vector similarity search (384-dim embeddings)
- FTS5 for keyword search
- Knowledge graph with typed edges (supersedes, depends_on, conflicts_with, relates_to)
- Version history for change tracking
- Usage analytics for access patterns
- WAL mode for concurrent access
See docs/ARCHITECTURE.md for detailed design decisions.
Development
# Clone repository
git clone https://github.com/seancorkum/enyal.git
cd enyal
# Install with dev dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Type checking
uv run mypy src/enyal
# Linting
uv run ruff check src/enyal
Performance
Benchmarked on Intel Mac with Python 3.12:
| Metric | Target (p95) | Measured (p95) | Status |
|---|---|---|---|
| Cold start (model load + first query) | <2000ms | ~1500ms | ✓ |
| Warm query latency | <50ms | ~34ms | ✓ |
| Write latency | <50ms | ~34ms | ✓ |
| Concurrent reads (4 threads) | <150ms | ~85ms | ✓ |
| Memory (100k entries estimated) | <500MB | ~35MB | ✓ |
Embedding model: all-MiniLM-L6-v2 (22M params, 384 dimensions)
Run benchmarks:
uv run python benchmarks/benchmark_performance.py
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file enyal-0.7.5.tar.gz.
File metadata
- Download URL: enyal-0.7.5.tar.gz
- Upload date:
- Size: 291.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8b032b02949aaeaf6d4b8ef365b8a4b61602c808bbcc1a915cd238b33a61f9c
|
|
| MD5 |
bbfc7b2260b870910404c9d17e1d3017
|
|
| BLAKE2b-256 |
6d898bba19bf02ee91fe136d6952adbec521c447228b70d485c4b87ee8fef3ab
|
Provenance
The following attestation bundles were made for enyal-0.7.5.tar.gz:
Publisher:
publish.yml on S-Corkum/enyal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
enyal-0.7.5.tar.gz -
Subject digest:
b8b032b02949aaeaf6d4b8ef365b8a4b61602c808bbcc1a915cd238b33a61f9c - Sigstore transparency entry: 963092600
- Sigstore integration time:
-
Permalink:
S-Corkum/enyal@1864fdfd94f2e66ee62be5e936b39f261ac98ef0 -
Branch / Tag:
refs/tags/0.7.5 - Owner: https://github.com/S-Corkum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1864fdfd94f2e66ee62be5e936b39f261ac98ef0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file enyal-0.7.5-py3-none-any.whl.
File metadata
- Download URL: enyal-0.7.5-py3-none-any.whl
- Upload date:
- Size: 68.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
089bdec405aa0d13adff258ae9ff4b3efee9d3fe66831f36e9c93d8c2786cb26
|
|
| MD5 |
947993afbbb285e566af6a630116c171
|
|
| BLAKE2b-256 |
7f89475be996a2d8cae935854195f92c98c346f221cc8ac7eb3a3ff3b015fd00
|
Provenance
The following attestation bundles were made for enyal-0.7.5-py3-none-any.whl:
Publisher:
publish.yml on S-Corkum/enyal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
enyal-0.7.5-py3-none-any.whl -
Subject digest:
089bdec405aa0d13adff258ae9ff4b3efee9d3fe66831f36e9c93d8c2786cb26 - Sigstore transparency entry: 963092617
- Sigstore integration time:
-
Permalink:
S-Corkum/enyal@1864fdfd94f2e66ee62be5e936b39f261ac98ef0 -
Branch / Tag:
refs/tags/0.7.5 - Owner: https://github.com/S-Corkum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1864fdfd94f2e66ee62be5e936b39f261ac98ef0 -
Trigger Event:
release
-
Statement type: