Skip to main content

Sulur — Graph-Based Persistent Memory for AI Agents

Your agents die every conversation. Sulur keeps them alive.

The persistent layer your LLMs run on top of. Memory formation, not retrieval. Background consolidation while they sleep. Conflict supersession when your mind changes. A knowledge-graph filesystem your agent walks instead of searches.


What this is

Most AI "memory" systems are retrieval wrappers.

They store chunks. Embed text. Run cosine similarity. Return vaguely related paragraphs.

Sulur is built around a different thesis:

Memory is not retrieval. Memory is formation, consolidation, synthesis, and evolving structure.

The engine continuously transforms raw conversations into a living cognitive graph, backed by Akar (pure-Rust embedded graph database, drop-in replacement for KuzuDB):

  • atomic facts,
  • semantic links,
  • supersession chains,
  • synthesized abstractions,
  • bridge memories,
  • latent preference structures,
  • temporal trajectories.

It does this locally. It works as a Hermes MemoryProvider adapter plugin (daemon-first). It survives across sessions.


Architecture — Daemon-First + Hermes Adapter

Sulur runs in daemon-first mode: a single sulur-server process (Sulur's own Rust binary) holds the exclusive graph DB lock on sulur.db (directory format), embedding the Akar engine (akar-main) in-process. All clients — Hermes adapter, MCP server, CLI, tests — connect via TCP loopback (JSON IPC) to that daemon. The graph backend is Akar (pure Rust, import kuzu aliased to import akar via sulur/kuzu.py).

Since Iterasi 4 (P4-RETIRE-1) the daemon is sulur-server, not the retired akar_server.exe broker — the wire protocol is unchanged (u32-LE framing + JSON), only who answers changed. Akar is now a 100% embedded library (SPEC §13); akar-server survives only as a test harness / wire reference.

HERMES AGENT PROCESS
  └─ hermes-plugins/sulur/client.py (thin adapter)
       • MemoryProvider ABC: initialize / prefetch / sync_turn / hooks
       • get_tool_schemas() → mcp_schemas.ALL_TOOL_SCHEMAS
       • handle_tool_call() → engine methods via daemon RPC
       • stdlib-only; delegates concurrency to the daemon
             │
             │ TCP loopback :<random-port> (JSON IPC, u32-LE framing)
             ▼
sulur-server PROCESS   (Rust binary, 1 per DB)
  • Tokio TCP listener on 127.0.0.1:<random-port>
  • embeds akar-main in-process: one Arc<Database> + exclusive file lock
  • serialised writes, concurrent reads
  • runs the 7-phase dream cycle as a background task (no leader election)
  • ops: query / ping / stats / flush / export / dream_control / recall / shutdown
  • announces via sidecar: sulur.db.daemon.json {host, port, token, pid}
             │
             ▼
      sulur.db/ (Akar graph directory, ~46 MB)
        ├── data.kz           (46,352 KB) — graph data
        ├── .wal              (115 KB)    — write-ahead log
        ├── catalog.kz        (13 KB)     — schema metadata
        ├── metadata.kz       (22 KB)     — storage metadata
        ├── n-*.hindex        (~20 KB ea) — vector indexes
        ├── .lock                          — graph DB internal lock
        └── .shadow                        — recovery shadow
      sulur.db.daemon.json     — sidecar (host, port, token, pid)
      sulur.db.daemon.lock     — OS-level exclusive lock (msvcrt.locking)
      sulur.db.daemon.log      — daemon stdout/stderr

Two Deployment Modes

Mode Who uses it DB lock holder Selected by
daemon (default for Hermes) Hermes adapter, MCP server, CLI Shared sulur-server process SULUR_KUZU_MODE=daemon (forced by the adapter)
embedded (explicit opt-in) Standalone from sulur import Sulur The engine's own process SULUR_KUZU_MODE=embedded (never automatic)

Decided direction: Daemon mode is the single operational path for multi-session Hermes. Embedded remains only as explicit opt-in for tests/CI/true single-process standalone. Automatic embedded/SQLite fallbacks have been removed for the daemon/kuzudb path — failures fail loud.

Concurrency

The thin adapter (hermes-plugins/sulur/client.py) delegates concurrency to the daemon — no in-process thread pools. The daemon serves each client connection on its own Tokio task with serialised writes and concurrent reads.

Cache

diskcache-only (Redis removed per architectural decision):

Domain TTL Contents
recall 300s Recall result cache
dream 3600s Dream queue + status
session 3600s Session buffers

Quick Start

# 0. One-line install (needs `uv`; pulls sulur + akar + fastembed as PyPI
#    wheels, deploys the Hermes plugin, no Akar build) — see docs/HOWTO.md
uvx --from "sulur" sulur install --check

# 1. Install Sulur package
pip install -e .

# 2. Deploy Hermes adapter plugin (one-time)
.\tools\deploy-sulur-provider.ps1

# 3. Configure Hermes
hermes memory setup sulur
# → prompts: db_path (default: ~/.sulur/engine/sulur.db)
# → prompts: embedding_backend (auto/hash/tfidf/sentence-transformers)

# 4. Verify
python -m sulur.installer install --check   # runs embedded smoke in the Hermes venv
python -m sulur.installer status            # reports plugin/provider deployment state
hermes config get memory
# → provider: sulur

Standalone (Single-Process)

# Explicit embedded mode (opt-in)
SULUR_KUZU_MODE=embedded python -c "
from sulur import Sulur
mem = Sulur()
mem.remember('The user has a dog named Lou')
results = mem.recall('What pet does the user have?')
mem.think(results[0].id)
"

Features

🧠 Core Memory

Function Description
remember() Store a fact with embedding + auto-connection to related memories
recall() Multi-channel recall (semantic, graph, temporal, FTS)
think() Spreading activation — explore connected memories
graph() Connection graph summary

🌙 Dream Engine (Background Consolidation)

Since Iterasi 4 (P4-RETIRE-1) the cycle runs inside sulur-server (Rust, sulur-core::dream) as a background task — no leader session, no Python _dream_pool. It auto-triggers on idle/new-memory thresholds (defaults: idle=300s, memory_threshold=50).

Phase What it does Notes / caveats
NREM Replay & strengthen active edges, prune weak (<0.05) Batch writes chunked (see §9.1)
Supersedes Directed "older→newer" edge for value-changed facts Only pairs with numeric tokens and differing tokens and cosine ≥ 0.85 (hardcoded). Does not dedup exact duplicates.
REM Bridge discovery for isolated memories Up to max_isolated
Insight Louvain community detection → derived:cluster nodes
AFE Atomic Fact Extraction: Stage A/B = regex (afe.py), Stage C = LLM user-state (SULUR_AFE_LLM_FALLBACK=1) Stage A/B have no LLM prompt; fragment quality governed by regex filters
Synthesis Stage S crystallization, grouped by source memory LLM mode via SULUR_SYNTHESIS_LLM=1
DAE Graph-weighted second embedding recompute Every N NREM cycles; batch chunked

🔧 Tools (MemoryProvider)

Sulur registers 38+ tools via Hermes MemoryProvider: sulur_remember, sulur_recall, sulur_think, sulur_graph, sulur_reasoning, sulur_profile, sulur_dream_*, metadata catalog tools, and more.

📊 Metadata Catalog

Semantic search over database schemas, tables, columns, and business terms — imported from BigQuery, SQL databases, or YAML definitions.


Dream Engine Auto-Trigger

Dream daemon runs automatically as a background task inside sulur-server. Auto-trigger based on:

  • idle_threshold — seconds without activity before trigger (default: 300)
  • memory_threshold — new memories before trigger (default: 50)

Configure via sulur_dream_config():

sulur_dream_config(action="set", idle_threshold=7200, memory_threshold=10)

No cron job, no SSE server, no external process. Single daemon holds the lock; all sessions share it.


Storage Backends

Backend Type Status
Akar Embedded graph (pure Rust; import kuzu → import akar) Production default (daemon mode) — graph + vector index
SQLite Embedded relational (stdlib + numpy; no akar needed) Secondary backend (SULUR_DB_BACKEND=sqlite + SULUR_KUZU_MODE=embedded)

Postgres/Supabase were removed (2026-08-21, Finding #19). The two remaining backends are akar and sqlite (see the "Only Akar or SQLite" gotcha in AGENTS.md). They are interchangeable behind StoreProtocol and cannot silently fall back to each other; the backend is chosen once at boot and any unsupported value fails loud. In daemon mode, graph DB failure fails loud — no silent fallback to an empty store.


Project Structure

sulur/
├── sulur/                        # Python package utama
│   ├── __init__.py                # Re-export ringan (Sulur, Memory)
│   ├── engine.py                  # Sulur API — unified entry point
│   ├── cache.py                  # Cache via diskcache (Redis dihapus — diskcache only)
│   ├── disk_fallback.py           # DiskCacheDomain wrapper
│   ├── mcp_schemas.py             # Tool schemas — single source of truth
│   ├── daemon_client.py           # DaemonClientStore — klien sulur-server (daemon mode)
│   ├── kuzu_ipc.py                # JSON framing (u32-LE length prefix) + sidecar/lock
│   ├── kuzu.py                    # Shim: `import kuzu` → `import akar`
│   ├── akar_store.py              # GraphStore (Akar) embedded mode
│   ├── afe.py                     # Atomic Fact Extraction
│   ├── dae.py                     # Dream-Augmented Embeddings
│   ├── sync_store.py              # Cross-backend sync
│   ├── embed_provider.py          # Embedding backends
│   └── ...                        # Other support files
├── hermes-plugins/sulur/          # Sanctioned thin adapter (MemoryProvider, client, cli)
├── crates/                        # Rust workspace (daemon + core)
│   ├── sulur-core/                # schema / formasi / recall / forget / siklus dream 7 fase
│   ├── sulur-server/              # daemon Tokio: siklus latar, op Hermes, sidecar
│   └── sulur-mcp/                 # permukaan MCP stdio (9 tool)
├── docs/                          # Documentation
│   ├── ARCHITECTURE.md            # Canonical architecture (this is the source of truth)
│   └── references/                # Historical reference notes
├── tools/
│   ├── deploy-sulur-provider.ps1 # One-command Hermes adapter deployment
│   └── sulur_doctor.py           # Read-only triage: sidecar, daemon, locks, backups
├── tests/
│   └── test_suite.py              # Test suite
└── pyproject.toml                 # Package metadata + dependencies

Hermes Adapter Plugin

The memory provider adapter lives at $HERMES_HOME/plugins/sulur/ (NOT under plugins/memory/):

$HERMES_HOME/plugins/sulur/
├── __init__.py          # Thin entry: from hermes_plugins.sulur.client import MemoryProvider; register()
└── plugin.yaml          # Metadata + pip_dependencies: [sulur]

Deployment

# One-command deploy (installs package, creates adapter, cleans legacy)
.\tools\deploy-sulur-provider.ps1

CLI (hermes sulur)

Deployed alongside the plugin (plugins/sulur/cli.py) and discovered by Hermes' plugin CLI registration. Talks to the engine via its public API (stats(), dream_stats(), dream_control()) — daemon RPC, never spawning an engine or touching the DB directly.

hermes sulur status               # Compact engine summary (backend, memories, connections, embedding dim, dream state)
hermes sulur stats [--pretty]     # Full engine stats as JSON (+ dream stats)
hermes sulur dream status|pause|resume|reset   # Dream Engine control/inspect

Desktop UI Config

Sulur appears in Hermes Desktop → Settings → Memory Provider with fields:

  • db_path (text, default: ~/.sulur/engine/sulur.db)
  • embedding_backend (select: auto, hash, tfidf, sentence-transformers)

MCP Server (stdio)

The stdio MCP server runs as an independent process using the Rust sulur-mcp crate, also in daemon mode. It sets SULUR_KUZU_MODE=daemon and SULUR_NO_FALLBACK=1 on startup.

Tools exposed: sulur_remember, sulur_recall, sulur_think, sulur_graph, sulur_reasoning, sulur_profile, sulur_dream_*, metadata catalog tools.

Backup: Supabase/Postgres sync removed (2026-08-21). For version-safe backup use sulur_backup_local (daemon EXPORT DATABASE op) — see docs/ARCHITECTURE.md §8.


Observability & Troubleshooting

Health Check

# Via tool (plugin)
sulur_health
# → {status, backend, memories, connections, embedding_dim, storage: {daemon_pid, sidecar_age, ...}}

# Via installer (deployment state only)
python -m sulur.installer status

# Via CLI (daemon RPC)
hermes sulur status
hermes sulur stats

Daemon Status

# Cek proses daemon
Get-CimInstance Win32_Process -Filter "Name LIKE 'sulur-server%'"

# Cek sidecar
Get-Content "$env:USERPROFILE\.sulur\engine\sulur.db.daemon.json" | ConvertFrom-Json

# Cek log daemon
Get-Content "$env:USERPROFILE\.sulur\engine\sulur.db.daemon.log" -Tail 50

Doctor Script (Comprehensive Triage)

python tools/sulur_doctor.py
# → healthy  OR  "1 issue(s) found: UNREACHABLE/STALE/LOCK_HELD"

Checks: sidecar liveness, daemon reachability, spawn/leader locks, backup freshness.

Common Issues

Symptom Cause Fix
memories: 0 but DB 40MB graph DB version mismatch Use logical export/import; check sulur_doctor.py
Could not set lock on file Stale lock / zombie daemon Remove-Item sulur.db.daemon.lock -Force; kill zombie python; restart
sulur_health / memories: 0 DB path not resolved to the store in use Check ~/.sulur/engine/db_path.txt — sulur/db_path.py (get_canonical_db_path()) is the only authority; run sulur_doctor.py
Dream not running Daemon not spawning or crashed sulur_doctor.py; the cycle runs inside sulur-server, so a healthy daemon is the precondition

License

Business Source License 1.1 — View License

  • Non-commercial use: free
  • Commercial use: requires a paid license
  • After 2030-01-01: automatically converts to Apache 2.0

Forks & Attribution

Sulur was forked from Mazemaker and evolved independently with:

  • Akar (pure Rust) as the primary backend (embedded, no Docker)
  • Complete Dream Backend for all store types
  • Daemon-first architecture — single sulur-server (Rust) holds the exclusive lock
  • Hermes MemoryProvider adapter plugin (thin, imports from package)
  • Auto-trigger dream daemon (no cron job)
  • Storage hardening: bounded retries, no silent fallbacks, backup-as-daemon-op, WAL crash fix, observability (stat op, sulur_health storage section, sulur_doctor)
  • Cleaned architecture, no license gates

Built with ❤️ for the Indonesian AI ecosystem.

Metadata

Release files for sulur 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sulur 1.1.1
File Size Uploaded Provenance
sulur-1.1.1.tar.gz 338.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sulur 1.1.1
File Interpreter ABI Platform
sulur-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 658.1 kB

Release files / sulur-1.1.1.tar.gz

Download URL sulur-1.1.1.tar.gz
Size 338.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a3925bd5e1482e221d330912e1e74e49f9c0ed8ad7336ece8a66b9b47c26e48f
BLAKE2b-256 checksum
How to use checksums
08e50753a1f0e3df1208f517a2bad19b5de19a0b53c88045e68d01ba7cd4faa1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / sulur-1.1.1-py3-none-any.whl

Download URL sulur-1.1.1-py3-none-any.whl
Size 319.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f1962181512f86decaf0af1ce67b8c13caae46edad224362349f4a14008beece
BLAKE2b-256 checksum
How to use checksums
df0e31e3498a70ff9c752edcf126495cad3a9a0d14e022b2e9d077a707a65eb2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for sulur 1.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page